ProgramingTip

간단한 Java 이름 기반 잠금?

bestdevel 2020. 12. 15. 19:37
반응형

간단한 Java 이름 기반 잠금?


MySQL은 편리한 기능이 있습니다.

SELECT GET_LOCK("SomeName")

이 응용 프로그램에 대한 간단하지만 매우 구체적인 이름 기반 잠금을 만드는 데 사용할 수 있습니다. 그러나 데이터베이스가 필요합니다.

다음과 같은 많은 상황이 있습니다.

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

예를 들어 사용자 B에 대해 메서드가 그 동안 호출되는 경우 사용자 B는 시작하기 전에 사용자 A가 완료 될 때까지 기다릴 필요가없고 사용자에 대한 작업 만 가능하기 때문에이 메서드를 동기화하는 것은 의미가 없습니다. A와 기능 X의 조합을 기다려야합니다.

MySql 잠금을 사용하면 다음과 같은 작업을 수행 할 수 있습니다.

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

자바 잠금은 객체를 기반으로하기 때문에 잠금에 대한 상황을 새 객체를 만든 다음 모든 것가 볼 수 있도록 어딘가에 정적 캐시에 넣어야 할 것입니다. 해당 상황에 대한 후속 잠금 요청은 캐시에서 잠금 개체를 찾고 잠금을 금액합니다. 나는 이와 같은 것을 만들려고했지만 잠금 캐시 자체에 동기화가 필요합니다. 또한 잠금 개체가 더 이상 사용되지 않는 경우를 감지하여 캐시에서 제거 할 수 있습니다.

Java 동시 패키지를 이와 같은 것을 처리 할 수있는 것이 없습니다. 구현하는 쉬운 방법이 있습니까?

편집하다 :

대규모하기 위해 미리 정의 된 잠금 풀을 생성하지 않고 필요에 따라 생성하고 싶습니다. 내가 생각하는 것에 대한 의사 코드는 다음과 같다.

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
}

아마도 이것이 당신에게 유용 할 것입니다 : jkeylockmanager

편집하다 :

내 초기 응답은 아마도 약간 짧았습니다. 나는 존재하는 문제에 여러 번 존재하는 솔루션을 수 없습니다. 그래서 Google 코드 에이 작은 라이브러리를 만들었습니다.


내가 보는 모든 대답은 너무 복잡합니다. 사용하지 않는 이유 :

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

요점은 메소드입니다 intern. vm-instance-wide 뮤텍스로 사용할 수 있습니다. 인턴 된 모든 개설 된 전역 풀에 보관되어있는 질문에서 참조 캐시입니다. memleaks에 대해 걱정하지 마십시오. 다른 수식이 참조하지 않습니다. 그러나 Java6 까지이 풀은 힙 대신 PermGen 공간에 보관이를 늘려야 할 수도 있습니다.

VM의 다른 코드가 완전히 다른 경우에는 많은 수가 잠그는 경우 문제가있을 것입니다) 이것은 거의 발생하지 않습니다 b) 스페이스를 도입하여 수 있습니다. executeInNamedLock(this.getClass().getName() + "_" + myLockName);


당신은 귀하의 주소 Map<String, java.util.concurrent.Lock>입니까? 자물쇠가 필요할 때마다 기본적으로 map.get(lockName).lock().

다음은 Google Guava 를 사용 예입니다 .

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

그런 다음 필요한 경우lockMap.get("anyOldString") 새 잠금이 생성 되어 사용자에게 반환됩니다. 그런 다음 해당 잠금 을 호출 할 수 있습니다 . 모든 형식과 공유 할 수 있습니다.lock()makeComputingMap


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

조금 후에 Google Guava Striped를 사용할 수 있습니다.

개념적으로 잠금 생성 스트라이핑은 단일 잠금의 세분성을 하나의 단일 잠금에 대한 경합을 대신하는 독립적 인 작업이 여러 곳에서 잠그고 동시에 진행할 수 있습니다.

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

