简单的基于Java名称的锁?

wor*_*pet 57 java locking

MySQL有一个方便的功能:

SELECT GET_LOCK("SomeName")
Run Code Online (Sandbox Code Playgroud)

这可用于为应用程序创建简单但非常具体的基于名称的锁.但是,它需要数据库连接.

我有很多情况,比如:

someMethod() {
    // do stuff to user A for their data for feature X
}
Run Code Online (Sandbox Code Playgroud)

它没有意义的只是同步的这种方法,因为,例如,如果调用此方法对于在此期间用户B,用户B不需要等待用户A完成它开始之前,用户只能操作A和功能X组合需要等待.

使用MySql锁我可以做类似的事情:

someMethod() {
    executeQuery("SELECT GET_LOCK('userA-featureX')")
    // only locked for user A for their data for feature X
    executeQuery("SELECT RELEASE_LOCK('userA-featureX')")
}
Run Code Online (Sandbox Code Playgroud)

由于Java锁定是基于对象的,因此我似乎需要创建一个新对象来表示此锁的情况,然后将其放在某个地方的静态缓存中,以便所有线程都可以看到它.锁定该情况的后续请求然后将锁定对象定位在高速缓存中并获取其锁定.我试图创建这样的东西,但锁缓存本身需要同步.此外,很难检测何时不再使用锁定对象,以便可以从缓存中删除锁定对象.

我查看了Java并发软件包,但没有什么能够处理这样的事情.有没有一种简单的方法来实现这一点,还是我从错误的角度看待这个?

编辑:

为了澄清,我不打算提前创建一个预定义的锁池,我想按需创建它们.我想的一些伪代码是:

LockManager.acquireLock(String name) {
    Lock lock;  

    synchronized (map) {
        lock = map.get(name);

        // doesn't exist yet - create and store
        if(lock == null) {
            lock = new Lock();
            map.put(name, lock);
        }
    }

    lock.lock();
}

LockManager.releaseLock(String name) {
    // unlock
    // if this was the last hold on the lock, remove it from the cache
}
Run Code Online (Sandbox Code Playgroud)

dmo*_*ius 33

我看到的所有答案都太复杂了.为什么不简单地使用:

public void executeInNamedLock(String lockName, Runnable runnable) {
  synchronized(lockName.intern()) {
    runnable.run();
  }
}
Run Code Online (Sandbox Code Playgroud)

关键点是方法intern:它确保返回的String是全局唯一对象,因此它可以用作vm实例范围的互斥锁.所有实习字符串都保存在一个全局池中,因此这是您在原始问题中讨论的静态缓存.不要担心memleaks; 如果没有其他线程引用它,那些字符串将被gc'ed.但是请注意,直到并包括Java6此池都保存在PermGen空间而不是堆中,因此您可能需要增加它.

但是如果你的vm中的某些其他代码由于完全不同的原因锁定在同一个字符串上会有问题,但是a)这是不太可能的,并且b)你可以通过引入名称空间来绕过它,例如 executeInNamedLock(this.getClass().getName() + "_" + myLockName);

  • 看看为什么实习是一个坏主意:http://stackoverflow.com/questions/133988/problem-with-synchronizing-on-string-objects (12认同)
  • 这看起来很棒 - 但这有多安全?这是在假设JVM当前如何工作的情况下工作,还是通过规范确实intern()'ed值是"=="? (3认同)
  • 在该线程中,所有关注点都是关于通过String.intern()创建的全局状态引入看不见的依赖关系,我用最后一段中的"命名空间"模式克服了这种关系. (3认同)
  • [Guava`Interner`为`String.intern()`提供与其他不可变类型相同的行为](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/Interner. HTML) (2认同)

sjr*_*sjr 21

你有Map<String, java.util.concurrent.Lock>吗?每次你需要一个锁,你基本上都会打电话map.get(lockName).lock().

以下是使用Google Guava的示例:

Map<String, Lock> lockMap = new MapMaker().makeComputingMap(new Function<String, Lock>() {
  @Override public Lock apply(String input) {
    return new ReentrantLock();
  }
});
Run Code Online (Sandbox Code Playgroud)

