简单的基于Java名称的锁?

时间:2011-04-12 18:26:32

标签: java locking

MySQL有一个方便的功能:

SELECT GET_LOCK("SomeName")

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

我有很多情况:

someMethod() {
    // do stuff to user A for their data for feature X
}

简单地同步此方法没有意义,因为,例如,如果同时为用户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')")
}

由于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
}

23 个答案:

答案 0 :(得分:32)

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

public void executeInNamedLock(String lockName, Runnable runnable) {
  synchronized(lockName.intern()) {
    runnable.run();
  }
}

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

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

答案 1 :(得分: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();
  }
});

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

答案 2 :(得分: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();

答案 3 :(得分:14)

这可能对您有用:jkeylockmanager

编辑:

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

答案 4 :(得分:8)

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

  

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

.css( "display", "none")

答案 5 :(得分:6)

为了锁定类似用户名的内容,地图中的内存Lock可能有点漏洞。作为替代方案,您可以查看使用带有WeakReferenceWeakHashMap来创建互斥对象,当没有任何引用它们时可以对其进行垃圾回收。这样可以避免进行任何手动引用计数以释放内存。

您可以找到实施here。请注意,如果您在地图上频繁查找,则可能会遇到获取互斥锁的争用问题。

答案 6 :(得分: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();

        }


    }

}

答案 7 :(得分:4)

基于answer of McDowell和他的班级IdMutexProvider,我编写了使用WeakHashMap存储锁定对象的泛型类LockMapLockMap.get()可用于检索密钥的锁定对象,然后可以将其与Java synchronized (...)语句一起使用以应用锁定。垃圾收集过程中会自动释放未使用的锁定对象。

import java.lang.ref.WeakReference;
import java.util.WeakHashMap;

// A map that creates and stores lock objects for arbitrary keys values.
// Lock objects which are no longer referenced are automatically released during garbage collection.
// Author: Christian d'Heureuse, www.source-code.biz
// Based on IdMutexProvider by McDowell, http://illegalargumentexception.blogspot.ch/2008/04/java-synchronizing-on-transient-id.html
// See also https://stackoverflow.com/questions/5639870/simple-java-name-based-locks
public class LockMap<KEY> {

private WeakHashMap<KeyWrapper<KEY>,WeakReference<KeyWrapper<KEY>>> map;

public LockMap() {
   map = new WeakHashMap<KeyWrapper<KEY>,WeakReference<KeyWrapper<KEY>>>(); }

// Returns a lock object for the specified key.
public synchronized Object get (KEY key) {
   if (key == null) {
      throw new NullPointerException(); }
   KeyWrapper<KEY> newKeyWrapper = new KeyWrapper<KEY>(key);
   WeakReference<KeyWrapper<KEY>> ref = map.get(newKeyWrapper);
   KeyWrapper<KEY> oldKeyWrapper = (ref == null) ? null : ref.get();
   if (oldKeyWrapper != null) {
      return oldKeyWrapper; }
   map.put(newKeyWrapper, new WeakReference<KeyWrapper<KEY>>(newKeyWrapper));
   return newKeyWrapper; }

// Returns the number of used entries in the map.
public synchronized int size() {
   return map.size(); }

// KeyWrapper wraps a key value and is used in three ways:
// - as the key for the internal WeakHashMap
// - as the value for the internal WeakHashMap, additionally wrapped in a WeakReference
// - as the lock object associated to the key
private static class KeyWrapper<KEY> {
   private KEY key;
   private int hashCode;
   public KeyWrapper (KEY key) {
      this.key = key;
      hashCode = key.hashCode(); }
   public boolean equals (Object obj) {
      if (obj == this) {
         return true; }
      if (obj instanceof KeyWrapper) {
         return ((KeyWrapper)obj).key.equals(key); }
      return false; }
   public int hashCode() {
      return hashCode; }}

} // end class LockMap

如何使用LockMap类的示例:

private static LockMap<String> lockMap = new LockMap<String>();

synchronized (lockMap.get(name)) {
   ... 
}

LockMap类的简单测试程序:

public static Object lock1;
public static Object lock2;

