我有一个多线程Java应用程序,它会在动态生成的路径(数量很大-超过10万)中附加到各种文件。我想防止并发写入。因为这是JVM中的争用,所以我不能使用FileLock

相反,我一直在尝试按以下方式对Path对象进行同步(PathLocker是单例)。

 public class PathLocker {
    private final ConcurrentMap<Path, ReentrantLock> pathLockMap = new ConcurrentHashMap<>();

    public void lock(Path path) {
        pathLockMap.computeIfAbsent(path, p -> new ReentrantLock()).lock();
    }

    public void unlock(Path path) {
        ReentrantLock reentrantLock = pathLockMap.get(path);
        if (!reentrantLock.hasQueuedThreads()) { // NPE OCCURS HERE
            pathLockMap.remove(path);
        }
        reentrantLock.unlock();
    }
}

唯一的客户端代码如下所示:
Path path = findPath(directory, dataType, bucketEnd, referenceId);
pathLocker.lock(path);
try {
    try (FileWriter fileWriter = new FileWriter(path.toFile(), true)) {
        fileWriter.write(string);
    }
} finally {
    pathLocker.unlock(path);
}

但是,此代码在reentrantLock中取消引用PathLocker::unlock时,很快会抛出一个空指针。

我不知道这种NPE会如何发生。显然,同时有其他线程删除了该值,但是-据我了解-唯一可能删除该锁的线程是那些已排队并首先等待该锁的线程。我想念什么?

最佳答案

在线程1中调用computeIfAbsent函数(并返回0)在线程1中的lockhasQueuedThreads函数之间存在一个小的可能性,当线程2完成其工作并试图解锁时,NPE就会在线程2中发生。

如果我的假设是正确的,则应在unlock方法中设置双重障碍。

public void unlock(Path path) {
    ReentrantLock reentrantLock = pathLockMap.get(path);
    if (!reentrantLock.hasQueuedThreads()) { // NPE OCCURS HERE
        pathLockMap.remove(path);
        if (reentrantLock.hasQueuedThreads()) {
            pathLockMap.put(path, reentrantLock);
        }
    }
    reentrantLock.unlock();
}

10-08 16:30