Des serrures simples basées sur des noms Java?

MySQL a une fonction pratique:

SELECT GET_LOCK("SomeName")

ceci peut être utilisé pour créer des serrures simples, mais très spécifiques, basées sur le nom pour une application. Toutefois, il nécessite une connexion à une base de données.

j'ai beaucoup de situations comme:

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

Ça 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 dans l'intervalle, l'utilisateur B n'a pas besoin d'attendre pour Un utilisateur de terminer avant de commencer, seules les opérations pour l'utilisateur A et la combinaison de la fonctionnalité X doivent attendre.

avec la serrure 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 Java locking est basé sur des objets, il semble que j'aurais besoin de créer un nouvel objet pour représenter la situation de ce lock et puis le mettre dans une cache statique quelque part pour que tous les threads puissent le voir. Les demandes subséquentes de verrouillage pour cette situation localiseraient alors l'objet de verrouillage dans la cache et d'acquérir sa serrure. J'ai essayé de créer quelque chose comme ça, mais le cache du verrou lui-même doit être synchronisé. En outre, il est difficile de détecter lorsqu'un objet lock n'est plus utilisé de sorte qu'il puisse être retiré du cache.

j'ai regardé les paquets Java concurrents, mais rien ne se démarque comme étant capable de gérer quelque chose comme ça. Y a-t-il un moyen facile de mettre en œuvre cela, ou est-ce que je regarde cela du mauvais point de vue?

Edit:

pour clarifier, Je ne cherche pas à créer un pool prédéfini d'écluses à l'avance, je voudrais les créer sur demande. Un pseudo-code pour ce que je pense est:

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
demandé sur Ollie 2011-04-12 22:26:31

22 réponses

peut-être que c'est utile pour vous: jkeylockmanager

Edit:

ma réponse initiale a probablement été un peu courte. Je suis l'auteur et j'ai été confronté à ce problème à plusieurs reprises et je n'ai pas pu trouver de solution existante. C'est pourquoi j'ai créé cette petite bibliothèque sur Google Code.

14
répondu moj 2016-02-19 07:44:44

toutes ces 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 retournée est un objet global unique, et peut donc être utilisée comme un mutex vm-instance-wide. Toutes les chaînes internes sont stockées dans un pool global, donc c'est votre cache statique dont vous parliez dans votre question originale. Ne vous inquiétez pas pour memleaks; ces chaînes seront gc'ed si aucun autre thread n'y fait référence. Notez cependant que Jusqu'à Java6 inclus cette piscine est conservée dans L'espace PermGen au lieu du tas, donc vous pourriez avoir à l'augmenter.

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

28
répondu dmoebius 2012-12-19 16:40:48

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

Voici un exemple d'utilisation de Google Goyave :

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

puis lockMap.get("anyOldString") provoquera la création d'une nouvelle serrure si nécessaire et vous sera retournée. Vous pouvez alors appeler lock() sur cette serrure. makeComputingMap retourne une Carte qui est thread-safe, vous pouvez simplement partager avec tous vos threads.

21
répondu sjr 2011-04-12 18:41:56
// 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
répondu irreputable 2011-04-12 21:00:50

pour verrouiller sur quelque chose comme un nom d'utilisateur, dans la mémoire Lock s dans une carte pourrait être un peu leaky. Comme alternative, vous pouvez regarder en utilisant weakref s avec Weakhmap pour créer des objets mutex qui peuvent être ramassés lorsque rien ne se réfère à eux. Cela vous évite d'avoir à faire n'importe quel comptage manuel de référence pour libérer la mémoire.

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

6
répondu McDowell 2011-04-12 18:57:31

peut-être un peu plus tard, mais vous pouvez utiliser Google Guava rayé

sur le plan conceptuel, le rayage de serrure est la technique qui consiste à diviser une serrure en plusieurs rayures, ce qui augmente la granularité d'une serrure unique et permet à des opérations indépendantes de verrouiller différentes rayures et de procéder simultanément, au lieu de créer une querelle pour une serrure unique.

//init
stripes=Striped.lazyWeakLock(size);
//or
stripes=Striped.lock(size);
//...
Lock lock=stripes.get(object);
6
répondu Doua Beri 2017-04-11 18:55:07

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
répondu Pablo Moretti 2013-12-22 20:45:17

basé sur la answer of McDowell et sa classe IdMutexProvider , j'ai écrit la classe générique LockMap qui utilise Weakhmap pour stocker des objets de verrouillage. LockMap.get() peut être utilisé pour récupérer un objet de verrouillage pour une clé, qui peut ensuite être utilisé avec Java synchronized (...) déclaration d'appliquer un verrou. Les objets de serrure inutilisés sont automatiquement libérés lors de la collecte des ordures.

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 /q/simple-java-name-based-locks-47603/"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 une meilleure façon de tester automatiquement la classe LockMap, veuillez écrire un commentaire.

4
répondu Christian d'Heureuse 2017-05-23 12:18:06

