Verrous de nom Java simples?

MySQL a une fonction pratique:

SELECT GET_LOCK("SomeName") 

Cela peut être utilisé pour créer des verrous simples, mais très spécifiques, basés sur des noms pour une application. Cependant, il nécessite une connexion à la firebase database.

J’ai beaucoup de situations comme:

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

Cela n’a pas de sens de simplement synchroniser cette méthode, parce que, par exemple, si cette méthode est appelée pour l’utilisateur B entre-temps, l’utilisateur B n’a pas besoin d’attendre que l’utilisateur A se termine avant de démarrer. Une combinaison a et caractéristique X doit attendre.

Avec le verrou MySql, je pourrais faire quelque chose comme:

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

Puisque le locking Java est basé sur des objects, il semble que je devrais créer un nouvel object pour représenter la situation de ce verrou, puis le placer dans un cache statique quelque part pour que tous les threads puissent le voir. Les demandes ultérieures de locking pour cette situation localiseraient alors l’object verrou dans le cache et acquerraient son verrou. J’ai essayé de créer quelque chose comme ça, mais le cache de locking lui-même doit être synchronisé. En outre, il est difficile de détecter quand un object de locking n’est plus utilisé pour pouvoir le supprimer du cache.

J’ai regardé les paquets Java simultanés, mais rien ne semble pouvoir gérer quelque chose comme ça. Existe-t-il un moyen facile de mettre cela en œuvre, ou suis-je en train de regarder cela du mauvais sharepoint vue?

Modifier:

Pour clarifier, je ne cherche pas à créer un pool de verrous prédéfini à l’avance, je voudrais les créer à la demande. Un pseudo-code pour ce que je pense est:

 LockManager.acquireLock(Ssortingng 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(Ssortingng name) { // unlock // if this was the last hold on the lock, remove it from the cache } 

c’est peut-être utile pour vous: jkeylockmanager

Modifier:

Ma réponse initiale était probablement un peu courte. Je suis l’auteur et j’ai été confronté à ce problème plusieurs fois et je n’ai pas trouvé de solution existante. C’est pourquoi j’ai créé cette petite bibliothèque sur Google Code.

Toutes les réponses que je vois sont trop compliquées. Pourquoi ne pas simplement utiliser:

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

Le point clé est la méthode intern : elle garantit que la chaîne renvoyée est un object unique global et peut donc être utilisée comme un mutex à l’échelle de l’instance vm. Toutes les chaînes internes sont conservées dans un pool global. Il s’agit donc de votre cache statique dont vous parliez dans votre question d’origine. Ne vous inquiétez pas de memleaks; ces chaînes seront gcrées si aucun autre thread n’y fait référence. Notez toutefois que jusqu’à et y compris Java6, ce pool est conservé dans l’espace PermGen au lieu du tas, vous devrez donc peut-être l’augmenter.

Il y a un problème cependant si un autre code dans votre vm verrouille la même chaîne pour des raisons complètement différentes, mais a) c’est très improbable, et b) vous pouvez le contourner en introduisant des espaces de noms, par exemple executeInNamedLock(this.getClass().getName() + "_" + myLockName);

Pouvez-vous avoir une Map ? Chaque fois que vous avez besoin d’un verrou, vous appelez essentiellement map.get(lockName).lock() .

Voici un exemple d’utilisation de Google Guava :

 Map lockMap = new MapMaker().makeComputingMap(new Function() { @Override public Lock apply(Ssortingng input) { return new ReentrantLock(); } }); 

Ensuite, lockMap.get("anyOldSsortingng") créera un nouveau verrou si nécessaire et vous sera renvoyé. Vous pouvez alors appeler lock() sur ce verrou. makeComputingMap renvoie une map qui est makeComputingMap threads, vous pouvez donc simplement la partager avec tous vos threads.

 // pool of names that are being locked HashSet pool = new HashSet(); 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(); 