然后,如果需要,lockMap.get("anyOldString")将导致创建一个新锁并返回给您.然后你可以调用那个锁.返回一个线程安全的Map,因此您可以与所有线程共享.lock()makeComputingMap

  • 我认为你应该考虑将Google Guava作为标准库的一部分.它比Apache公共库非常有用且质量更高.你怎么知道什么时候不再使用锁或者锁已经锁定?您可以要求MapMaker使一段时间之前创建的条目失效,但这可能会导致违反您尝试创建的互斥规则(例如,如果有人持有锁的时间超过过期时间). (7认同)
  • @srj:不是简单地使用`weakValues()`吗?如果持有锁的线程终止(有或没有释放它),那么它将被GC. (3认同)
  • 这接近于我要寻找的内容,但是我希望仅使用标准库或apache commons库来创建一些东西。这解决了如何按需创建锁,但是我仍然不确定如何清除未使用的旧锁。可能有成千上万的用户,但一次最多只有大约一百个用户处于活动状态。 (2认同)

irr*_*ble 20

// pool of names that are being locked
HashSet<String> pool = new HashSet<String>(); 

lock(name)
    synchronized(pool)
        while(pool.contains(name)) // already being locked
            pool.wait();           // wait for release
        pool.add(name);            // I lock it

unlock(name)
    synchronized(pool)
        pool.remove(name);
        pool.notifyAll();
Run Code Online (Sandbox Code Playgroud)

  • @teerapap对`pool.wait()`的调用使得线程失去对`pool`的锁定. (6认同)

moj*_*moj 15

也许这对你有用:jkeylockmanager

编辑:

我最初的反应可能有点短.我是作者,多次遇到这个问题,找不到现有的解决方案.这就是我在Google Code上创建这个小型库的原因.

  • 如果您是此工具/ lib的作者,那么可以提及它...... (4认同)

Dou*_*eri 9

也许稍晚但你可以使用Google Guava Striped

从概念上讲,锁定条带化是将锁分为多个条带的技术,增加了单个锁的粒度,允许独立操作锁定不同的条带并同时进行,而不是为单个锁创建争用.

//init
stripes=Striped.lazyWeakLock(size);
//or
stripes=Striped.lock(size);
//...
Lock lock=stripes.get(object);
Run Code Online (Sandbox Code Playgroud)

  • `size`参数确定在封面下管理的单个锁的数量.Striped根据哈希码为密钥分配锁.您应该将`size`设置为与您尝试通过锁定保护的对象数成比例.例如,使用`size` = 1来保护100个对象与独占锁定相同; `size` = 2将对所有100个对象使用2个锁,因此你只有50%的机会获得任何并发; 在计算大小时,也可能值得考虑有多少线程共享访问权限. (3认同)
  • 什么是正确的"大小"给予?我使用10并不确定它是如何工作的 (2认同)

McD*_*ell 6

对于像用户名这样的东西的锁定,Lock地图中的内存中的s可能有点漏洞.作为替代方案,您可以使用WeakReferenceWeakHashMap一起创建互斥对象,当没有任何引用它们时可以对其进行垃圾回收.这可以避免您必须进行任何手动引用计数以释放内存.

你可以在这里找到一个实现.请注意,如果您在地图上频繁查找,则可能会遇到获取互斥锁的争用问题.


Pab*_*tti 5

使用java.util.concurrent的通用解决方案

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

public class LockByName<L> {

    ConcurrentHashMap<String, L> mapStringLock;

    public LockByName(){
        mapStringLock = new ConcurrentHashMap<String, L>();
    }

    public LockByName(ConcurrentHashMap<String, L> mapStringLock){
        this.mapStringLock = mapStringLock;
    }

    @SuppressWarnings("unchecked")
    public L getLock(String key) {
        L initValue = (L) createIntanceLock();
        L lock = mapStringLock.putIfAbsent(key, initValue);
        if (lock == null) {
            lock = initValue;
        }
        return lock;
    }

    protected Object createIntanceLock() {
        return new ReentrantLock();
    }

    public static void main(String[] args) {

        LockByName<ReentrantLock> reentrantLocker = new LockByName<ReentrantLock>();

        ReentrantLock reentrantLock1 = reentrantLocker.getLock("pepe");

        try {
            reentrantLock1.lock();
            //DO WORK

        }finally{
            reentrantLock1.unlock();

        }


    }

}
Run Code Online (Sandbox Code Playgroud)

  • 看来地图只能增大尺寸.另外,为什么检查if(lock == null),它必须至少返回你传入的那个. (3认同)