사용자 이름과 같은 것을 잠그기 Lock위해 맵의 메모리가 약간있을 수 있습니다. 대안 으로 의 WeakHashMap 과 함께 WeakReference를 를 사용하여 아무 것도 참조하지 않을 때 가비지 수집 될 수있는 뮤텍스 object-를 만들 수 있습니다. 이렇게하면 메모리를 확보하기 위해 수동 참조 계산을 수행 할 필요가 없습니다.

여기 에서 구현을 사용할 수 있습니다 . 맵에서 자주 조회하는 경우 뮤텍스를 사용하는 경합 문제가 있습니다.


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

        }


    }

}

McDowell 및 그의 클래스 IdMutexProvider답변을 기반으로 WeakHashMapLockMap사용 하여 잠금 개체를 저장하고 일반 클래스 작성했습니다 . 키에 대한 잠금 오브젝트를 검색하는 데 사용할 수 있고 잠금 을 적용하기 위해 Java 문과 함께 사용할 수 있습니다 . 사용하지 않는 잠금 잠금은 가비지 수집 중에 자동으로 해제됩니다.LockMap.get()synchronized (...)

import java.lang.ref.WeakReference;
import java.util.WeakHashMap;

// A map that creates and stores lock objects for arbitrary keys values.
// Lock objects which are no longer referenced are automatically released during garbage collection.
// Author: Christian d'Heureuse, www.source-code.biz
// Based on IdMutexProvider by McDowell, http://illegalargumentexception.blogspot.ch/2008/04/java-synchronizing-on-transient-id.html
// See also https://stackoverflow.com/questions/5639870/simple-java-name-based-locks
public class LockMap<KEY> {

private WeakHashMap<KeyWrapper<KEY>,WeakReference<KeyWrapper<KEY>>> map;

public LockMap() {
   map = new WeakHashMap<KeyWrapper<KEY>,WeakReference<KeyWrapper<KEY>>>(); }

// Returns a lock object for the specified key.
public synchronized Object get (KEY key) {
   if (key == null) {
      throw new NullPointerException(); }
   KeyWrapper<KEY> newKeyWrapper = new KeyWrapper<KEY>(key);
   WeakReference<KeyWrapper<KEY>> ref = map.get(newKeyWrapper);
   KeyWrapper<KEY> oldKeyWrapper = (ref == null) ? null : ref.get();
   if (oldKeyWrapper != null) {
      return oldKeyWrapper; }
   map.put(newKeyWrapper, new WeakReference<KeyWrapper<KEY>>(newKeyWrapper));
   return newKeyWrapper; }

// Returns the number of used entries in the map.
public synchronized int size() {
   return map.size(); }

// KeyWrapper wraps a key value and is used in three ways:
// - as the key for the internal WeakHashMap
// - as the value for the internal WeakHashMap, additionally wrapped in a WeakReference
// - as the lock object associated to the key
private static class KeyWrapper<KEY> {
   private KEY key;
   private int hashCode;
   public KeyWrapper (KEY key) {
      this.key = key;
      hashCode = key.hashCode(); }
   public boolean equals (Object obj) {
      if (obj == this) {
         return true; }
      if (obj instanceof KeyWrapper) {
         return ((KeyWrapper)obj).key.equals(key); }
      return false; }
   public int hashCode() {
      return hashCode; }}

} // end class LockMap

사용 방법의 예 :

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

synchronized (lockMap.get(name)) {
   ... 
}

LockMap 클래스에 대한 간단한 테스트 프로그램 :

public static Object lock1;
public static Object lock2;

public static void main (String[] args) throws Exception {
   System.out.println("TestLockMap Started");
   LockMap<Integer> map = new LockMap<Integer>();
   lock1 = map.get(1);
   lock2 = map.get(2);
   if (lock2 == lock1) {
      throw new Error(); }
   Object lock1b = map.get(1);
   if (lock1b != lock1) {
      throw new Error(); }
   if (map.size() != 2) {
      throw new Error(); }
   for (int i=0; i<10000000; i++) {
      map.get(i); }
   System.out.println("Size before gc: " + map.size());   // result varies, e.g. 4425760
   System.gc();
   Thread.sleep(1000);
   if (map.size() != 2) {
      System.out.println("Size after gc should be 2 but is " + map.size()); }
   System.out.println("TestLockMap completed"); }