public static void main (String[] args) throws Exception {
   System.out.println("TestLockMap Started");
   LockMap<Integer> map = new LockMap<Integer>();
   lock1 = map.get(1);
   lock2 = map.get(2);
   if (lock2 == lock1) {
      throw new Error(); }
   Object lock1b = map.get(1);
   if (lock1b != lock1) {
      throw new Error(); }
   if (map.size() != 2) {
      throw new Error(); }
   for (int i=0; i<10000000; i++) {
      map.get(i); }
   System.out.println("Size before gc: " + map.size());   // result varies, e.g. 4425760
   System.gc();
   Thread.sleep(1000);
   if (map.size() != 2) {
      System.out.println("Size after gc should be 2 but is " + map.size()); }
   System.out.println("TestLockMap completed"); }

如果有人知道自动测试LockMap类的更好方法,请写一条评论。

答案 8 :(得分:3)

我想注意ConcurrentHashMap具有内置锁定功能,足以实现简单的独占多线程锁定。无需额外的Lock个对象。

以下是此类锁定映射的示例,用于为单个客户端最多强制执行一次活动jms处理。

private static final ConcurrentMap<String, Object> lockMap = new ConcurrentHashMap<String, Object>();
private static final Object DUMMY = new Object();

private boolean tryLock(String key) {
    if (lockMap.putIfAbsent(key, DUMMY) != null) {
        return false;
    }
    try {
        if (/* attempt cluster-wide db lock via select for update nowait */) {
            return true;
        } else {
            unlock(key);
            log.debug("DB is already locked");
            return false;
        }
    } catch (Throwable e) {
        unlock(key);
        log.debug("DB lock failed", e);
        return false;
    }
}

private void unlock(String key) {
    lockMap.remove(key);
}

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void onMessage(Message message) {
    String key = getClientKey(message);
    if (tryLock(key)) {
        try {
            // handle jms
        } finally {
            unlock(key);
        }
    } else {
        // key is locked, forcing redelivery
        messageDrivenContext.setRollbackOnly();
    }
}

答案 9 :(得分:2)

2年后,我正在寻找一个简单命名的储物柜解决方案,并且遇到了这个,很有用,但我需要一个更简单的答案,所以低于我的想法。

在某个名称下进行简单锁定,然后以相同名称再次发布。

private void doTask(){
  locker.acquireLock(name);
  try{
    //do stuff locked under the name
  }finally{
    locker.releaseLock(name);
  }
}

以下是代码:

public class NamedLocker {
    private ConcurrentMap<String, Semaphore> synchSemaphores = new ConcurrentHashMap<String, Semaphore>();
    private int permits = 1;

    public NamedLocker(){
        this(1);
    }

    public NamedLocker(int permits){
        this.permits = permits;
    }

    public void acquireLock(String... key){
        Semaphore tempS = new Semaphore(permits, true);
        Semaphore s = synchSemaphores.putIfAbsent(Arrays.toString(key), tempS);
        if(s == null){
            s = tempS;
        }
        s.acquireUninterruptibly();
    }

    public void releaseLock(String... key){
        Semaphore s = synchSemaphores.get(Arrays.toString(key));
        if(s != null){
            s.release();
        }
    }
}

答案 10 :(得分:1)

也许是这样的:

public class ReentrantNamedLock {

private class RefCounterLock {

    public int counter;
    public ReentrantLock sem;

    public RefCounterLock() {
        counter = 0;
        sem = new ReentrantLock();
    }
}
private final ReentrantLock _lock = new ReentrantLock();
private final HashMap<String, RefCounterLock> _cache = new HashMap<String, RefCounterLock>();

public void lock(String key) {
    _lock.lock();
    RefCounterLock cur = null;
    try {
        if (!_cache.containsKey(key)) {
            cur = new RefCounterLock();
            _cache.put(key, cur);
        } else {
            cur = _cache.get(key);
        }
        cur.counter++;
    } finally {
        _lock.unlock();
    }
    cur.sem.lock();
}

public void unlock(String key) {
    _lock.lock();
    try {
        if (_cache.containsKey(key)) {
            RefCounterLock cur = _cache.get(key);
            cur.counter--;
            cur.sem.unlock();
            if (cur.counter == 0) { //last reference
                _cache.remove(key);
            }
            cur = null;
        }
    } finally {
        _lock.unlock();
    }
}}

