我需要实现对多个资源的线程安全同步,其中每个资源可以由一个线程一次访问,但不同的资源可以并发访问。我编写了下面的代码,用于try-with-resources语句。
public class Gatekeeper implements AutoCloseable
{
private static final ConcurrentMap<Long, ReentrantLock> lockMap = new ConcurrentHashMap<>();
private final ReentrantLock lock;
private final Long key;
public Gatekeeper(Long key)
{
this.key = key;
lock = lockMap.computeIfAbsent(key, (Long absentKey) -> new ReentrantLock(true)); // computeIfAbsent is an atomic operation
try
{
lock.tryLock(30, TimeUnit.SECONDS);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
throw new Something(":(", e);
}
}
@Override
public void close()
{
if(lock.isHeldByCurrentThread())
{
lock.unlock();
}
}
}
这段代码的一个问题是没有项从lockMap中删除,我不知道如何做到线程安全。下面的代码肯定不是线程安全的:
@Override
public void close()
{
if (lock.isHeldByCurrentThread())
{
if (lock.getQueueLength() == 1) // todo: getQueueLength is meant for system monitoring purposes only
{
lockMap.remove(key); // todo: not thread-safe, queuelength could have changed by now
}
lock.unlock();
}
}
getQueueLength:
返回等待的线程数的估计值获取此锁。这个值只是一个估计值,因为当此方法遍历时,线程可能会动态变化内部数据结构。这种方法是为……设计的监视系统状态,而不是用于同步控制。
有谁知道这个问题的解决方案吗?是否有不同的策略来实现我的目标?
经过更多的实验,我想出了下面的代码,谁能评论这是否是一个好方法,代码是正确的?
public class Gatekeeper implements AutoCloseable
{
private static final ConcurrentMap<Long, ReentrantLock> lockMap = new ConcurrentHashMap<>();
private final ReentrantLock lock;
private final Long key;
private static final ConcurrentMap<Long, Integer> claimsPerLock = new ConcurrentHashMap<>();
private static final Object mutex = new Object();
public Gatekeeper(Long key)
{
this.key = key;
synchronized (mutex)
{
lock = lockMap.computeIfAbsent(key, (Long absentKey) -> new ReentrantLock(true));
claimsPerLock.compute(key, (k, oldValue) -> oldValue == null ? 1 : ++oldValue);
}
try
{
if(!lock.tryLock(30, TimeUnit.SECONDS))
{
throw new SomeException("Timeout occurred while trying to acquire lock");
}
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
throw new SomeException("Interrupted", e);
}
}
@Override
public void close()
{
lock.unlock();
synchronized (mutex)
{
claimsPerLock.compute(key, (k, oldValue) -> oldValue == null ? 0 : --oldValue);
if (claimsPerLock.get(key) <= 0)
{
lockMap.remove(key);
claimsPerLock.remove(key);
}
}
}
}