在并发环境中替换双重检查锁定

时间:2017-01-26 13:49:59

标签: java multithreading double-checked-locking

我已经topic with same code

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);
}

之前我证明代码不是线程安全的。

在这个主题上,我想提供我脑子里的解决方案,并请你回顾这些解决方案:

通过ReadWriteLock

解决方案#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);
}

解决方案#2 通过CHM

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 个答案:

答案 0 :(得分:0)

与我们在上一个问题中遇到的群集不同,这是更好的。

正如前面问题duplicate中所示,原始代码不是线程安全的,因为HashMap不是线程安全的,而get()可以调用put()正在synchronized块中执行。这可以打破各种各样的事情,所以这绝对不是线程安全的。

第二个解决方案是线程安全的,因为对cache的所有访问都是在保护代码中完成的。初始get()受读锁保护,put()在writelock内部完成,保证线程在写入时无法读取缓存,但是可以自由地读取它与其他阅读线程同时进行。没有并发问题,没有可见性问题,没有死锁机会。一切都很好。

最后一个当然是最优雅的一个。由于computeIfAbsent()是一个原子操作,它保证从javadoc直接返回或计算一次值:

  

如果指定的键尚未与值关联,请尝试   使用给定的映射函数计算其值并输入它   进入此地图,除非null。整个方法调用是   以原子方式执行,因此每个键最多应用一次该函数。   其他线程可能会对此映射进行一些尝试的更新操作   计算正在进行时被阻止,因此计算应该是   简短,并且不得尝试更新任何其他映射   这张地图。

有问题的Map不应该是volatile,而应该是final。如果它不是最终的,它可以(至少在理论上)被改变,并且2个线程可以在不同的对象上工作,这不是你想要的。