我没有测试它。

答案 11 :(得分:1)

许多实现但与我的非常相似。

将我的动态锁实现称为ProcessDynamicKeyLock,因为它是一个单一进程锁,对于任何对象都是键(等于+ hashcode用于唯一性)。

TODO:添加提供实际锁定的方法,例如ReentrantReadWriteLock而不是ReentrantLock

实现:

public class ProcessDynamicKeyLock<T> implements Lock
{
    private final static ConcurrentHashMap<Object, LockAndCounter> locksMap = new ConcurrentHashMap<>();

    private final T key;

    public ProcessDynamicKeyLock(T lockKey)
    {
        this.key = lockKey;
    }

    private static class LockAndCounter
    {
        private final Lock lock = new ReentrantLock();
        private final AtomicInteger counter = new AtomicInteger(0);
    }

    private LockAndCounter getLock()
    {
        return locksMap.compute(key, (key, lockAndCounterInner) ->
        {
            if (lockAndCounterInner == null) {
                lockAndCounterInner = new LockAndCounter();
            }
            lockAndCounterInner.counter.incrementAndGet();
            return lockAndCounterInner;
        });
    }

    private void cleanupLock(LockAndCounter lockAndCounterOuter)
    {
        if (lockAndCounterOuter.counter.decrementAndGet() == 0)
        {
            locksMap.compute(key, (key, lockAndCounterInner) ->
            {
                if (lockAndCounterInner == null || lockAndCounterInner.counter.get() == 0) {
                    return null;
                }
                return lockAndCounterInner;
            });
        }
    }

    @Override
    public void lock()
    {
        LockAndCounter lockAndCounter = getLock();

        lockAndCounter.lock.lock();
    }

    @Override
    public void unlock()
    {
        LockAndCounter lockAndCounter = locksMap.get(key);
        lockAndCounter.lock.unlock();

        cleanupLock(lockAndCounter);
    }


    @Override
    public void lockInterruptibly() throws InterruptedException
    {
        LockAndCounter lockAndCounter = getLock();

        try
        {
            lockAndCounter.lock.lockInterruptibly();
        }
        catch (InterruptedException e)
        {
            cleanupLock(lockAndCounter);
            throw e;
        }
    }

    @Override
    public boolean tryLock()
    {
        LockAndCounter lockAndCounter = getLock();

        boolean acquired = lockAndCounter.lock.tryLock();

        if (!acquired)
        {
            cleanupLock(lockAndCounter);
        }

        return acquired;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException
    {
        LockAndCounter lockAndCounter = getLock();

        boolean acquired;
        try
        {
            acquired = lockAndCounter.lock.tryLock(time, unit);
        }
        catch (InterruptedException e)
        {
            cleanupLock(lockAndCounter);
            throw e;
        }

        if (!acquired)
        {
            cleanupLock(lockAndCounter);
        }

        return acquired;
    }

    @Override
    public Condition newCondition()
    {
        LockAndCounter lockAndCounter = locksMap.get(key);

        return lockAndCounter.lock.newCondition();
    }
}

简单测试:

public class ProcessDynamicKeyLockTest
{
    @Test
    public void testDifferentKeysDontLock() throws InterruptedException
    {
        ProcessDynamicKeyLock<Object> lock = new ProcessDynamicKeyLock<>(new Object());
        lock.lock();
        AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false);
        try
        {
            new Thread(() ->
            {
                ProcessDynamicKeyLock<Object> anotherLock = new ProcessDynamicKeyLock<>(new Object());
                anotherLock.lock();
                try
                {
                    anotherThreadWasExecuted.set(true);
                }
                finally
                {
                    anotherLock.unlock();
                }
            }).start();
            Thread.sleep(100);
        }
        finally
        {
            Assert.assertTrue(anotherThreadWasExecuted.get());
            lock.unlock();
        }
    }

