【问题标题】:How to acquire a lock by a key如何通过钥匙获得锁
【发布时间】:2012-06-22 21:12:37
【问题描述】:

在不锁定整个集合的情况下,防止同时更新键值集中的一条记录的最佳方法是什么?从语义上讲,我正在寻找某种按键锁定(理想情况下,Java 实现,但不一定):

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

此锁旨在同步对远程存储的访问,因此不能选择某些同步的 Java 集合。

【问题讨论】:

标签: java algorithm synchronization locking


【解决方案1】:

为每个存储桶保留一个互斥锁/锁。这将确保只有冲突在该互斥体上等待。

【讨论】:

    【解决方案2】:

    如果你提到的“记录”是一个可变对象,而“更新”是指在不干扰容器结构的情况下修改了对象的内部状态,那么你只需锁定记录对象即可完成你想要的。

    如果“更新”意味着从容器中删除记录对象并替换它,那么您必须锁定整个容器以防止其他线程看到它处于不一致的状态。

    无论哪种情况,您都应该查看 java.util.concurrent 包中的类。

    【讨论】:

      【解决方案3】:

      Guava 在 13.0 中发布了类似的内容;如果你愿意,你可以把它从 HEAD 中取出。

      Striped<Lock> 或多或少分配特定数量的锁,然后根据其哈希码将字符串分配给锁。 API 看起来或多或少像

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

      或多或少,可控制的条带数量让您可以用内存使用来交换并发性,因为为每个字符串键分配一个完整的锁可能会很昂贵;从本质上讲,只有在发生哈希冲突时才会出现锁争用,而这种情况(可以预见的)很少见。

      (披露:我为 Guava 做出了贡献。)

      【讨论】:

      • 与旧锁相关的内存是否被清理?就像保存在 WeakHashMap 中一样。
      • 如果使用工厂方法生成弱锁就可以了。
      • @Jose Martinez:不,它不清洁任何东西,它不必清洁任何东西,它的运作原理不同。以Striped.lock(1024) 为例,它创建了简单的 Lock[1024] 数组,用 1024 个预生成的 Lock 对象进行了热切初始化;见Striped.CompactStriped。您可以拥有具有数十亿个唯一 ID 的应用程序,但您的锁池始终保持在 1024 个相同的锁。 Striped 在统计上以非常低的概率运行,即 2 个或更多生成相同哈希的 ID 尝试同时访问互斥体。
      • @Espinosa 锁很弱,如果你调用Striped.lazyWeakLock而不是Striped.lock
      【解决方案4】:

      这是怎么回事;我做到了。是的,我同意如果两个不同的字符串共享相同的哈希码,最终会获得相同的锁。

      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];
          }
      }
      
      【解决方案5】:

      我编写了一个可以动态锁定任何键的类。 它使用静态CuncurrentHashMap。但如果不使用锁,则地图为空。作为我们基于键创建的新对象,语法可能会令人困惑。 如果不使用,它会清理unlock 上的锁。 保证基于两个相等/hascode 键创建的任何两个DynamicKeyLock,它们将被相互锁定。

      查看 Java 8、Java 6 的实现和一个小测试。

      Java 8:

      public class DynamicKeyLock<T> implements Lock
      {
          private final static ConcurrentHashMap<Object, LockAndCounter> 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<T> implements Lock
      {
          private final static ConcurrentHashMap<Object, LockAndCounter> locksMap = new ConcurrentHashMap<Object, LockAndCounter>();
          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<Object> lock = new DynamicKeyLock<>(new Object());
              lock.lock();
              AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false);
              try
              {
                  new Thread(() ->
                  {
                      DynamicKeyLock<Object> 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<Object> lock = new DynamicKeyLock<>(key);
              lock.lock();
              AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false);
              try
              {
                  new Thread(() ->
                  {
                      DynamicKeyLock<Object> anotherLock = new DynamicKeyLock<>(key);
                      anotherLock.lock();
                      try
                      {
                          anotherThreadWasExecuted.set(true);
                      }
                      finally
                      {
                          anotherLock.unlock();
                      }
                  }).start();
                  Thread.sleep(100);
              }
              finally
              {
                  Assert.assertFalse(anotherThreadWasExecuted.get());
                  lock.unlock();
              }
          }
      }
      

      【讨论】:

      【解决方案6】:
      private static final Set<String> lockedKeys = new HashSet<>();
      
      private void lock(String key) throws InterruptedException {
          synchronized (lockedKeys) {
              while (!lockedKeys.add(key)) {
                  lockedKeys.wait();
              }
          }
      }
      
      private void unlock(String key) {
          synchronized (lockedKeys) {
              lockedKeys.remove(key);
              lockedKeys.notifyAll();
          }
      }
      
      public void doSynchronously(String key) throws InterruptedException {
          try {
              lock(key);
      
              //Do what you need with your key.
              //For different keys this part is executed in parallel.
              //For equal keys this part is executed synchronously.
      
          } finally {
              unlock(key);
          }
      }
      

      try-finally - 非常重要 - 即使您的操作抛出异常,您也必须保证在操作后解锁等待线程。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-02-19
        • 2016-06-01
        • 2018-11-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多