LockMap 클래스를 자동으로 테스트하는 더 좋은 방법을 아는 사람이 있으면 의견을 작성하십시오.


ConcurrentHashMap간단한 배타적 멀티 기능 잠금에 충분한 잠금 기능이 내장 되어 있습니다. 추가 Lock개체가 필요하지 않습니다 .

다음은 단일 클라이언트에 대해 최대 하나의 활성 jms 처리를 적용하는 데 사용되는 잠금 맵의 예입니다.

private static final ConcurrentMap<String, Object> lockMap = new ConcurrentHashMap<String, Object>();
private static final Object DUMMY = new Object();

private boolean tryLock(String key) {
    if (lockMap.putIfAbsent(key, DUMMY) != null) {
        return false;
    }
    try {
        if (/* attempt cluster-wide db lock via select for update nowait */) {
            return true;
        } else {
            unlock(key);
            log.debug("DB is already locked");
            return false;
        }
    } catch (Throwable e) {
        unlock(key);
        log.debug("DB lock failed", e);
        return false;
    }
}

private void unlock(String key) {
    lockMap.remove(key);
}

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void onMessage(Message message) {
    String key = getClientKey(message);
    if (tryLock(key)) {
        try {
            // handle jms
        } finally {
            unlock(key);
        }
    } else {
        // key is locked, forcing redelivery
        messageDrivenContext.setRollbackOnly();
    }
}

2 년 후, 간단한 이름의 사물함 솔루션을 찾고 있었는데 발견하고 유용했지만 더 간단한 대답이있었습니다.

어떤 이름으로 간단하게 잠그고 같은 이름으로 다시 해제하십시오.

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

다음은 코드입니다.

public class NamedLocker {
    private ConcurrentMap<String, Semaphore> synchSemaphores = new ConcurrentHashMap<String, Semaphore>();
    private int permits = 1;

    public NamedLocker(){
        this(1);
    }

    public NamedLocker(int permits){
        this.permits = permits;
    }

    public void acquireLock(String... key){
        Semaphore tempS = new Semaphore(permits, true);
        Semaphore s = synchSemaphores.putIfAbsent(Arrays.toString(key), tempS);
        if(s == null){
            s = tempS;
        }
        s.acquireUninterruptibly();
    }

    public void releaseLock(String... key){
        Semaphore s = synchSemaphores.get(Arrays.toString(key));
        if(s != null){
            s.release();
        }
    }
}

아마도 다음과 가변적입니다.

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

그래도 테스트하지 않을 것입니다.


명명 된 잠금에 대한 언어 수준 지원이나 간략하게 Guava / Commons 클래스가 실망한 후,

이것이 내가 아니라 한 것입니다.

ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();

Object getLock(String name) {
    Object lock = locks.get(name);
    if (lock == null) {
        Object newLock = new Object();
        lock = locks.putIfAbsent(name, newLock);
        if (lock == null) {
            lock = newLock;
        }
    }
    return lock;
}

void somethingThatNeedsNamedLocks(String name) {
    synchronized(getLock(name)) {
        // some operations mutually exclusive per each name
    }
}

여기에서 달성했습니다. 라이브러리가없는 상용구 코드, 잠금 개체를 원자 적으로 적으로 그리고, 글로벌 인턴 수준 알림 / 대기 혼돈 및 try-catch-finally 엉망이 없습니다.


Lyomi의 답변과 대화 할 때 동기화 된 블록 대신 더 융통성있는 ReentrantLock을 사용합니다.

public class NamedLock
{
    private static final ConcurrentMap<String, Lock> lockByName = new ConcurrentHashMap<String, Lock>();

    public static void lock(String key)
    {
        Lock lock = new ReentrantLock();
        Lock existingLock = lockByName.putIfAbsent(key, lock);

        if(existingLock != null)
        {
            lock = existingLock;
        }
        lock.lock();
    }

    public static void unlock(String key) 
    {
        Lock namedLock = lockByName.get(key);
        namedLock.unlock();
    }
}

