java在并发环境中取代双重检查锁定
我已经{a1}:
public abstract class Digest {
private Map<String, byte[]> cache = new HashMap<>();
public byte[] digest(String input) {
byte[] result = cache.get(input);
if (result == null) {
synchronized (cache) {
result = cache.get(input);
if (result == null) {
result = doDigest(input);
cache.put(input, result);
}
}
}
return result;
}
protected abstract byte[] doDigest(String input);
}
在上一篇文章中,我已经证明了代码不是线程安全的
在本主题中,我想提供我脑海中的解决方案,并请大家回顾这些解决方案:
解决方案#1通过读写锁:
public abstract class Digest {
private final ReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock readLock = rwl.readLock();
private final Lock writeLock = rwl.writeLock();
private Map<String, byte[]> cache = new HashMap<>(); // I still don't know should I use volatile or not
public byte[] digest(String input) {
byte[] result = null;
readLock.lock();
try {
result = cache.get(input);
} finally {
readLock.unlock();
}
if (result == null) {
writeLock.lock();
try {
result = cache.get(input);
if (result == null) {
result = doDigest(input);
cache.put(input, result);
}
} finally {
writeLock.unlock();
}
}
return result;
}
protected abstract byte[] doDigest(String input);
}
通过CHM解决方案#2
public abstract class Digest {
private Map<String, byte[]> cache = new ConcurrentHashMap<>(); //should be volatile?
public byte[] digest(String input) {
return cache.computeIfAbsent(input, this::doDigest);
}
protected abstract byte[] doDigest(String input);
}
请检查两种解决方案的正确性。这不是什么解决方案更好的问题。我更不理解那个CHM。请检查执行情况
# 1 楼答案
与我们在上一个问题中陷入的混乱不同,这更好
如前面问题的duplicate所示,原始代码不是线程安全的,因为
HashMap
不是线程安全的,当put()
在同步块内执行时,可以调用初始get()
。这会破坏各种东西,所以这绝对不是线程安全的第二种解决方案是线程安全的,因为对
cache
的所有访问都是在保护代码中完成的。initalget()
受readlock保护,而put()
是在writelock内完成的,这保证了线程在写入缓存时不能读取缓存,但可以与其他读取线程同时自由读取缓存。没有并发问题,没有可见性问题,没有死锁的可能性。一切都很好最后一个当然是最优雅的。由于
computeIfAbsent()
是一个原子操作,因此它保证直接返回值,或者从javadoc中最多计算一次:所讨论的
Map
不应该是volatile
,而应该是final
。如果它不是最终的,它可以(至少在理论上)被改变,两个线程可以处理不同的对象,这不是你想要的