设计基于密钥的锁(或锁映射)

2022-09-02 10:57:00

我试图设计一个基于密钥的锁定工具:类似于普通的重入锁定,但不是lock()和unlock(),而是锁定(key)和unlock(key),合同是没有人能够同时锁定(key1),如果key.equals(key1)。

此代码是否有效?有没有更高效的解决方案?我特别不喜欢在试图将锁放在地图上时的while循环......

package luca;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;

public class KeyedReentrantLock<K> {
    private ConcurrentMap<K, ReentrantLock> lockMap = new ConcurrentHashMap<K, ReentrantLock>();

    public void lock(K key) {
        ReentrantLock oldLock = lockMap.get(key);
        if (oldLock != null && oldLock.isHeldByCurrentThread()){
            // increase lock count and return.
            oldLock.lock();
            return;
        }
        ReentrantLock newLock = new ReentrantLock();
        newLock.lock();
        while ((oldLock = lockMap.putIfAbsent(key, newLock)) != null){
            // wait for the old lock to be released;
            oldLock.lock();
            oldLock.unlock();
        }
        return;
    }

    public void unlock(K key){
        ReentrantLock lock = lockMap.get(key);
        if (lock == null) throw new IllegalMonitorStateException("There was no lock for this key!");
        if (lock.getHoldCount() == 1){
            lockMap.remove(key);
        }
        lock.unlock();
    }

}

答案 1

为什么不直接使用简单的条纹锁定,例如:

/**
 * Striped locks holder, contains array of {@link java.util.concurrent.locks.ReentrantLock}, on which lock/unlock
 * operations are performed. Purpose of this is to decrease lock contention.
 * <p>When client requests lock, it gives an integer argument, from which target lock is derived as follows:
 * index of lock in array equals to <code>id & (locks.length - 1)</code>.
 * Since <code>locks.length</code> is the power of 2, <code>locks.length - 1</code> is string of '1' bits,
 * and this means that all lower bits of argument are taken into account.
 * <p>Number of locks it can hold is bounded: it can be from set {2, 4, 8, 16, 32, 64}.
  */
public class StripedLock {
    private final ReentrantLock[] locks;

    /**
     * Default ctor, creates 16 locks
     */
    public StripedLock() {
        this(4);
    }

    /**
     * Creates array of locks, size of array may be any from set {2, 4, 8, 16, 32, 64} 
     * @param storagePower size of array will be equal to <code>Math.pow(2, storagePower)</code>
     */
    public StripedLock(int storagePower) {
        if (!(storagePower >= 1 && storagePower <= 6)) { throw new IllegalArgumentException("storage power must be in [1..6]"); }

        int lockSize = (int) Math.pow(2, storagePower);
        locks = new ReentrantLock[lockSize];
        for (int i = 0; i < locks.length; i++)
            locks[i] = new ReentrantLock();
    }

    /**
     * Locks lock associated with given id.
     * @param id value, from which lock is derived
     */
    public void lock(int id) {
        getLock(id).lock();
    }

    /**
     * Unlocks lock associated with given id.
     * @param id value, from which lock is derived 
     */
    public void unlock(int id) {
        getLock(id).unlock();
    }

    /**
     * Map function between integer and lock from locks array
     * @param id argument
     * @return lock which is result of function 
     */
    private ReentrantLock getLock(int id) {
        return locks[id & (locks.length - 1)];
    }
}

答案 2
public class KeyLock<K> {

    private /*static*/ final Set<K> lockedKeys = new HashSet<>();

    public /*static*/ void lock(K key) throws InterruptedException {
        synchronized (lockedKeys) {
            while (!lockedKeys.add(key)) {
                lockedKeys.wait();
            }
        }
    }

    public /*static*/ void unlock(K key) {
        synchronized (lockedKeys) {
            lockedKeys.remove(key);
            lockedKeys.notifyAll();
        }
    }

}

class Test {
    private KeyLock<String> keyLock = new KeyLock<>();

    public void doSynchronouslyOnlyForEqualKeys(String key) throws InterruptedException {
        try {
            keyLock.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 {
            keyLock.unlock(key);
        }
    }
}
  • K 必须正确覆盖方法“等于”“哈希码”。
  • try-final-非常重要-您必须保证在操作后解锁等待的线程,即使您的操作抛出了异常。
  • 如果您的后端分布在多个服务器/JVM 上,则此方法将不起作用。

推荐