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ロックはオブジェクトに基づいているため、このロックの状況を表す新しいオブジェクトを作成し、すべてのスレッドが見えるように静的キャッシュに配置する必要があるようです。そのような状況でロックする後続の要求は、キャッシュ内のロックオブジェクトを見つけてロックを取得します。私はこのような何かを作成しようとしましたが、ロックキャッシュ自体に同期が必要です。オブジェクトはキャッシュから削除できるように使用されなくなっています。
私は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 Codeで作成しました。
私が見るそれらの答えはすべて非常に複雑です。なぜ単に使用しないのですか:
_public void executeInNamedLock(String lockName, Runnable runnable) {
synchronized(lockName.intern()) {
runnable.run();
}
}
_
キーポイントはメソッドintern
です。返される文字列がグローバルな一意のオブジェクトであることを保証するため、vmインスタンス全体のミューテックスとして使用できます。インターンされたすべての文字列はグローバルプールに保持されるため、元の質問で話していた静的キャッシュになります。 memleaksを心配しないでください。他のスレッドがそれを参照していない場合、これらの文字列はgcされます。ただし、Java6までは、このプールはヒープではなくPermGenスペースに保持されるため、増やす必要がある場合があります。
ただし、vm内の他のコードがまったく異なる理由で同じ文字列をロックする場合は問題がありますが、a)これは非常にまれであり、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
sが少し漏れやすいかもしれません。別の方法として、 WeakReference sと WeakHashMap を使用して、何も参照していないときにガベージコレクションできるmutexオブジェクトを作成する方法もあります。これにより、メモリを解放するために手動で参照カウントを行う必要がなくなります。
実装を見つけることができます こちら 。マップで頻繁にルックアップを行う場合、ミューテックスを取得する競合の問題が発生する可能性があることに注意してください。
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
を記述しましたオブジェクト。 LockMap.get()
を使用してキーのロックオブジェクトを取得し、Java 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
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
オブジェクトは必要ありません。
単一のクライアントに対して最大で1つのアクティブな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を使用すると、マップからロックを削除する可能性が広がります。ただし、マップからアイテムを削除しても、マップから値を削除してもサイズが縮小しないことを考慮すると、それほど便利ではないようです。いくつかの手動のマップサイズ変更ロジックを実装する必要があります。
多くの実装ですが、私の実装とは異なります。
動的ロックの実装を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();
}
}
}
多くの場合、特定のキーに必要な同期は短命です。解放されたキーを保持することは、過度のメモリの浪費につながり、実行不可能になります。
以下は、リリースされたキーを内部的に保持しない実装です。
_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
}
_
このスレッドは古いですが、考えられる解決策はフレームワーク 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();
}
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
}
以下は、使用済みロックの削除にも対処するシンプルで最適化されたソリューションですが、マップの同期のオーバーヘッドがあります。
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();
}
}
新しいMapMaker()。makeComputingMap()を使用する提案に応えて...
MapMaker()。makeComputingMap()は安全上の理由から非推奨です。後継者はCacheBuilderです。弱いキー/値がCacheBuilderに適用されているため、ソリューションに近づいています。
問題はCacheBuilder.weakKeys()の注意です:
when this method is used, the resulting cache will use identity (==) comparison to determine equality of keys.
これにより、文字列値で既存のロックを選択できなくなります。えー.
(4年後...)私の答えはuser2878608の場合と似ていますが、そのロジックにはいくつかの欠落したEdgeケースがあると思います。また、セマフォは複数のリソースを一度にロックするためのものだと思いました(そのようなロッカーのカウントにも使用すると思いますが)ので、代わりに汎用POJOロックオブジェクトを使用しました。エッジケースのそれぞれにIMOが存在し、作業中のプロジェクトでそれを使用することを実証するテストを1つ実行しました。それが誰かを助けることを願っています。 :)
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;
}
}
}
}
}