How to acquire a lock by a key

前端 未结 6 1374
一个人的身影
一个人的身影 2020-11-28 09:47

What is the best way to prevent concurrent update of one record in a key-value set without locking the entire set? Semantically, I\'m looking for some kind of locking by a k

6条回答
  •  暗喜
    暗喜 (楼主)
    2020-11-28 10:16

    I've written a class that can lock on any key dynamically. It uses a static CuncurrentHashMap. But if no lock is used, the map is empty. The syntax can be confusing as a new object us created based on the key. It cleans up the lock, if not used, on unlock. There's a guarantee that any two DynamicKeyLock that were created based on two equal/hascode keys, they'll be mutually locked.

    See implementation for Java 8, Java 6 and a small test.

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

    Test:

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

    提交回复
    热议问题