Pour verrouiller quelque chose comme un nom d’utilisateur, le Lock mémoire dans une carte peut être un peu étouffant. En guise d’ alternative, vous pouvez envisager d’utiliser WeakReference s avec WeakHashMap pour créer des objects de mutex pouvant être collectés lorsque rien ne s’y réfère. Cela vous évite de faire un comptage manuel de référence pour libérer de la mémoire.

Vous pouvez trouver une implémentation ici . Notez que si vous effectuez des recherches fréquentes sur la carte, vous pouvez rencontrer des problèmes de conflit en acquérant le mutex.

Peut-être un peu plus tard mais vous pouvez utiliser Google Guava Ssortingped

Conceptuellement, la répartition des verrous consiste à diviser un verrou en plusieurs bandes, ce qui augmente la granularité d’un verrou unique et permet à des opérations indépendantes de verrouiller différentes bandes et de procéder simultanément, au lieu de créer un conflit pour un seul verrou.

 //init ssortingpes=Ssortingped.lazyWeakLock(size); //or ssortingpes=Ssortingped.lock(size); //... Lock lock=ssortingpes.get(object); 

Une solution générique utilisant java.util.concurrent

 import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; public class LockByName { ConcurrentHashMap mapSsortingngLock; public LockByName(){ mapSsortingngLock = new ConcurrentHashMap(); } public LockByName(ConcurrentHashMap mapSsortingngLock){ this.mapSsortingngLock = mapSsortingngLock; } @SuppressWarnings("unchecked") public L getLock(Ssortingng key) { L initValue = (L) createIntanceLock(); L lock = mapSsortingngLock.putIfAbsent(key, initValue); if (lock == null) { lock = initValue; } return lock; } protected Object createIntanceLock() { return new ReentrantLock(); } public static void main(Ssortingng[] args) { LockByName reentrantLocker = new LockByName(); ReentrantLock reentrantLock1 = reentrantLocker.getLock("pepe"); try { reentrantLock1.lock(); //DO WORK }finally{ reentrantLock1.unlock(); } } } 

Sur la base de la réponse de McDowell et de sa classe IdMutexProvider , j’ai écrit la classe générique LockMap qui utilise WeakHashMap pour stocker des objects de locking. LockMap.get() peut être utilisé pour récupérer un object de locking pour une clé, qui peut ensuite être utilisé avec l’instruction Java synchronized (...) pour appliquer un verrou. Les objects de locking non utilisés sont automatiquement libérés lors de la récupération de la mémoire.

 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 { private WeakHashMap,WeakReference>> map; public LockMap() { map = new WeakHashMap,WeakReference>>(); } // Returns a lock object for the specified key. public synchronized Object get (KEY key) { if (key == null) { throw new NullPointerException(); } KeyWrapper newKeyWrapper = new KeyWrapper(key); WeakReference> ref = map.get(newKeyWrapper); KeyWrapper oldKeyWrapper = (ref == null) ? null : ref.get(); if (oldKeyWrapper != null) { return oldKeyWrapper; } map.put(newKeyWrapper, new WeakReference>(newKeyWrapper)); return newKeyWrapper; } // Returns the number of used ensortinges 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 { 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 

Exemple d’utilisation de la classe LockMap:

 private static LockMap lockMap = new LockMap(); synchronized (lockMap.get(name)) { ... } 

Un programme de test simple pour la classe LockMap:

 public static Object lock1; public static Object lock2; public static void main (Ssortingng[] args) throws Exception { System.out.println("TestLockMap Started"); LockMap map = new LockMap(); 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, eg 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"); } 

Si quelqu'un sait mieux tester automatiquement la classe LockMap, écrivez un commentaire.

Je voudrais remarquer que ConcurrentHashMap a une fonction de locking intégrée suffisante pour un locking multithread exclusif. Aucun object Lock supplémentaire requirejs.

Voici un exemple de mappage de locking utilisé pour appliquer au plus un traitement jms actif pour un seul client.

 private static final ConcurrentMap lockMap = new ConcurrentHashMap(); private static final Object DUMMY = new Object(); private boolean tryLock(Ssortingng 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(Ssortingng key) { lockMap.remove(key); } @TransactionAtsortingbute(TransactionAtsortingbuteType.REQUIRED) public void onMessage(Message message) { Ssortingng key = getClientKey(message); if (tryLock(key)) { try { // handle jms } finally { unlock(key); } } else { // key is locked, forcing redelivery messageDrivenContext.setRollbackOnly(); } } 

2 ans plus tard, mais je cherchais une solution de casier nommée simple et je suis tombée sur cela, c’était utile mais j’avais besoin d’une réponse plus simple, donc en dessous de ce que j’avais trouvé.

Verrouillage simple sous un nom et relâchez-le sous le même nom.

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

Voici le code:

 public class NamedLocker { private ConcurrentMap synchSemaphores = new ConcurrentHashMap(); private int permits = 1; public NamedLocker(){ this(1); } public NamedLocker(int permits){ this.permits = permits; } public void acquireLock(Ssortingng... key){ Semaphore tempS = new Semaphore(permits, true); Semaphore s = synchSemaphores.putIfAbsent(Arrays.toSsortingng(key), tempS); if(s == null){ s = tempS; } s.acquireUninterruptibly(); } public void releaseLock(Ssortingng... key){ Semaphore s = synchSemaphores.get(Arrays.toSsortingng(key)); if(s != null){ s.release(); } } } 

Peut-être quelque chose comme ça:

 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 _cache = new HashMap(); public void lock(Ssortingng 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(Ssortingng 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(); } }} 

Je ne l’ai pas testé cependant.

Après une certaine déception qu’il n’existe pas de support de niveau de langue ou de classe Guava / Commons simple pour les verrous nommés,

C’est ce que j’ai décidé de:

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

Ici, j’ai atteint: peu de code passe-partout sans dépendance de bibliothèque, acquérant de façon atomique l’object de locking, ne polluant pas les objects de chaîne internes internes, pas de chaos de notification / attente de bas niveau et pas de désordre.

Semblable à la réponse de Lyomi, mais utilise le ReentrantLock plus flexible au lieu d’un bloc synchronisé.

 public class NamedLock { private static final ConcurrentMap lockByName = new ConcurrentHashMap(); public static void lock(Ssortingng key) { Lock lock = new ReentrantLock(); Lock existingLock = lockByName.putIfAbsent(key, lock); if(existingLock != null) { lock = existingLock; } lock.lock(); } public static void unlock(Ssortingng key) { Lock namedLock = lockByName.get(key); namedLock.unlock(); } } 

Oui, cela augmentera avec le temps – mais l’utilisation de ReentrantLock ouvre de plus grandes possibilités pour supprimer le verrou de la carte. Bien que le fait de supprimer des éléments de la carte ne semble pas très utile, la suppression des valeurs de la carte ne réduira pas sa taille. Une logique de dimensionnement manuel des cartes devrait être implémentée.

Prise en compte de la mémoire

Souvent, la synchronisation nécessaire pour une clé particulière est de courte durée. Garder les clés libérées peut entraîner un gaspillage de mémoire excessif, le rendant non viable.

Voici une implémentation qui ne garde pas en interne les clés libérées.

 import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; public class KeyedMutexes { private final ConcurrentMap 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(); } } 

Réentrance et autres cloches et sifflets

Si l’on veut obtenir une sémantique de locking ré-entrant, ils peuvent étendre ce qui précède en encapsulant l’object mutex dans une classe qui assure le suivi du thread de locking et du nombre de verrous.

par exemple:

 private static class Lock { final CountDownLatch mutex = new CountDownLatch(1); final long threadId = Thread.currentThread().getId(); int lockedCount = 1; } 

Si l’on veut que lock() renvoie un object pour rendre les versions plus faciles et plus sûres, c’est aussi une possibilité.

En résumé, voici à quoi la classe pourrait ressembler:

 public class KeyedReentrantLocks { private final ConcurrentMap 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(); } } 

Et voici comment on pourrait l’utiliser:

 try (KeyedLock lock = locks.acquire("SomeName")) { // do something critical here } 

En réponse à la suggestion d’utiliser un nouveau MapMaker (). MakeComputingMap () …

MapMaker (). MakeComputingMap () est obsolète pour des raisons de sécurité. Le successeur est CacheBuilder. Avec des clés / valeurs faibles appliquées à CacheBuilder, nous sums tellement près d’une solution.

Le problème est une note dans CacheBuilder.weakKeys ():

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

Cela rend impossible de sélectionner un verrou existant par valeur de chaîne. Erg.

(4 ans plus tard …) Ma réponse est similaire à celle de user2878608 mais je pense qu’il y a des cas manquants dans cette logique. Je pensais aussi que Semaphore était pour verrouiller plusieurs ressources à la fois (bien que je suppose que l’utiliser pour compter des casiers comme celui-ci est bien aussi), donc j’ai utilisé un object de locking POJO générique à la place. J’ai effectué un test qui a démontré que chacun des cas de pointe existait à l’OMI et l’utilisera dans mon projet au travail. J’espère que ça aide quelqu’un. 🙂

 class Lock { int c; // count threads that require this lock so you don't release and acquire needlessly } ConcurrentHashMap map = new ConcurrentHashMap(); LockManager.acquireLock(Ssortingng 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(Ssortingng 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; } } } } } 

J’ai créé un tokenProvider basé sur le IdMutexProvider de McDowell. Le gestionnaire utilise un WeakHashMap qui nettoie les verrous inutilisés.

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. * * 
 * Usage: * private final TokenMutexProvider<Ssortingng> myTokenProvider = new TokenMutexProvider<Ssortingng>(); * * Mutex mutex = myTokenProvider.getMutex("123456"); * synchronized (mutex) { * // your code here * } * 

* * Class inspired by McDowell. * url: http://illegalargumentexception.blogspot.nl/2008/04/java-synchronizing-on-transient-id.html * * @param 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}).
* Eg *

 * Ssortingng key1 = "1"; * Ssortingng key1b = new Ssortingng("1"); * key1.equals(key1b) == true; * * or * Integer key1 = 1; * Integer key1b = new Integer(1); * key1.equals(key1b) == true; * 

*/ public class TokenMutexProvider { private final Map> mutexMap = new WeakHashMap>(); /** * 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 ref = mutexMap.get(key); if (ref==null) { mutexMap.put(key, new WeakReference(key)); return key; } Mutex mutex = ref.get(); if (mutex==null) { mutexMap.put(key, new WeakReference(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(); } } }

Usage:

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

ou plutôt utiliser des entiers?

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

Ce thread est ancien, mais une solution possible est le framework 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(); } 

Voici une solution simple et optimisée qui traite également de la suppression des verrous utilisés, mais avec une surcharge de synchronisation de la carte:

 public class NamedLock { private Map 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(); } 

}

Beaucoup d’implémentations mais non similaires aux miennes.

Called my Dynamic lock implementation as ProcessDynamicKeyLock because it’s a single process lock, for any object as key (equals+hashcode for uniqueness).

TODO: Add a way to provide the actual lock, for example, ReentrantReadWriteLock instead of ReentrantLock .

La mise en oeuvre:

 public class ProcessDynamicKeyLock implements Lock { private final static ConcurrentHashMap 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(); } } 

Simple test:

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

Your idea about a shared static repository of lock objects for each situation is correct.
You don’t need the cache itself to be synchronized … it can be as simple as a hash map.

Threads can simultaneously get a lock object from the map. The actual synchronization logic should be encapsulated within each such object separately (see the java.util.concurrent package for that – http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/package-summary.html )

TreeMap because in HashMap size of inner array can only increase

 public class Locker { private final Object lock = new Object(); private final Map map = new TreeMap(); public Value 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 r) { r.lock.unlock(); synchronized (lock) { r.count--; if (r.count == 0) map.remove(r.id); } } public static class Value { private Lock lock; private long count; private T id; } }