java 如何通过钥匙获得锁

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11124539/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 03:54:28  来源:igfitidea点击:

How to acquire a lock by a key

javaalgorithmsynchronizationlocking

提问by user1128016

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 key (ideally, Java implementation, but not necessarily):

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

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

This lock is intended to synchronize an access to a remote store, so some synchronized Java collection is not an option.

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

回答by Louis Wasserman

Guava has something like this being released in 13.0; you can get it out of HEAD if you like.

Guava 有类似的东西在 13.0 中发布;如果你愿意,你可以把它弄出来。

Striped<Lock>more or less allocates a specific number of locks, and then assigns strings to locks based on their hash code. The API looks more or less like

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

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

More or less, the controllable number of stripes lets you trade concurrency against memory usage, because allocating a full lock for each string key can get expensive; essentially, you only get lock contention when you get hash collisions, which are (predictably) rare.

或多或少,可控的条带数量使您可以在并发性与内存使用之间进行权衡,因为为每个字符串键分配一个完整的锁可能会变得昂贵;本质上,只有在发生哈希冲突时才会发生锁争用,而这种冲突(可预测)很少见。

(Disclosure: I contribute to Guava.)

(披露:我为番石榴做出了贡献。)

回答by AlikElzin-kilaka

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 DynamicKeyLockthat were created based on two equal/hascode keys, they'll be mutually locked.

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

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

请参阅 Java 8、Java 6 的实现和一个小测试。

Java 8:

爪哇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:

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

Test:

测试:

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

回答by Anton Fil

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- is very important - you must guarantee to unlock waiting threads after your operation even if your operation threw exception.

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

回答by Karthik Kumar Viswanathan

Keep a mutex/lock per bucket. This will ensure that only collisions wait on that mutex.

每个桶保持一个互斥锁/锁。这将确保只有冲突等待该互斥锁。

回答by Jim Garrison

If the "record" you mention is a mutable object and "update" means that the object's internal state is modified without disturbing the structure of the container, then you can accomplish what you want just by locking the record object.

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

If however "update" means removing the record object from the container and replacing it, then you must lock then entire container to prevent other threads from seeing it in an inconsistent state.

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

In either case, you should be looking at the classes in the java.util.concurrentpackage.

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

回答by Kanagavelu Sugumar

This is how; i did it. And yes I agree if two different strings shares the same hashcode will end up with acquiring the same lock.

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

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];
    }
}