¿Cerraduras simples basadas en el nombre de Java?

MySQL tiene una función útil:

SELECT GET_LOCK("SomeName") 

Esto se puede usar para crear lockings simples, pero muy específicos, basados ​​en el nombre para una aplicación. Sin embargo, requiere una conexión a la base de datos.

Tengo muchas situaciones como:

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

No tiene sentido simplemente sincronizar este método, porque, por ejemplo, si este método se llama para el usuario B mientras tanto, el usuario B no necesita esperar a que el usuario A finalice antes de que comience, solo operaciones para el usuario Una combinación de A y X necesita esperar.

Con el locking de MySql podría hacer algo como:

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

Como el locking de Java se basa en objetos, parece que necesitaría crear un nuevo objeto para representar la situación de este locking y luego colocarlo en un caché estático en algún lugar para que todos los hilos puedan verlo. Las solicitudes posteriores de locking para esa situación localizarían el objeto de locking en la memoria caché y obtendrían su locking. Traté de crear algo como esto, pero luego el caché de locking necesita sincronización. Además, es difícil detectar cuándo ya no se usa un objeto de locking para que pueda eliminarse de la memoria caché.

He analizado los paquetes simultáneos de Java, pero nada se destaca por ser capaz de manejar algo como esto. ¿Hay una manera fácil de implementar esto, o estoy mirando esto desde una perspectiva equivocada?

Editar:

Para aclarar, no estoy buscando crear un conjunto predefinido de lockings antes de tiempo, me gustaría crearlos bajo demanda. Algún pseudo código para lo que estoy pensando es:

 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 } 

tal vez esto sea útil para ti: jkeylockmanager

Editar:

Mi respuesta inicial fue probablemente un poco corta. Soy el autor y me enfrenté a este problema varias veces y no pude encontrar una solución existente. Es por eso que hice esta pequeña biblioteca en Google Code.

Todas esas respuestas que veo son demasiado complicadas. ¿Por qué no usar simplemente:

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

El punto clave es el método intern : asegura que la cadena devuelta es un objeto único global, y por lo tanto se puede usar como un mutex de ancho de instancia de toda la instancia de VM. Todas las cadenas internas se guardan en un grupo global, por lo que esa es su caché estática de la que estaba hablando en su pregunta original. No te preocupes por Memleaks; esas cuerdas serán gc’ed si ningún otro hilo lo hace referencia. Sin embargo, tenga en cuenta que hasta e incluyendo Java6 este grupo se mantiene en el espacio PermGen en lugar del almacenamiento dynamic, por lo que es posible que tenga que boostlo.

Sin embargo, hay un problema si algún otro código en su vm se bloquea en la misma cadena por razones completamente diferentes, pero a) esto es muy improbable, yb) puede executeInNamedLock(this.getClass().getName() + "_" + myLockName); introduciendo espacios de nombres, por ejemplo, executeInNamedLock(this.getClass().getName() + "_" + myLockName);

¿Puedes tener un Map ? Cada vez que necesita un locking, básicamente llama a map.get(lockName).lock() .

Aquí hay un ejemplo con Google Guava :

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

Entonces lockMap.get("anyOldString") hará que se lockMap.get("anyOldString") un nuevo locking si es necesario y se lo devolveremos. Luego puede llamar a lock() en ese locking. makeComputingMap devuelve un mapa que es seguro para la ejecución de subprocesos, por lo que puede compartirlo con todos sus subprocesos.

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

Para bloquear algo como un nombre de usuario, los Lock en memoria en un mapa pueden tener fugas. Como alternativa, podría usar WeakReference s con WeakHashMap para crear objetos mutex que pueden ser recolectados como basura cuando nada se refiere a ellos. Esto evita tener que hacer un conteo de referencia manual para liberar memoria.

Puede encontrar una implementación aquí . Tenga en cuenta que si realiza búsquedas frecuentes en el mapa, puede encontrarse con problemas de contención al adquirir el mutex.

Tal vez un poco más tarde, pero puedes usar Google Guava Striped

Conceptualmente, la división de lockings es la técnica de dividir un locking en muchas bandas, lo que aumenta la granularidad de un solo locking y permite que las operaciones independientes bloqueen diferentes franjas y procedan al mismo tiempo, en lugar de crear una contención para un solo locking.

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