je voudrais noter que ConcurrentHashMap a intégré la facilité de verrouillage qui est suffisante pour la serrure multithread exclusive simple. Aucun objet supplémentaire Lock n'est nécessaire.

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

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
répondu Vadzim 2014-06-20 14:22:57

2 ans plus tard, mais je cherchais une solution simple appelé casier et est tombé sur ce, était utile, mais j'avais besoin d'une réponse plus simple, donc en dessous de ce que j'ai trouvé.

serrure Simple sous un certain nom et relâcher à nouveau 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
répondu Jaques 2013-10-14 11:11:51

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é.

1
répondu Java Newbie 2011-08-15 00:37:18

après une certaine déception qu'il n'y a pas de niveau de soutien de langue ou simple classe de Guava / Commons pour les serrures nommées,

C'est ce que j'ai fixé à:

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

ici j'ai réalisé: peu de code boilerplate sans dépendance de bibliothèque, atomiquement l'acquisition de l'objet lock, ne polluant pas les objets globaux de chaîne de caractères internés, pas de bas niveau notify/wait chaos et pas de try-catch-finalement mess.

1
répondu lyomi 2013-12-06 07:06:01

similaire à la réponse de Lyomi, mais utilise le ReentrantLock plus flexible 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 va augmenter avec le temps - mais l'utilisation de la rentrée ouvre de plus grandes possibilités pour enlever la serrure de la carte. Bien que, enlever des éléments de la carte ne semble pas tout que utile en considérant enlever des valeurs de la carte ne réduira pas sa taille. Il faudrait mettre en œuvre une certaine logique de dimensionnement manuel.

1
répondu BrightLight 2015-08-12 13:11:15

Mémoire considération

souvent, la synchronisation nécessaire pour une clé particulière est de courte durée. Le fait de se tenir à proximité des clés libérées peut entraîner un gaspillage excessif de mémoire, ce qui le rend non viable.

Voici une implémentation qui ne conserve 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<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();
    }
}

rentrée, et autres cloches et sifflets

si l'on veut la sémantique de l'écluse de rentrée, ils peuvent étendre le ci-dessus en enveloppant l'objet mutex dans une classe qui garde trace du thread de verrouillage et compte verrouillé.

p.ex.:

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 objet pour rendre les rejets plus faciles et plus sûrs, c'est aussi une possibilité.

en unissant tous les éléments, 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 peut l'utiliser:

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

    // do something critical here
}
1
répondu antak 2016-04-13 07:48:23

en réponse à la suggestion d'utiliser New MapMaker().makeComputingMap ()...

MapMaker().makeComputingMap () est déprécié 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'une serrure existante par valeur de chaîne. ERG.

0
répondu Ryan 2012-07-18 23:28:24

(4 ans plus tard...) Ma réponse est similaire à user2878608 mais je pense qu'il manque des cas limites dans cette logique. J'ai aussi pensé que le sémaphore était pour verrouiller plusieurs ressources à la fois (bien que je suppose que l'utiliser pour compter les casiers comme ça soit aussi bien), donc j'ai utilisé un objet générique de verrouillage de POJO à la place. J'ai fait un test sur elle qui a démontré que chacun des cas de bord existe IMO et sera l'utiliser sur mon projet au travail. Espérons que cela 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
répondu vazor 2015-02-12 00:56:52

j'ai créé un tokenProvider basé sur le IdMutexProvider de McDowell. Le gestionnaire utilise un WeakHashMap qui s'occupe du nettoyage des serrures inutilisées.

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

Utilisation:

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épondu R. Oosterholt 2017-07-25 12:17:44

ce fil est vieux, 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
répondu brandao 2018-02-17 21:59:36

Voici une solution simple et optimisée qui traite la suppression des serrures utilisées aussi, mais avec un plafond 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
répondu Nikhil 2018-04-27 14:10:46

plusieurs implémentations mais non similaires à la mienne.

a appelé mon implémentation de serrure dynamique comme ProcessDynamicKeyLock parce que c'est une serrure de processus simple, pour n'importe quel objet comme clé (égal+hashcode pour unicité).

TODO: Ajouter un moyen de fournir la serrure réelle, par exemple, ReentrantReadWriteLock au lieu de ReentrantLock .

mise en œuvre:

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

Simple test:

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();
        }
    }
}
0
répondu AlikElzin-kilaka 2018-05-22 15:25:31

votre idée d'un dépôt statique partagé d'objets de verrouillage pour chaque situation est correcte.

Vous n'avez pas besoin de synchroniser le cache lui-même ... cela peut être aussi simple qu'une carte de hachage.

Threads peut simultanément obtenir un objet lock de la carte. La logique de synchronisation réelle doit être encapsulée dans chacun de ces objets séparément (voir java.util.paquet concurrent pour cela - http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/package-summary.html )

-1
répondu Yoni 2011-04-12 18:36:27

TreeMap parce que dans HashMap la taille du tableau intérieur ne peut augmenter

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

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

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

    public static class Value<T> {

        private Lock lock;
        private long count;
        private T id;
    }
}
-2
répondu user249654 2012-11-21 19:45:54