Java 设计基于密钥的锁(或锁映射)
我正试图设计一个基于密钥的锁定工具:类似于普通的可重入锁,但不是lock()和unlock(),而是锁定(key)和解锁(key),如果key.equals(key1),没有人能够同时锁定(key1) 这个代码行吗?有更有效的解决方案吗?我特别不喜欢while循环,因为它试图在地图中设置锁Java 设计基于密钥的锁(或锁映射),java,concurrency,locking,Java,Concurrency,Locking,我正试图设计一个基于密钥的锁定工具:类似于普通的可重入锁,但不是lock()和unlock(),而是锁定(key)和解锁(key),如果key.equals(key1),没有人能够同时锁定(key1) 这个代码行吗?有更有效的解决方案吗?我特别不喜欢while循环,因为它试图在地图中设置锁 package luca; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap;
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();
}
}
package-luca;
导入java.util.concurrent.ConcurrentHashMap;
导入java.util.concurrent.ConcurrentMap;
导入java.util.concurrent.locks.ReentrantLock;
公共类KeyedReentrantLock{
私有ConcurrentMap lockMap=新ConcurrentHashMap();
公共无效锁(K钥匙){
ReentrantLock oldLock=lockMap.get(key);
if(oldLock!=null&&oldLock.isHeldByCurrentThread()){
//增加锁计数并返回。
lock();
返回;
}
ReentrantLock newLock=新的ReentrantLock();
newLock.lock();
while((oldLock=lockMap.putIfAbsent(key,newLock))!=null){
//等待旧锁释放;
lock();
oldLock.unlock();
}
返回;
}
公共无效解锁(K密钥){
ReentrantLock lock=lockMap.get(key);
如果(lock==null)抛出新的IllegalMonitorStateException(“此密钥没有锁!”);
if(lock.getHoldCount()==1){
锁图。移除(钥匙);
}
lock.unlock();
}
}
为什么不使用简单的条带锁定,比如:
/**
* 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)];
}
}
请参考下面的示例代码,我已经为每个线程创建了一个新锁
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
public class Processor implements Runnable {
final static ConcurrentHashMap<Integer, ReentrantReadWriteLock> CONCURRENT_HASH_MAP = new ConcurrentHashMap<Integer, ReentrantReadWriteLock>();
final private Employee employee;
public Processor(int id) {
this.employee = new Employee(id);
}
public void run() {
processDate(employee);
}
/**
* Method to be shared
*
* @param id
*/
public void processDate(final Employee employee) {
final int employeeId = employee.getId();
ReentrantReadWriteLock monitoredObject = new ReentrantReadWriteLock();
System.out.println("Before taking the lock"
+ Thread.currentThread().getName());
while (CONCURRENT_HASH_MAP.putIfAbsent(employeeId, monitoredObject) != null) {
}
ReadLock lock = monitoredObject.readLock();
lock.lock();
try {
processXML(employee);
} catch (Exception e) {
e.printStackTrace();
}
CONCURRENT_HASH_MAP.remove(employeeId);
lock.unlock();
}
/**
* For similar id object this will run one by one but for different objects
* this will run parallal.
*
* This method will execute serially if called by multiple threads for
* employee with same emp id
*
* @throws Exception
*/
public void processXML(final Employee employee) throws Exception {
System.out.println("Process XML for " + employee.getId()
+ Thread.currentThread().getName());
Thread.sleep(2000);
System.out.println("Done XML Processing for " + employee.getId()
+ Thread.currentThread().getName());
ReentrantReadWriteLock lock = CONCURRENT_HASH_MAP.get(employee.getId());
System.out.println("lock object " + lock + "queue length "
+ lock.getQueueLength());
}
class Employee {
private Integer id;
public Employee(final int id) {
this.id = id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
}
public static void main(String[] args) {
final ExecutorService executorService = Executors.newFixedThreadPool(10);
long startTime = System.currentTimeMillis();
/**
* In Processors Constructor instead of i write 1 and see the
* difference.
*/
for (int i = 1; i <= 5; i++)
executorService.submit(new Processor(i));
executorService.shutdown();
/*
* Let the main thread wait till the executor service is terminated to
* observe the total time taken
*/
while (executorService.isTerminated() != true) {
}
long endTime = System.currentTimeMillis();
long timeTaken = endTime - startTime;
System.out.println("time taken.... " + timeTaken + " ms");
}
}
import java.util.concurrent.ConcurrentHashMap;
导入java.util.concurrent.ExecutorService;
导入java.util.concurrent.Executors;
导入java.util.concurrent.locks.ReentrantReadWriteLock;
导入java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
公共类处理器实现可运行{
最终静态ConcurrentHashMap CONCURRENT_HASH_MAP=新ConcurrentHashMap();
最终私人雇员;
公共处理器(int id){
this.employee=新员工(id);
}
公开募捐{
处理日期(员工);
}
/**
*要共享的方法
*
*@param-id
*/
公共作废处理日期(最终员工){
final int employeeId=employee.getId();
ReentrantReadWriteLock monitoredObject=新建ReentrantReadWriteLock();
System.out.println(“取锁前”
+Thread.currentThread().getName());
while(CONCURRENT_HASH_MAP.putIfAbsent(employeeId,monitoredObject)!=null){
}
ReadLock lock=monitoredObject.ReadLock();
lock.lock();
试一试{
processXML(员工);
}捕获(例外e){
e、 printStackTrace();
}
并发散列映射移除(employeeId);
lock.unlock();
}
/**
*对于类似的id对象,这将逐个运行,但针对不同的对象
*这将运行视差。
*
*如果被多个线程调用,此方法将串行执行
*具有相同emp id的员工
*
*@抛出异常
*/
public void processXML(最终员工)引发异常{
System.out.println(“处理”+employee.getId()的XML)
+Thread.currentThread().getName());
《睡眠》(2000年);
System.out.println(“为”+employee.getId()完成XML处理)
+Thread.currentThread().getName());
ReentrantReadWriteLock=CONCURRENT_HASH_MAP.get(employee.getId());
System.out.println(“锁对象”+锁+“队列长度”
+lock.getQueueLength());
}
班级员工{
私有整数id;
公共雇员(最终内部id){
this.id=id;
}
公共无效集合id(整数id){
this.id=id;
}
公共整数getId(){
返回id;
}
}
公共静态void main(字符串[]args){
final ExecutorService ExecutorService=Executors.newFixedThreadPool(10);
long startTime=System.currentTimeMillis();
/**
*在Processors构造函数中,而不是我写1,并看到
*差别。
*/
对于(int i=1;i称之为DynamicKeyLock。它是任何对象作为键的单个进程锁(
等于+
hashcode`以获得唯一性)
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();
}
}
公共类DynamicKeyLock实现锁
{
私有最终静态ConcurrentHashMap locksMap=新ConcurrentHashMap();
私钥;
公共动态锁(T锁钥匙)
{
this.key=lockKey;
}
专用静态类锁和计数器
{
private final Lock=new ReentrantLock();
私有最终AtomicInteger计数器=新的AtomicInteger(0);
}
私有锁和计数器getLock()
{
返回锁映射计算(键,(键,锁和计数器内部)->
{
if(lockAndCounterInner==null){
lockAndCounterInner=新的LockAndCounter();
}
lockAndCounterInner.counter.incrementAndGet();
返回锁和计数器内部;
});
}
专用真空清洁锁(锁和柜锁和柜外)
{
if(lockAndCounterOuter.counter.decrementAndGet()==0)
{
锁映射计算(键,(键,锁和计数器内部)->
{
if(lockAndCounterInner==null | | lockAndCounterInner.counter.get()==0){
返回null;
}
返回锁和计数器内部;
});
}
}
@凌驾
公共无效锁()
{
LockAndCounter LockAndCounter=getLock();
lock和counter.lock.lock();
}
@凌驾
公开作废解锁()
{
锁和计数器锁和计数器
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 ProcessLockTest
{
@Test
public void testDifferentKeysDontLock() throws InterruptedException
{
ProcessLock<Object> lock = new ProcessLock<>(new Object());
lock.lock();
AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false);
try
{
new Thread(() ->
{
ProcessLock<Object> anotherLock = new ProcessLock<>(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();
ProcessLock<Object> lock = new ProcessLock<>(key);
lock.lock();
AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false);
try
{
new Thread(() ->
{
ProcessLock<Object> anotherLock = new ProcessLock<>(key);
anotherLock.lock();
try
{
anotherThreadWasExecuted.set(true);
}
finally
{
anotherLock.unlock();
}
}).start();
Thread.sleep(100);
}
finally
{
Assert.assertFalse(anotherThreadWasExecuted.get());
lock.unlock();
}
}
}
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);
}
}
}