Una solución genérica usando java.util.concurrent

 import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; public class LockByName { ConcurrentHashMap mapStringLock; public LockByName(){ mapStringLock = new ConcurrentHashMap(); } public LockByName(ConcurrentHashMap 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 reentrantLocker = new LockByName(); ReentrantLock reentrantLock1 = reentrantLocker.getLock("pepe"); try { reentrantLock1.lock(); //DO WORK }finally{ reentrantLock1.unlock(); } } } 

Basado en la respuesta de McDowell y su clase IdMutexProvider , escribí la clase genérica LockMap que usa WeakHashMap para almacenar objetos de locking. LockMap.get() se puede usar para recuperar un objeto de locking para una clave, que luego se puede usar con la sentencia synchronized (...) Java para aplicar un locking. Los objetos de locking no utilizados se liberan automáticamente durante la recolección de basura.

 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 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 { 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 

Ejemplo de cómo usar la clase LockMap:

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

Un progtwig de prueba simple para la clase LockMap:

 public static Object lock1; public static Object lock2; public static void main (String[] 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 alguien conoce una forma mejor de probar automáticamente la clase LockMap, por favor escriba un comentario.

Me gustaría notar que ConcurrentHashMap tiene una función de locking incorporada que es suficiente para el locking de múltiples hilos exclusivo. No se necesitan objetos Lock adicionales.

Aquí hay un ejemplo de dicho mapa de locking utilizado para aplicar como máximo un procesamiento de jms activo para un solo cliente.

 private static final ConcurrentMap lockMap = new ConcurrentHashMap(); 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(); } } 

2 años más tarde, pero estaba buscando una solución de casillero con nombre simple y encontré esto, fue útil, pero necesitaba una respuesta más simple, por lo que debajo de lo que se me ocurrió.

Bloqueo simple bajo algún nombre y liberación bajo el mismo nombre.

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

Aquí está el código:

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

Tal vez algo como eso:

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

Aunque no lo probé.

Después de cierta desilusión de que no hay soporte de nivel de idioma o una clase simple de Guava / Commons para lockings con nombre,

Esto es a lo que me establecí:

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

Aquí logré: un pequeño código repetitivo sin dependencia de la biblioteca, adquirir atómicamente el objeto de locking, no contaminar los objetos de cadena internados globales, no hay caos de notificación / espera de bajo nivel y no hay un caos try-catch-finally.

Similar a la respuesta de Lyomi, pero usa ReentrantLock más flexible en lugar de un bloque sincronizado.

 public class NamedLock { private static final ConcurrentMap lockByName = new ConcurrentHashMap(); 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(); } } 

Sí, esto boostá con el tiempo, pero usar ReentrantLock abre mayores posibilidades para eliminar el locking del mapa. Aunque, eliminar elementos del mapa no parece tan útil, ya que la eliminación de valores del mapa no reducirá su tamaño. Habría que implementar cierta lógica de dimensionamiento manual del mapa.

Consideración de memoria

Muchas veces, la sincronización necesaria para una clave en particular es de corta duración. Mantener alrededor de las llaves liberadas puede conducir a una pérdida excesiva de memoria, lo que la hace inviable.

Aquí hay una implementación que no se guarda internamente alrededor de las claves liberadas.

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

Reentrada y otras campanas y silbatos

Si uno quiere la semántica de locking reentrante, puede ampliar lo anterior al envolver el objeto mutex en una clase que realiza un seguimiento de la secuencia de locking y el conteo bloqueado.

p.ej:

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

Si uno quiere que lock() devuelva un objeto para hacer los lanzamientos más fáciles y seguros, esa también es una posibilidad.

Poniéndolo todo junto, he aquí cómo se vería la clase:

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

Y así es como uno podría usarlo:

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

En respuesta a la sugerencia de utilizar MapMaker (). MakeComputingMap () nuevo …

MapMaker (). MakeComputingMap () está en desuso por razones de seguridad. El sucesor es CacheBuilder. Con claves / valores débiles aplicados a CacheBuilder, estamos tan cerca de una solución.

El problema es una nota en CacheBuilder.weakKeys ():

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

Esto hace que sea imposible seleccionar un locking existente por valor de cadena. Ergio.

(4 años después …) Mi respuesta es similar a la de user2878608, pero creo que faltan algunos casos extremos en esa lógica. También pensé que Semaphore era para bloquear múltiples recursos a la vez (aunque supongo que usarlo para contar taquillas así también está bien), así que usé un objeto de locking POJO genérico en su lugar. Ejecuté una prueba que demostró que cada uno de los casos extremos existía en la OMI y lo utilizaré en mi proyecto en el trabajo. Espero que ayude a alguien. 🙂

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

Creé un tokenProvider basado en IdMutexProvider de McDowell. El administrador usa un WeakHashMap que se encarga de limpiar los lockings no utilizados.

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<String> myTokenProvider = new TokenMutexProvider<String>(); * * 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 *

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

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

Uso:

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

o más bien usar enteros?

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

Este hilo es antiguo, pero una posible solución es el marco 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(); } 

Aquí hay una solución simple y optimizada que aborda la eliminación de lockings usados ​​también, pero con una sobrecarga de sincronización del Mapa:

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

}

Many implementations but non similar to mine.

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 .

Implementación:

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