예,이 시간이 지남에 따라 증가하지만 ReentrantLock을 사용하면지도에서 잠금을 제거 할 수있는 더 많은 가능성이 있습니다. 하지만 맵에서 항목을 제거하는 것이 맵에서 값을 제거하는 것을 고려할 때 그다지 유용하지 않은 것 같지만 크기가 유용하지 않습니다. 일부 수동 맵 크기 조정 논리를 구현해야합니다.


메모리 고려 사항

종종 특정 키에 필요한 동기화는 수명이 짧습니다. 해제 된 키를 유지하면 여분의 메모리가 발생하여 실행 불가능하게 될 수 있습니다.

다음은 릴리스 된 키를 내부적으로 유지하지 않는 구현입니다.

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

재진입 및 기타 종소리와 휘파람

재진입 잠금 의미 체계를 체계적인 잠금과 잠긴 개수를 추적하는 클래스에서 뮤텍스를 확장 래핑하여 위의 내용을 확장합니다.

예 :

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

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

    int lockedCount = 1;
}

lock()릴리스를 더 제조 업체 만들기 위해 객체를 반환 할 경우 에도 가능합니다.

종합 해보면 다음과 같은 수업이 될 수 있습니다.

public class KeyedReentrantLocks<K> {

    private final ConcurrentMap<K, KeyedLock> key2Lock = new ConcurrentHashMap<>();

    public KeyedLock acquire(K key) throws InterruptedException {
        final KeyedLock ourLock = new KeyedLock() {
            @Override
            public void close() {
                if (Thread.currentThread().getId() != threadId) {
                    throw new IllegalStateException("wrong thread");
                }
                if (--lockedCount == 0) {
                    key2Lock.remove(key);
                    mutex.countDown();
                }
            }
        };
        for (;;) {
            KeyedLock theirLock = key2Lock.putIfAbsent(key, ourLock);
            if (theirLock == null) {
                return ourLock;
            }
            if (theirLock.threadId == Thread.currentThread().getId()) {
                theirLock.lockedCount++;
                return theirLock;
            }
            theirLock.mutex.await();
        }
    }

    public static abstract class KeyedLock implements AutoCloseable {
        protected final CountDownLatch mutex = new CountDownLatch(1);
        protected final long threadId = Thread.currentThread().getId();
        protected int lockedCount = 1;

        @Override
        public abstract void close();
    }
}

그리고 그것을 사용하는 방법은 다음과 가변합니다.

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

    // do something critical here
}

많은 구현이지만 내 것과 유사하지 않습니다.

ProcessDynamicKeyLock모든 객체를 키로 (고유성을 위해 같음 + 해시 코드) 단일 프로세스 잠금이기 내 동적 잠금 구현을 호출 합니다.

TODO : 예를 들어, ReentrantReadWriteLock대신 실제 잠금을 제공하는 방법을 추가합니다 ReentrantLock.

이행 :

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

간단한 테스트 :

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

새지도 작성기 (). makeComputingMap () ... 사용 제안에 대한 응답으로 ...

지도 작성기 (). makeComputingMap ()은 안전상의 자료가 더 이상 사용되지 않습니다. 계속 제품은 CacheBuilder입니다. CacheBuilder에 거의 약한 키 / 값으로 우리는 솔루션에 가깝습니다.

문제는 CacheBuilder.weakKeys ()의 참고 사항입니다.

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

이로 인해 기존에 존재하는 기존 잠금을 ​​선택할 수 없습니다. 에르그.


(4 년 후 ...) 내 대답은 user2878608과 해결해야 할 논리에 몇 가지 누락 된 경우가 있다고 생각합니다. 나는 또한 Semaphore가 한 번에 여러 리소스를 잠그는 대신 일반 생각 때문에 (그렇게 로커를 계산하는데 사용하는 생각하지만) 대신 일반 POJO 잠금 개체를 사용했습니다. 나는 IMO가 존재하는 각 에지 케이스를 증명하는 하나의 테스트를 실행하는 것 직장에서 내 프로젝트에 사용할 수 있습니다. 누군가에게 도움이되기를 바랍니다. :)

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

}