    @Test
    public void testSameKeysLock() throws InterruptedException
    {
        Object key = new Object();
        ProcessDynamicKeyLock<Object> lock = new ProcessDynamicKeyLock<>(key);
        lock.lock();
        AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false);
        try
        {
            new Thread(() ->
            {
                ProcessDynamicKeyLock<Object> anotherLock = new ProcessDynamicKeyLock<>(key);
                anotherLock.lock();
                try
                {
                    anotherThreadWasExecuted.set(true);
                }
                finally
                {
                    anotherLock.unlock();
                }
            }).start();
            Thread.sleep(100);
        }
        finally
        {
            Assert.assertFalse(anotherThreadWasExecuted.get());
            lock.unlock();
        }
    }
}

答案 12 :(得分:1)

在失望之后,没有语言级支持或简单的Guava / Commons类用于命名锁,

这是我安定下来的:

ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();

Object getLock(String name) {
    Object lock = locks.get(name);
    if (lock == null) {
        Object newLock = new Object();
        lock = locks.putIfAbsent(name, newLock);
        if (lock == null) {
            lock = newLock;
        }
    }
    return lock;
}

void somethingThatNeedsNamedLocks(String name) {
    synchronized(getLock(name)) {
        // some operations mutually exclusive per each name
    }
}

我在这里实现了:没有库依赖的小样板代码,原子地获取锁定对象,没有污染全局实体化字符串对象,没有低级别通知/等待混乱,也没有尝试捕获最终混乱。

答案 13 :(得分:1)

记忆考虑

通常,特定密钥所需的同步是短暂的。保持释放的密钥可能会导致过多的内存浪费,使其无法生存。

这里的实现并没有在内部保留已发布的密钥。

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;

public class KeyedMutexes<K> {

    private final ConcurrentMap<K, CountDownLatch> key2Mutex = new ConcurrentHashMap<>();

    public void lock(K key) throws InterruptedException {
        final CountDownLatch ourLock = new CountDownLatch(1);
        for (;;) {
            CountDownLatch theirLock = key2Mutex.putIfAbsent(key, ourLock);
            if (theirLock == null) {
                return;
            }
            theirLock.await();
        }
    }

    public void unlock(K key) {
        key2Mutex.remove(key).countDown();
    }
}

可重入,以及其他花里胡哨

如果想要重入锁定语义,他们可以通过将互斥对象包装在跟踪锁定线程和锁定计数的类中来扩展上述内容。

e.g:

private static class Lock {
    final CountDownLatch mutex = new CountDownLatch(1);

    final long threadId = Thread.currentThread().getId();

    int lockedCount = 1;
}

如果有人希望lock()返回一个对象以使发布更容易和更安全,那也是可能的。

总而言之,这就是课程的样子:

public class KeyedReentrantLocks<K> {

    private final ConcurrentMap<K, KeyedLock> key2Lock = new ConcurrentHashMap<>();

    public KeyedLock acquire(K key) throws InterruptedException {
        final KeyedLock ourLock = new KeyedLock() {
            @Override
            public void close() {
                if (Thread.currentThread().getId() != threadId) {
                    throw new IllegalStateException("wrong thread");
                }
                if (--lockedCount == 0) {
                    key2Lock.remove(key);
                    mutex.countDown();
                }
            }
        };
        for (;;) {
            KeyedLock theirLock = key2Lock.putIfAbsent(key, ourLock);
            if (theirLock == null) {
                return ourLock;
            }
            if (theirLock.threadId == Thread.currentThread().getId()) {
                theirLock.lockedCount++;
                return theirLock;
            }
            theirLock.mutex.await();
        }
    }

    public static abstract class KeyedLock implements AutoCloseable {
        protected final CountDownLatch mutex = new CountDownLatch(1);
        protected final long threadId = Thread.currentThread().getId();
        protected int lockedCount = 1;

        @Override
        public abstract void close();
    }
}

以下是人们如何使用它:

try (KeyedLock lock = locks.acquire("SomeName")) {

    // do something critical here
}

答案 14 :(得分:1)

与Lyomi的答案类似,但使用更灵活的ReentrantLock而不是同步块。

public class NamedLock
{
    private static final ConcurrentMap<String, Lock> lockByName = new ConcurrentHashMap<String, Lock>();

    public static void lock(String key)
    {
        Lock lock = new ReentrantLock();
        Lock existingLock = lockByName.putIfAbsent(key, lock);

        if(existingLock != null)
        {
            lock = existingLock;
        }
        lock.lock();
    }

