Java 在ConcurrentHashmap中缓存值以避免数据库读取

Java 在ConcurrentHashmap中缓存值以避免数据库读取,java,multithreading,Java,Multithreading,下面的代码是否正确地将映射用作简单的线程安全缓存以避免从数据库读取?我只是想知道下面代码的正确性,而不是建议使用frameworkx public class Foo { private static final Map<String, String> CACHE = new ConcurrentHashMap<>(); public void doWork(String key) { String value = CACHE.get(k

下面的代码是否正确地将映射用作简单的线程安全缓存以避免从数据库读取?我只是想知道下面代码的正确性,而不是建议使用frameworkx

public class Foo {
    private static final Map<String, String> CACHE = new ConcurrentHashMap<>();

    public void doWork(String key) {
        String value = CACHE.get(key);
        if (value == null) {
            synchronized (CACHE) {
                value = CACHE.get(key);
                if (value == null) {
                    value = database.getValue();
                    CACHE.put(key, value);
                }
            }
        }
        // do work with value
    }
}
公共类Foo{
私有静态最终映射缓存=新ConcurrentHashMap();
公共无效doWork(字符串键){
字符串值=CACHE.get(键);
如果(值==null){
已同步(缓存){
value=CACHE.get(key);
如果(值==null){
value=database.getValue();
CACHE.put(键、值);
}
}
}
//做有价值的工作
}
}
其他问题:

  • 与其在
    synchronized()
    中使用
    缓存
    ,不如在类中使用
    对象锁
    并在其上使用synchronized
  • 缓存使用
    HashMap
    会起作用吗
    CACHE.get(key)
    如果key为null,则会抛出
    NullPointerException
    。阅读:

    与Hashtable类似,但与HashMap不同,该类不允许将null用作键或值

    此外,在地图上同步并再次尝试检索值也没有什么意义。该方法应该返回它无法获取该键的值,仅此而已

    此外,无需通过
    ConcurrentHashMap
    进行同步,因此得名

    创建一个附加方法,如果值不在映射中,则从数据库中检索该值

    我强烈建议使用单元测试来测试您的方法

    CACHE.get(key)
    如果key为null,则会抛出
    NullPointerException
    。阅读:

    与Hashtable类似,但与HashMap不同,该类不允许将null用作键或值

    此外,在地图上同步并再次尝试检索值也没有什么意义。该方法应该返回它无法获取该键的值,仅此而已

    此外,无需通过
    ConcurrentHashMap
    进行同步,因此得名

    创建一个附加方法,如果值不在映射中,则从数据库中检索该值


    我强烈建议使用单元测试来测试您的方法

    以这种方式使用
    ConcurrentHashMap
    有一个相当标准的“模式”(在这种情况下,您不想使用
    同步的
    块或其他锁定机制):

    calculateValueForKey()
    运行速度快且没有任何副作用时,这种方法效果很好——根据时间的不同,可以对同一个键多次调用它。缺点是,如果
    calculateValueForKey()
    需要很长时间,并且是I/O绑定的(在您的情况下),那么可能会有多个线程同时为同一个键运行
    calculateValueForKey()
    。如果有3个线程为同一个键执行第3行,其中2个线程将在第4行“丢失”,并将其结果丢弃,这不是非常有效。对于这些情况,我会推荐一些类似于(Goetz,B.(2006))中的
    回忆录
    示例的东西,我强烈推荐:

    private static final ConcurrentMap<String, Future<String>> CACHE
            = new ConcurrentHashMap<>();
    
    public void doWork(String key)
    {
        String value;
    
        try {
            value = calculateValueForKey(key);
        } catch (InterruptedException e) {
            // Restore interrupted status and return
            Thread.currentThread.interrupt();
            return;
        }
    
        // do work with value
    }
    
    private String calculateValueForKey(final String key)
             throws InterruptedException
    {
        while (true) {
            Future<String> f = CACHE.get(key);
            if (f == null) {
                FutureTask<String> newCalc = new FutureTask<>(new Callable<String>() {
                    @Override
                    public String call()
                    {
                        return database.getValue(key);
                    }
                )};
    
                f = CACHE.putIfAbsent(key, newCalc);
                if (f == null) {
                    f = newCalc;
                    newCalc.run();
                }
            }
    
            try {
                return f.get();
            } catch (CancellationException e) {
                CACHE.remove(key, f);
            } catch (ExecutionException e) {
                Throwable cause = e.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else if (cause instanceof Error) {
                    throw (Error) cause;
                } else {
                    throw new IllegalStateException("Not unchecked", cause);
                }
            }
        }
    }
    
    私有静态最终ConcurrentMap缓存
    =新的ConcurrentHashMap();
    公共无效doWork(字符串键)
    {
    字符串值;
    试一试{
    值=calculateValueForKey(键);
    }捕捉(中断异常e){
    //恢复中断状态并返回
    Thread.currentThread.interrupt();
    返回;
    }
    //做有价值的工作
    }
    私有字符串calculateValueForKey(最终字符串键)
    抛出中断异常
    {
    while(true){
    Future f=CACHE.get(key);
    如果(f==null){
    FutureTask newCalc=新的FutureTask(new Callable()){
    @凌驾
    公共字符串调用()
    {
    返回database.getValue(key);
    }
    )};
    f=CACHE.putIfAbsent(key,newCalc);
    如果(f==null){
    f=新计算公式;
    newCalc.run();
    }
    }
    试一试{
    返回f.get();
    }捕获(取消异常e){
    缓存。删除(键,f);
    }捕获(执行例外){
    可丢弃的原因=e.getCause();
    if(导致运行时异常的实例){
    抛出(运行时异常)原因;
    }else if(导致instanceof错误){
    抛出(错误)原因;
    }否则{
    抛出新的非法状态异常(“未检查”,原因);
    }
    }
    }
    }
    
    显然,这段代码更复杂,这就是为什么我将它的精华提取到另一个方法中,但它非常强大。您不是将值放入地图,而是将表示该值计算的
    未来
    放入地图。对该future调用
    get()
    将阻塞,直到计算完成。这意味着,如果3个线程同时尝试检索给定键的值,那么当所有3个线程等待相同的结果时,只会运行一次计算。对同一密钥的后续请求将立即返回计算结果

    要回答您的具体问题:

  • 我下面的代码是否正确地将映射用作简单的线程安全缓存,以避免从数据库中读取?我要说不。这里使用的
    同步
    块是不必要的。此外,如果多个线程同时尝试访问尚未在
    映射中的不同键的值,则它们将在各自的数据库查询期间相互阻止,这意味着它们将
    
    private static final ConcurrentMap<String, Future<String>> CACHE
            = new ConcurrentHashMap<>();
    
    public void doWork(String key)
    {
        String value;
    
        try {
            value = calculateValueForKey(key);
        } catch (InterruptedException e) {
            // Restore interrupted status and return
            Thread.currentThread.interrupt();
            return;
        }
    
        // do work with value
    }
    
    private String calculateValueForKey(final String key)
             throws InterruptedException
    {
        while (true) {
            Future<String> f = CACHE.get(key);
            if (f == null) {
                FutureTask<String> newCalc = new FutureTask<>(new Callable<String>() {
                    @Override
                    public String call()
                    {
                        return database.getValue(key);
                    }
                )};
    
                f = CACHE.putIfAbsent(key, newCalc);
                if (f == null) {
                    f = newCalc;
                    newCalc.run();
                }
            }
    
            try {
                return f.get();
            } catch (CancellationException e) {
                CACHE.remove(key, f);
            } catch (ExecutionException e) {
                Throwable cause = e.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else if (cause instanceof Error) {
                    throw (Error) cause;
                } else {
                    throw new IllegalStateException("Not unchecked", cause);
                }
            }
        }
    }