간단한 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 의 답변을 기반으로 WeakHashMap 을 LockMap
사용 하여 잠금 개체를 저장하고 일반 클래스 를 작성했습니다 . 키에 대한 잠금 오브젝트를 검색하는 데 사용할 수 있고 잠금 을 적용하기 위해 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<String> myTokenProvider = new TokenMutexProvider<String>();
*
* 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
'ProgramingTip' 카테고리의 다른 글
Netbeans에서 현재 줄 선택, 복사 또는 잘라 내기 (0) | 2020.12.15 |
---|---|
VBA 언어로 "값이 비어 있지 않은 경우"를 어떻게 표현합니까? (0) | 2020.12.15 |
ModelState 유효성 검사 수동 호출 (0) | 2020.12.15 |
/ WEB-INF에 넣어야하는 XHTML 파일과 않은 파일은 무엇입니까? (0) | 2020.12.15 |
클래스에 __getitem__을 정의하면 반복 할 수있는 이유는 무엇입니까? (0) | 2020.12.14 |