Как получить блокировку ключом

Каков наилучший способ предотвратить одновременное обновление одной записи в наборе ключевых значений без блокировки всего набора? Семантически, я ищу некоторую блокировку ключом (в идеале, реализацией Java, но не обязательно):

interface LockByKey { void lock(String key); // acquire an exclusive lock for a key void unlock(String key); // release lock for a key } 

Эта блокировка предназначена для синхронизации доступа к удаленному хранилищу, поэтому некоторая синхронизированная коллекция Java не является вариантом.

У Guava есть что-то подобное, выпущенное в 13.0; вы можете получить его из HEAD, если хотите.

Striped более или менее выделяет определенное количество блокировок, а затем назначает строки для блокировок на основе их hash-кода. API выглядит примерно так:

 Striped locks = Striped.lock(stripes); Lock l = locks.get(string); l.lock(); try { // do stuff } finally { l.unlock(); } 

Более или менее контролируемое количество полос позволяет торговать параллелизмом с использованием памяти, потому что выделение полной блокировки для каждого строкового ключа может стать дорогостоящим; по сути, вы получаете только блокировку, когда получаете хеш-коллизии, которые (как предсказуемо) редки.

(Раскрытие информации: Я вношу свой вклад в Гуаву.)

Вот как это делается; я сделал это. И да, я согласен с тем, что две разные строки разделяют один и тот же hash-код, в результате получится тот же замок.

 class LockByKey { ObjectForString objHolder = new ObjectForString(100); public void lockThenWorkForKey (String key) { synchronized(objHolder.valueOf(key)){ //DoSomeWork } } } public final class ObjectForString { private final Object[] cache; private final int cacheSize; final int mask; public ObjectForString(int size) { // Find power-of-two sizes best matching arguments int ssize = 1; while (ssize < size) { ssize <<= 1; } mask = ssize - 1; cache = new Object[ssize]; cacheSize = ssize; //build the Cache for (int i = 0; i < cacheSize; i++) { this.cache[i] = new Object(); } } public Object valueOf(String key) { int index = key.hashCode(); return cache[index & mask]; } } 

Храните мьютекс / замок на ведро. Это гарантирует, что на этом мьютексе ждут только столкновения.

Если упоминаемая вами «запись» является изменчивым объектом, а «обновление» означает, что внутреннее состояние объекта изменяется без нарушения структуры контейнера, то вы можете выполнить то, что хотите, только путем блокировки объекта записи.

Если, однако, «обновление» означает удаление объекта записи из контейнера и его замену, вы должны заблокировать весь контейнер, чтобы другие streamи не видели его в непоследовательном состоянии.

В любом случае вы должны смотреть на classы в пакете java.util.concurrent .

Я написал class, который может блокировать любую клавишу динамически. Он использует статический CuncurrentHashMap . Но если блокировка не используется, карта пуста. Синтаксис может сбивать с толку как новый объект, созданный нами на основе ключа. Он очищает замок, если он не используется, при unlock . Есть гарантия, что любые два DynamicKeyLock , созданные на основе двух одинаковых / hascode-ключей, будут взаимно заблокированы.

См. Реализацию для Java 8, Java 6 и небольшой тест.

Java 8:

 public class DynamicKeyLock implements Lock { private final static ConcurrentHashMap locksMap = new ConcurrentHashMap<>(); private final T key; public DynamicKeyLock(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(); } } 

Java 6:

 public class DynamicKeyLock implements Lock { private final static ConcurrentHashMap locksMap = new ConcurrentHashMap(); private final T key; public DynamicKeyLock(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() { while (true) // Try to init lock { LockAndCounter lockAndCounter = locksMap.get(key); if (lockAndCounter == null) { LockAndCounter newLock = new LockAndCounter(); lockAndCounter = locksMap.putIfAbsent(key, newLock); if (lockAndCounter == null) { lockAndCounter = newLock; } } lockAndCounter.counter.incrementAndGet(); synchronized (lockAndCounter) { LockAndCounter lastLockAndCounter = locksMap.get(key); if (lockAndCounter == lastLockAndCounter) { return lockAndCounter; } // else some other thread beat us to it, thus try again. } } } private void cleanupLock(LockAndCounter lockAndCounter) { if (lockAndCounter.counter.decrementAndGet() == 0) { synchronized (lockAndCounter) { if (lockAndCounter.counter.get() == 0) { locksMap.remove(key); } } } } @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 DynamicKeyLockTest { @Test public void testDifferentKeysDontLock() throws InterruptedException { DynamicKeyLock lock = new DynamicKeyLock<>(new Object()); lock.lock(); AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false); try { new Thread(() -> { DynamicKeyLock anotherLock = new DynamicKeyLock<>(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(); DynamicKeyLock lock = new DynamicKeyLock<>(key); lock.lock(); AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false); try { new Thread(() -> { DynamicKeyLock anotherLock = new DynamicKeyLock<>(key); anotherLock.lock(); try { anotherThreadWasExecuted.set(true); } finally { anotherLock.unlock(); } }).start(); Thread.sleep(100); } finally { Assert.assertFalse(anotherThreadWasExecuted.get()); lock.unlock(); } } } 
  • Android: Как получить модальный диалог или подобное модальное поведение?
  • Interesting Posts
    Давайте будем гением компьютера.