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

标签 java multithreading

我下面的代码是否正确使用 Map 作为简单的线程安全缓存以避免从数据库读取?我只是想知道下面代码的正确性,而不是建议使用 X 框架。

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

其他问题:

  1. 如果我在类中有一个对象锁并在synchronized()中使用CACHE,而不是在synchronized()中使用synchronized,会更好吗?而是那个?
  2. 使用 HashMap 进行 CACHE 可以吗?

最佳答案

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

        String value = CACHE.get(key);
        if (value == null) {
/* 3 */    String newValue = calculateValueForKey(key);
/* 4 */    value = CACHE.putIfAbsent(key, newValue);
           if (value == null) {
               value = newValue;
           }
        }

        /* Work with 'value' */

calculateValueForKey() 运行快速并且没有任何副作用时,此方法效果很好 - 根据时间的不同,可以对同一键多次调用它。缺点是,如果 calculateValueForKey() 需要很长时间并且受 I/O 限制(正如您的情况),您可能有多个线程都在运行 calculateValueForKey() 同时用于相同的 key 。如果有 3 个线程针对同一个键执行第 3 行,则其中 2 个线程将在第 4 行“失败”,并丢弃其结果,这不是很高效。对于这些情况,我会推荐一些类似的东西,这些东西主要是从 Java Concurrency in Practice 中的 Memoizer 示例中提取的。 (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);
            }
        }
    }
}

显然这段代码更复杂,这就是为什么我将其核心内容提取到另一个方法中,但它非常强大。您不是将值放入映射中,而是将表示该值的计算Future 放入映射中。在该 future 上调用 get() 将会阻塞,直到计算完成。这意味着,如果 3 个线程同时尝试检索给定键的值,则当所有 3 个线程等待相同结果时,只会运行一次计算。对同一 key 的后续请求将立即返回计算结果。

回答您的具体问题:

  1. 我下面的代码使用 Map 作为简单的线程安全缓存以避免从数据库读取数据是否正确?我会说不。您在这里使用 synchronized block 是不必要的。此外,如果多个线程同时尝试访问 Map 中尚未存在的不同键的值,它们将在各自的数据库查询期间相互阻塞,这意味着它们将串行运行而不是顺序运行。平行。
  2. 如果我在类中使用对象锁并对其使用同步,而不是在 synchronized() 中使用 CACHE,会更好吗? 不。当您想要读/写多个可变字段并且不希望类的使用者能够“从外部”影响对象的同步语义时,您通常会使用代理对象进行同步。 ”
  3. 使用HashMap作为CACHE可以吗?我猜你可以吗?但是,您需要调整同步策略,以便在读取 Map 时,CACHE(或代理锁对象)始终同步或写信给.我不确定如果有更好的选择,您为什么要这样做。

关于java - 在 ConcurrentHashmap 中缓存值以避免数据库读取,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38614178/

相关文章:

java - 需要 N Queens 程序的帮助(检查对角线)

java - Spring Data LDAP 使用 Pageable 可能吗?

java - 原始查询无法将 BLOB 转换为字符串以选择 String[] 的列

java - 推荐的框架、库和概念

C++多线程成员变量

multithreading - Xamarin 使用 async./wait VS await Task.Run(在 Xamarin 中)

java - 无法将 java.util.concurrent.ForkJoinWorkerThread 转换为 java.lang.Thread- 复杂结构

java - 外部程序关闭时的条件

c++ - 如何使这段代码线程安全?编号++;打印值(ID);

线程中的 Java 指令重新排序/缓存