McDowell IdMutexProvider기반으로 tokenProvider를 만들었습니다 . 사용 WeakHashMap하지 않는 잠금을 정리하는 관리자를 사용합니다.

TokenManager :

/**
 * Token provider used to get a {@link Mutex} object which is used to get exclusive access to a given TOKEN.
 * Because WeakHashMap is internally used, Mutex administration is automatically cleaned up when
 * the Mutex is no longer is use by any thread.
 *
 * <pre>
 * Usage:
 * private final TokenMutexProvider&lt;String&gt; myTokenProvider = new TokenMutexProvider&lt;String&gt;();
 *
 * Mutex mutex = myTokenProvider.getMutex("123456");
 * synchronized (mutex) {
 *  // your code here
 * }
 * </pre>
 *
 * Class inspired by McDowell.
 * url: http://illegalargumentexception.blogspot.nl/2008/04/java-synchronizing-on-transient-id.html
 *
 * @param <TOKEN> type of token. It is important that the equals method of that Object return true
 * for objects of different instances but with the same 'identity'. (see {@link WeakHashMap}).<br>
 * E.g.
 * <pre>
 *  String key1 = "1";
 *  String key1b = new String("1");
 *  key1.equals(key1b) == true;
 *
 *  or
 *  Integer key1 = 1;
 *  Integer key1b = new Integer(1);
 *  key1.equals(key1b) == true;
 * </pre>
 */
public class TokenMutexProvider<TOKEN> {

    private final Map<Mutex, WeakReference<Mutex>> mutexMap = new WeakHashMap<Mutex, WeakReference<Mutex>>();

    /**
     * Get a {@link Mutex} for the given (non-null) token.
     */
    public Mutex getMutex(TOKEN token) {
        if (token==null) {
            throw new NullPointerException();
        }

        Mutex key = new MutexImpl(token);
        synchronized (mutexMap) {
            WeakReference<Mutex> ref = mutexMap.get(key);
            if (ref==null) {
                mutexMap.put(key, new WeakReference<Mutex>(key));
                return key;
            }
            Mutex mutex = ref.get();
            if (mutex==null) {
                mutexMap.put(key, new WeakReference<Mutex>(key));
                return key;
            }
            return mutex;
        }
    }

    public int size() {
        synchronized (mutexMap) {
            return mutexMap.size();
        }
    }

    /**
     * Mutex for acquiring exclusive access to a token.
     */
    public static interface Mutex {}

    private class MutexImpl implements Mutex {
        private final TOKEN token;

        protected MutexImpl(TOKEN token) {
            this.token = token;
        }

        @Override
        public boolean equals(Object other) {
            if (other==null) {
                return false;
            }
            if (getClass()==other.getClass()) {
                TOKEN otherToken = ((MutexImpl)other).token;
                return token.equals(otherToken);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return token.hashCode();
        }
    }
}

용법 :

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

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

아니면 정수를 사용 하시겠습니까?

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

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

스레드는 오래이되었지만 가능한 솔루션은 https://github.com/brandaof/named-lock -frame 워크 입니다.

NamedLockFactory lockFactory = new NamedLockFactory();

...

Lock lock = lockFactory.getLock("lock_name");
lock.lock();

try{
  //manipulate protected state
}
finally{
    lock.unlock();
}

다음은 사용 된 잠금의 제거를 해결하는 간단하고 최적화 된 솔루션이지만 맵 동기화의 오버가 있습니다.

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

}


각 상황에 대한 잠금 개체의 공유 정적 저장소에 대한 아이디어는 있습니다.
캐시 자체를 동기화 할 필요가 없습니다. 해시 맵처럼 간단하게 할 수 있습니다.

스레드는 맵에서 동시에 잠금 객체를 가져올 수 있습니다. 실제 동기화 로직은 이러한 각 객체 내에 별도로 캡슐화되어야합니다 ( http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/에 대한 java.util.concurrent 패키지 참조). package-summary.html )


내부 배열의 HashMap 크기가 증가 할 수 있기 때문에 TreeMap

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

참조 URL : https://stackoverflow.com/questions/5639870/simple-java-name-based-locks

반응형