不在不同线程中重新评估昂贵的数据
Posted
技术标签:
【中文标题】不在不同线程中重新评估昂贵的数据【英文标题】:Not reevaluating expensive data in different threads 【发布时间】:2021-12-23 13:48:57 【问题描述】:我有这样的方法
public Object doSomethingExpensive(String x);
现在,如果我处理了这个方法,我可以将结果保存在 HashMap 中,例如,它们的键是字符串 x,值是结果对象。
如果该地图中存在数据,我不必再次处理它。
但现在我几乎同时收到两个请求。 在这种情况下,我想让第二个请求等待,直到第一个请求完成,第二个请求也可以在计算后得到第一个请求的结果,所以我不必计算两次或并行两次。
关键是,我不能用
public synchronized Object doSomethingExpensive(String x);
因为如果 String x 是其他东西,Object 就是其他东西。 所以我需要在那个字符串 x 上进行一些同步。
但 synchronized(x) 是不可能的,因为 java 中的字符串文字......
另外,如果没有字符串而是对象作为 x,那么我可能会在第二个请求中获得与请求 1 相关的内容相同的类似对象,但它们每个都是其他一些对象。
是的,所以我的问题是,如何解决这个问题,如何防止并行计算 String x 其结果两次,如何同步它并将结果缓存在 HashMap 中。
【问题讨论】:
【参考方案1】:不知道是否理解你的问题,如果是为了避免重复计算,这本好书(Java Concurrency in Practice)给出了一个解决方案的例子:
private final Map<String, Future<Object>> cache
= new ConcurrentHashMap<String, Future<Object>>();
public Object doSomethingExpensive(String x) throws InterruptedException
while (true)
Future<Object> future = cache.get(x);
if (future == null)
Callable<Object> callable = new Callable<Object>()
@Override
public Object call() throws Exception
// doSomethingExpensive todo
return new Object();
;
FutureTask<Object> futureTask = new FutureTask<>(callable);
future = cache.putIfAbsent(x, futureTask);
if (future == null)
future = futureTask;
futureTask.run();
try
return future.get();
catch (CancellationException e)
cache.remove(x);
catch (ExecutionException e)
throw new RuntimeException(e.getCause());
编辑:
来自cmets,使用JAVA8#ConcurrentHashMap#computeIfAbsent
,真的很方便:
ConcurrentHashMap<String, Object> concurrentHashMap = new ConcurrentHashMap<>();
public Object doSthEx(String key)
return concurrentHashMap.computeIfAbsent(key, new Function<String, Object>()
@Override
public Object apply(String s)
// todo
return new Object();
);
或者使用一些库来获得评论中提到的更全面的功能:https://github.com/ben-manes/caffeine。
【讨论】:
Java 8 的ConcurrentHashMap#computeIfAbsent
方法原生支持这一点。一个缓存库,比如Caffeine,增加了对限制大小的支持。
"在计算过程中,其他线程对该映射的一些尝试更新操作可能会被阻塞,因此计算应该简短而简单。"在computeIfAbsent 上写在javadoc 中。因此,如果我使用它来防止两次为 String X 计算结果,我还可以防止计算此映射中的任何其他 String x2?
@zysaaa 为什么将线程代码(这里作为 FutureTask)放入 doSomethingExpensive/computeIfAbsent 中?......因为有问题的阻塞还有地图中的其他操作,所以你做了同步块的时间很短?
@RobinKreuzer 该文档是因为 ConcurrentHashMap 锁定了 hashbin,这意味着对不同条目的写入可能会延迟。像数据库加载这样的典型成本是可以的,但是非常慢的操作是不合适的。在这种情况下,您将使用未来,但通过插入调用者在映射操作之外运行它,以便您获得每个条目锁定并且不使用额外的线程。这些在 Caffeine 的 faq 中进行了讨论。
@RobinKreuzer 未来是一个方便的持有者对象,它包含一个内部锁。除了更好、内置并且具有众所周知的意图之外,它等同于自定义持有人。这两种解决方案(直接计算、未来)的不同之处在于使用每个条目锁定或锁条带化,后者以低冲突率共享锁(映射的初始容量决定锁的数量)。我认为没有更好的解决方案,只是编写等效代码的不同风格。以上是关于不在不同线程中重新评估昂贵的数据的主要内容,如果未能解决你的问题,请参考以下文章
在python中恢复具有不同输入的进程或重新启动它是否更好?