Подтвердить что ты не робот

Простые блокировки на основе Java?

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, но ничто не отличается тем, что можно обрабатывать что-то вроде этого. Есть ли простой способ реализовать это, или я смотрю на это с неправильной точки зрения?

Edit:

Чтобы уточнить, я не хочу заранее создавать предопределенный пул блокировок, я бы хотел создать их по требованию. Некоторый псевдокод для того, что я думаю:

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
}
4b9b3361

Ответ 1

возможно, это полезно для вас: jkeylockmanager

Edit:

Мой первоначальный ответ был, вероятно, немного коротким. Я автор и столкнулся с этой проблемой несколько раз и не смог найти существующее решение. Вот почему я создал эту небольшую библиотеку в Google Code.

Ответ 2

У вас есть 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 возвращает карту, которая является потокобезопасной, поэтому вы можете просто поделиться ею со всеми вашими потоками.

Ответ 3

// 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();

Ответ 4

Все те ответы, которые я вижу, слишком сложны. Почему бы просто не использовать:

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

Ключевым моментом является метод intern: он гарантирует, что возвращаемый String является глобальным уникальным объектом, поэтому его можно использовать как мьютекс vm-instance-wide. Все интернированные строки хранятся в глобальном пуле, так что ваш статический кеш, о котором вы говорили в своем исходном вопросе. Не беспокойтесь о memleaks; эти строки будут отображаться, если ни одна другая нить не ссылается на нее. Однако обратите внимание, что вплоть до Java6 этот пул хранится в пространстве PermGen вместо кучи, поэтому вам может потребоваться его увеличить.

Проблема в том, что если какой-либо другой код в вашем vm блокирует одну строку для совершенно разных причин, но a) это очень маловероятно, и b) вы можете обойти его, введя пространства имен, например. executeInNamedLock(this.getClass().getName() + "_" + myLockName);

Ответ 5

Для блокировки чего-то вроде имени пользователя, в памяти Lock на карте может быть немного негерметично. В качестве альтернативы вы можете посмотреть WeakReference с WeakHashMap для создания объектов мьютекса, которые могут быть собраны в мусор, когда к ним ничего не говорится. Это позволяет избежать необходимости ручного подсчета ссылок для освобождения памяти.

Здесь вы можете найти реализацию здесь. Обратите внимание, что если вы часто просматриваете на карте, вы можете столкнуться с конфликтными проблемами, приобретая мьютекс.

Ответ 6

Общее решение с использованием 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

Возможно, немного позже, но вы можете использовать Google Guava Striped

Концептуально, strip striping - это метод деления блокировки на многие полосы, увеличивая гранулярность одного замка и позволяя независимым операциям блокировать разные полосы и продолжаться одновременно, вместо того, чтобы создавать конкуренцию для одного замка.

//init
stripes=Striped.lazyWeakLock(size);
//or
stripes=Striped.lock(size);
//...
Lock lock=stripes.get(object);

Ответ 8

На основе ответа McDowell и его класса IdMutexProvider, у меня есть написал общий класс LockMap, который использует WeakHashMap для хранения объектов блокировки. LockMap.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, напишите комментарий.

Ответ 9

Может быть, что-то вроде этого:

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();
    }
}}

Я не тестировал это.

Ответ 10

Спустя 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();
        }
    }
}

Ответ 11

После некоторого разочарования, что нет поддержки уровня языка или простого класса 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
    }
}

Здесь я достиг: небольшого кода шаблона без зависимости от библиотеки, атомарно приобретающего объект блокировки, не загрязняя глобальные интернированные строковые объекты, низкоуровневого уведомления/ожидания хаоса и беспорядка try-catch-finally.

Ответ 12

Я хотел бы заметить, что 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();
    }
}

Ответ 13

Подобно ответу от 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 открывает больше возможностей для удаления блокировки с карты. Хотя удаление элементов с карты не кажется полезным, учитывая, что удаление значений с карты не уменьшит ее размер. Необходимо будет выполнить некоторую логику калибровки карты вручную.

Ответ 14

Рассмотрение памяти

Часто, синхронизация, необходимая для конкретного ключа, недолговечна. Сохранение выпущенных ключей может привести к чрезмерному сбою памяти, что делает ее нежизнеспособной.

Здесь реализация не внутренне поддерживает выпущенные ключи.

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();
    }
}

Регентство и другие навороты

Если требуется семантика блокировки повторного входа, они могут расширить вышеуказанное, обернув объект mutex в класс, который отслеживает блокирующий поток и заблокировал счет.

например:.

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
}

Ответ 15

В ответ на предложение использовать новый 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. 

Это делает невозможным выбор существующей блокировки по строковому значению. Эрг.

Ответ 16

(4 года спустя...) Мой ответ похож на user2878608, но я думаю, что в этой логике есть некоторые недостающие грани. Я также думал, что Семафор был за блокировку нескольких ресурсов сразу (хотя я полагаю, что использовать его для подсчета шкафчиков, как это тоже хорошо), поэтому вместо этого я использовал общий объект блокировки POJO. Я провела на нем один тест, который показал, что каждый из крайних случаев существует ИМО и будет использовать его в моем проекте на работе. Надеюсь, это поможет кому-то.:)

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;
                }
            }
        }
    }

}

Ответ 17

Я создал tokenProvider на основе IdMutexProvider от McDowell. Менеджер использует 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
}

Ответ 18

Ваша идея об общем статическом хранилище объектов блокировки для каждой ситуации верна.
Вам не нужно, чтобы кеш был синхронизирован... он может быть таким же простым, как хэш-карта.

Темы могут одновременно получать объект блокировки с карты. Фактическая логика синхронизации должна быть инкапсулирована внутри каждого такого объекта отдельно (см. Пакет java.util.concurrent для этого - http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/package-summary.html)

Ответ 19

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;
    }
}