web-dev-qa-db-fra.com

Verrous basés sur des noms Java simples?

MySQL a une fonction pratique:

SELECT GET_LOCK("SomeName")

Cela peut être utilisé pour créer des verrous nommés simples, mais très spécifiques, pour une application. Cependant, cela nécessite une connexion à la base de données.

J'ai de nombreuses situations comme:

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

Synchroniser cette méthode n'a pas de sens, car, par exemple, si cette méthode est appelée entre-temps pour l'utilisateur B, l'utilisateur B n'a pas besoin d'attendre que l'utilisateur A se termine avant de démarrer, il ne s'agit que d'opérations pour l'utilisateur Les combinaisons A et feature X doivent 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')")
}

Comme le verrouillage Java est basé sur des objets, il me semble nécessaire de créer un nouvel objet pour représenter la situation de ce verrou, puis de le placer dans un cache statique quelque part afin que tous les threads puissent le voir. Les demandes ultérieures de verrouillage pour cette situation localiseraient alors l'objet verrouillé dans le cache et acquerraient son verrou. J'ai essayé de créer quelque chose comme ceci, mais le cache de verrouillage lui-même a besoin d'être synchronisé. En outre, il est difficile de détecter le moment où un objet verrou n'est plus utilisé pour pouvoir le supprimer du cache.

J'ai examiné les packages concurrents Java, mais rien ne se démarque comme étant capable de gérer quelque chose comme ceci. Existe-t-il un moyen simple de mettre cela en œuvre, ou est-ce que je regarde cela du mauvais point de vue?

Modifier:

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

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

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 pu trouver de solution existante. C'est pourquoi j'ai créé cette petite bibliothèque sur Google Code.

14
moj

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

public void executeInNamedLock(String 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 objet unique global et peut donc être utilisée en tant que mutex à l'échelle d'une instance de machine virtuelle. Toutes les chaînes internées sont stockées dans un pool global. C'est donc votre cache statique dont vous avez parlé dans votre question initiale. Ne vous inquiétez pas pour Memleaks; ces chaînes seront générées si aucun autre fil n'y fait référence. Notez toutefois que jusqu'à Java6 inclus, ce pool est conservé dans l'espace PermGen au lieu du tas, vous devrez donc peut-être l'augmenter.

Il existe toutefois un problème si un autre code de votre machine virtuelle se verrouille sur la même chaîne pour des raisons complètement différentes, mais a) cela est très peu probable et b) vous pouvez le contourner en introduisant des espaces de noms, par exemple. executeInNamedLock(this.getClass().getName() + "_" + myLockName);

33
dmoebius

Pouvez-vous avoir un Map<String, Java.util.concurrent.Lock>? Chaque fois que vous avez besoin d'un verrou, vous appelez essentiellement map.get(lockName).lock().

Voici un exemple utilisant Google Guava :

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

Ensuite, lockMap.get("anyOldString") créera un nouveau verrou si nécessaire et vous sera restitué. Vous pouvez alors appeler lock() sur ce verrou. makeComputingMap renvoie une mappe sécurisée pour les threads. Vous pouvez donc la partager avec tous vos threads.

21
sjr
// 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();
20
irreputable

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

Sur le plan conceptuel, la segmentation de verrou est la technique consistant à diviser un verrou en plusieurs bandes, en augmentant la granularité d'un verrou unique et en permettant à 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 verrou unique. 

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

Pour verrouiller quelque chose comme un nom d'utilisateur, les Locks en mémoire dans une carte peuvent être un peu étanches. Au lieu de cela, vous pouvez utiliser WeakReference s avec WeakHashMap pour créer des objets mutex qui peuvent être ramassés à la poubelle lorsque rien ne leur fait référence. Cela vous évite d'avoir à compter manuellement les références 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 risquez d’être confronté à des problèmes de conflit lors de l’acquisition du mutex.

6
McDowell

Une solution générique utilisant 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();

        }


    }

}
5
Pablo Moretti

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

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

Exemple d'utilisation de la classe LockMap:

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

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