    public static void unlock(String key) 
    {
        Lock namedLock = lockByName.get(key);
        namedLock.unlock();
    }
}

是的,这会随着时间的推移而增长 - 但使用ReentrantLock可以更好地从地图中删除锁定。虽然从地图中删除项目看起来并不是很有用,但考虑从地图中删除值不会缩小其大小。必须实施一些手动地图大小调整逻辑。

答案 15 :(得分:0)

这是一个简单且优化的解决方案,它也解决了使用过的锁的删除问题,但是还有Map的同步开销:

public class NamedLock {
private Map<String, ReentrantLock> lockMap;

public NamedLock() {
    lockMap = new HashMap<>();
}

public void lock(String... name) {
    ReentrantLock newLock = new ReentrantLock(true);
    ReentrantLock lock;
    synchronized (lockMap) {
        lock = Optional.ofNullable(lockMap.putIfAbsent(Arrays.toString(name), newLock)).orElse(newLock);
    }
    lock.lock();
}

public void unlock(String... name) {
    ReentrantLock lock = lockMap.get(Arrays.toString(name));
    synchronized (lockMap) {
        if (!lock.hasQueuedThreads()) {
            lockMap.remove(name);
        }
    }
    lock.unlock();
}    

}

答案 16 :(得分:0)

当遇到与原始海报相同的要求时,我实施并测试了另一种可能的解决方案。
在这个解决方案中:

  • 没有外部库
  • 不将未使用的对象留在内存中
  • 最少使用 this.Books 和最少的“交叉名称”锁定
  • 使用 synchronized 没有缺点

助手类代码:

intern

用法:

public class IdBasedLockHelper<T> {

    private final static AtomicIntegerWithEquals zero = new AtomicIntegerWithEquals(0);
    private final ConcurrentMap<T, AtomicIntegerWithEquals> identifierToLockCounter = new ConcurrentHashMap<>();
    

    public void executeLocked(T lockId, Runnable runnable) {
        AtomicIntegerWithEquals counterAndLock = identifierToLockCounter.compute(lockId, (key, existing) -> {
            if (existing == null) {
                return new AtomicIntegerWithEquals(1);
            }
            existing.atomicValue.incrementAndGet();
            return existing;
        });

        synchronized (counterAndLock) {
            try {
                runnable.run();
            } finally {
                counterAndLock.atomicValue.decrementAndGet();
                identifierToLockCounter.remove(lockId, zero);
            }
        }
    }

    // AtomicInteger does not implement equals() properly so there is a need for such wrapper
    private static class AtomicIntegerWithEquals {

        private final AtomicInteger atomicValue;

        AtomicIntegerWithEquals(int value) {
            this.atomicValue = new AtomicInteger(value);
        }

        // Used internally by remove()
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof IdBasedLockHelper.AtomicIntegerWithEquals)) return false;
            return atomicValue.get() == ((AtomicIntegerWithEquals) o).atomicValue.get();
        }

        // Not really used, but when implementing custom equals() it is a good practice to implement also hashCode()
        @Override
        public int hashCode() {
            return atomicValue.get();
        }
    }
}

IdBasedLockHelper<String> idBasedLockHelper = new IdBasedLockHelper<>(); idBasedLockHelper.executeLocked("Some Name", () -> { // Your code to execute synchronized per name }); 用于存储每个锁id的同步对象。
ConcurrentHashMap 已经提供 ConcurrentHashMapcompute(如果值相等)作为原子操作。存储值中的 remove 计算同步对象的保留次数,这允许仅在未使用时将其从映射中删除(保留数等于 0)。

答案 17 :(得分:0)

这个线程很旧,但可能的解决方案是框架https://github.com/brandaof/named-lock

NamedLockFactory lockFactory = new NamedLockFactory();

...

Lock lock = lockFactory.getLock("lock_name");
lock.lock();

try{
  //manipulate protected state
}
finally{
    lock.unlock();
}

答案 18 :(得分:0)

我已经基于McDowell的IdMutexProvider创建了一个tokenProvider。 经理使用WeakHashMap来处理清理未使用的锁。

TokenManager:

/**
 * Token provider used to get a {@link Mutex} object which is used to get exclusive access to a given TOKEN.
 * Because WeakHashMap is internally used, Mutex administration is automatically cleaned up when
 * the Mutex is no longer is use by any thread.
 *
 * <pre>
 * Usage:
 * private final TokenMutexProvider&lt;String&gt; myTokenProvider = new TokenMutexProvider&lt;String&gt;();
 *
 * Mutex mutex = myTokenProvider.getMutex("123456");
 * synchronized (mutex) {
 *  // your code here
 * }
 * </pre>
 *
 * Class inspired by McDowell.
 * url: http://illegalargumentexception.blogspot.nl/2008/04/java-synchronizing-on-transient-id.html
 *
 * @param <TOKEN> type of token. It is important that the equals method of that Object return true
 * for objects of different instances but with the same 'identity'. (see {@link WeakHashMap}).<br>
 * E.g.
 * <pre>
 *  String key1 = "1";
 *  String key1b = new String("1");
 *  key1.equals(key1b) == true;
 *
 *  or
 *  Integer key1 = 1;
 *  Integer key1b = new Integer(1);
 *  key1.equals(key1b) == true;
 * </pre>
 */
public class TokenMutexProvider<TOKEN> {

    private final Map<Mutex, WeakReference<Mutex>> mutexMap = new WeakHashMap<Mutex, WeakReference<Mutex>>();

    /**
     * Get a {@link Mutex} for the given (non-null) token.
     */
    public Mutex getMutex(TOKEN token) {
        if (token==null) {
            throw new NullPointerException();
        }

        Mutex key = new MutexImpl(token);
        synchronized (mutexMap) {
            WeakReference<Mutex> ref = mutexMap.get(key);
            if (ref==null) {
                mutexMap.put(key, new WeakReference<Mutex>(key));
                return key;
            }
            Mutex mutex = ref.get();
            if (mutex==null) {
                mutexMap.put(key, new WeakReference<Mutex>(key));
                return key;
            }
            return mutex;
        }
    }

    public int size() {
        synchronized (mutexMap) {
            return mutexMap.size();
        }
    }

    /**
     * Mutex for acquiring exclusive access to a token.
     */
    public static interface Mutex {}

    private class MutexImpl implements Mutex {
        private final TOKEN token;

        protected MutexImpl(TOKEN token) {
            this.token = token;
        }

        @Override
        public boolean equals(Object other) {
            if (other==null) {
                return false;
            }
            if (getClass()==other.getClass()) {
                TOKEN otherToken = ((MutexImpl)other).token;
                return token.equals(otherToken);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return token.hashCode();
        }
    }
}

用法:

private final TokenMutexManager<String> myTokenManager = new TokenMutexManager<String>();

Mutex mutex = myTokenManager.getMutex("UUID_123456");
synchronized(mutex) {
    // your code here
}

或者更确切地说使用整数?

private final TokenMutexManager<Integer> myTokenManager = new TokenMutexManager<Integer>();

Mutex mutex = myTokenManager.getMutex(123456);
synchronized(mutex) {
    // your code here
}

答案 19 :(得分:0)

(4年后......) 我的回答类似于user2878608,但我认为该逻​​辑中存在一些缺失的边缘情况。我也认为Semaphore是用于同时锁定多个资源(虽然我认为使用它来计算像这样的储物柜也很好),所以我使用了通用的POJO锁定对象。我对它进行了一次测试,证明每个边缘情况都存在于IMO中,并将在我的工作项目中使用它。希望它可以帮助某人。 :)

class Lock
{
    int c;  // count threads that require this lock so you don't release and acquire needlessly
}

ConcurrentHashMap<SomeKey, Lock> map = new ConcurrentHashMap<SomeKey, Lock>();

LockManager.acquireLock(String name) {
    Lock lock = new Lock();  // creating a new one pre-emptively or checking for null first depends on which scenario is more common in your use case
    lock.c = 0;

    while( true )
    {
        Lock prevLock = map.putIfAbsent(name, lock);
        if( prevLock != null )
            lock = prevLock;

        synchronized (lock)
        {
            Lock newLock = map.get(name);
            if( newLock == null )
                continue;  // handles the edge case where the lock got removed while someone was still waiting on it
            if( lock != newLock )
            {
                lock = newLock;  // re-use the latest lock
                continue;  // handles the edge case where a new lock was acquired and the critical section was entered immediately after releasing the lock but before the current locker entered the sync block
            }

            // if we already have a lock
            if( lock.c > 0 )
            {
                // increase the count of threads that need an offline director lock
                ++lock.c;
                return true;  // success
            }
            else
            {
                // safely acquire lock for user
                try
                {
                    perNameLockCollection.add(name);  // could be a ConcurrentHashMap or other synchronized set, or even an external global cluster lock
                    // success
                    lock.c = 1;
                    return true;
                }
                catch( Exception e )
                {
                    // failed to acquire
                    lock.c = 0;  // this must be set in case any concurrent threads are waiting
                    map.remove(name);  // NOTE: this must be the last critical thing that happens in the sync block!
                }
            }
        }
    }
}

LockManager.releaseLock(String name) {
    // unlock
    // if this was the last hold on the lock, remove it from the cache

    Lock lock = null;  // creating a new one pre-emptively or checking for null first depends on which scenario is more common in your use case

    while( true )
    {
        lock = map.get(name);
        if( lock == null )
        {
            // SHOULD never happen
            log.Error("found missing lock! perhaps a releaseLock call without corresponding acquireLock call?! name:"+name);
            lock = new Lock();
            lock.c = 1;
            Lock prevLock = map.putIfAbsent(name, lock);
            if( prevLock != null )
                lock = prevLock;
        }

        synchronized (lock)
        {
            Lock newLock = map.get(name);
            if( newLock == null )
                continue;  // handles the edge case where the lock got removed while someone was still waiting on it
            if( lock != newLock )
            {
                lock = newLock;  // re-use the latest lock
                continue;  // handles the edge case where a new lock was acquired and the critical section was entered immediately after releasing the lock but before the current locker entered the sync block
            }

            // if we are not the last locker
            if( lock.c > 1 )
            {
                // decrease the count of threads that need an offline director lock
                --lock.c;
                return true;  // success
            }
            else
            {
                // safely release lock for user
                try
                {
                    perNameLockCollection.remove(name);  // could be a ConcurrentHashMap or other synchronized set, or even an external global cluster lock
                    // success
                    lock.c = 0;  // this must be set in case any concurrent threads are waiting
                    map.remove(name);  // NOTE: this must be the last critical thing that happens in the sync block!
                    return true;
                }
                catch( Exception e )
                {
                    // failed to release
                    log.Error("unable to release lock! name:"+name);
                    lock.c = 1;
                    return false;
                }
            }
        }
    }

}

答案 20 :(得分:0)

回应使用新MapMaker()。makeComputingMap()...

的建议 出于安全原因,不推荐使用MapMaker()。makeComputingMap()。后继者是CacheBuilder。使用弱密钥/值应用于CacheBuilder,我们太接近于解决方案了。

问题是CacheBuilder.weakKeys()中的注释:

when this method is used, the resulting cache will use identity (==) comparison to determine equality of keys. 

这使得无法按字符串值选择现有锁定。尔格。

答案 21 :(得分:-1)

您对每种情况的锁定对象的共享静态存储库的想法是正确的 您不需要同步缓存本身......它可以像哈希映射一样简单。

线程可以同时从地图中获取锁定对象。实际的同步逻辑应该分别封装在每个这样的对象中(参见java.util.concurrent包 - http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/package-summary.html

答案 22 :(得分:-2)

TreeMap因为在HashMap内部数组的大小只能增加

public class Locker<T> {
    private final Object lock = new Object();
    private final Map<T, Value> map = new TreeMap<T, Value>();

    public Value<T> lock(T id) {
        Value r;
        synchronized (lock) {
            if (!map.containsKey(id)) {
                Value value = new Value();
                value.id = id;
                value.count = 0;
                value.lock = new ReentrantLock();
                map.put(id, value);
            }
            r = map.get(id);
            r.count++;
        }
        r.lock.lock();
        return r;
    }

    public void unlock(Value<T> r) {
        r.lock.unlock();
        synchronized (lock) {
            r.count--;
            if (r.count == 0)
                map.remove(r.id);
        }
    }

    public static class Value<T> {

        private Lock lock;
        private long count;
        private T id;
    }
}