Si quelqu'un connaît un meilleur moyen de tester automatiquement la classe LockMap, veuillez écrire un commentaire.

4

Je voudrais noter que ConcurrentHashMap a une fonction de verrouillage intégrée qui suffit pour un verrouillage exclusif à plusieurs couches. Aucun objet Lock supplémentaire n'est requis.

Voici un exemple d'une telle carte de verrouillage utilisée pour appliquer au plus un traitement JMS actif pour un client unique.

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

Deux ans plus tard, mais je cherchais une solution simple de casiers nommés, je l’ai trouvée utile, mais j’avais besoin d’une réponse plus simple, c’est donc en dessous de ce que j’ai trouvé. 

Verrouillage simple sous un nom et relâchez-le sous ce 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<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();
        }
    }
}
2
Jaques

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

Je ne l'ai pas testé cependant.

1
Java Newbie

Après une certaine déception quant à l’absence de prise en charge du niveau de langue ou de la simple classe Guava/Commons pour les verrous nommés,

C'est ce que je me suis installé à:

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

Voici ce que j’ai réalisé: petit code standard sans dépendance à la bibliothèque, acquisition atomique de l’objet verrou, ne pas polluer les objets chaîne internés globaux, pas de chaos de notification/attente de bas niveau ni de désordre try-catch-finally.

1
lyomi

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

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

Oui, cela augmentera avec le temps - mais l'utilisation de ReentrantLock ouvre de plus grandes possibilités pour supprimer le verrou de la carte. Cependant, supprimer des éléments de la carte ne semble pas très utile, car supprimer des valeurs de la carte ne réduira pas sa taille. Une certaine logique de dimensionnement manuel des cartes devrait être mise en œuvre.

1
BrightLight

Beaucoup d'implémentations mais non similaire à la mienne.

Appelé mon implémentation de verrouillage dynamique comme étant ProcessDynamicKeyLock car il s'agit d'un verrou de processus unique, pour n'importe quel objet en tant que clé (est égal à + code de hachage pour l'unicité).

TODO: Ajoutez un moyen de fournir le verrou réel, par exemple, ReentrantReadWriteLock au lieu de ReentrantLock.

La mise en oeuvre:

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

Test simple:

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

Considération de mémoire

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

Voici une implémentation qui ne conserve pas en interne les clés publiées.

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

Réentrance, et autres cloches et sifflets

Si on veut une sémantique de verrouillage ré-entrante, ils peuvent étendre ce qui précède en encapsulant l'objet mutex dans une classe qui garde la trace du thread bloquant et du nombre verrouillé.

par exemple.:

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

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

    int lockedCount = 1;
}

Si on veut que lock() renvoie un objet pour rendre les libérations plus faciles et plus sûres, c'est aussi une possibilité.

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

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

Et voici comment on pourrait l'utiliser:

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

    // do something critical here
}
1
antak

Ce fil est ancien, mais une solution possible est le cadre 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();
}
0
brandao

J'ai créé un tokenProvider basé sur IdMutexProvider de McDowell . Le gestionnaire utilise une WeakHashMap qui s'occupe de nettoyer 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.
 *
 * <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();
        }
    }
}

Usage:

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

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

ou plutôt utiliser des entiers?

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

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

Voici une solution simple et optimisée qui aborde également la suppression des verrous utilisés, mais avec un temps système de synchronisation de la carte:

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

}

0
Nikhil

En réponse à la suggestion d'utiliser le 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 sommes 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 la sélection d'un verrou existant par valeur de chaîne. Erg.

0
Ryan

(4 ans plus tard ...) Ma réponse est similaire à celle de user2878608 mais je pense qu'il manque des cas Edge dans cette logique. J'ai également pensé que Semaphore était destiné à verrouiller plusieurs ressources à la fois (bien que je suppose que l'utiliser pour compter des casiers comme celui-ci convient également), j'ai donc utilisé un objet de verrouillage POJO générique. J'ai effectué un test sur celui-ci qui a démontré que chacun des cas Edge existait à l'OMI et l'utilisera pour 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<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;
                }
            }
        }
    }

}
0
vazor