不在不同线程中重新评估昂贵的数据
Not reevaluating expensive data in different threads
我有这样的方法
public Object doSomethingExpensive(String x);
现在,如果我处理此方法,我可以将结果保存在 HashMap 中,例如,它们的键是字符串 x,值是结果对象。
如果该地图中存在数据,我就不必再次处理它。
但现在我几乎同时收到两个请求。
在这种情况下,我想让第二个请求等待,直到第一个请求完成,第二个请求在计算后也可以得到第一个请求的结果,所以我不必计算两次或并行两次。
重点是,我不会用
public synchronized Object doSomethingExpensive(String x);
因为如果 String x 是其他东西,则 Object 是其他东西。
所以我需要在那个字符串 x 上进行一些同步。
但是 synchronized(x) 是不可能的,因为 java 中的字符串文字...
此外,如果没有字符串而是一个对象作为 x,那么我可能会在第二个请求中得到一个与请求 1 相关的具有相同内容的类似对象,但它们各自是一些其他对象。
是的,所以我的问题是,如何解决这个问题,如何防止并行计算字符串 x 的结果两次,如何同步它并将结果缓存在 HashMap 中。
不知道我是否理解你的问题,如果是为了避免重复计算,这本好书(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());
}
}
}
编辑:
来自评论,使用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。
我有这样的方法
public Object doSomethingExpensive(String x);
现在,如果我处理此方法,我可以将结果保存在 HashMap 中,例如,它们的键是字符串 x,值是结果对象。
如果该地图中存在数据,我就不必再次处理它。
但现在我几乎同时收到两个请求。 在这种情况下,我想让第二个请求等待,直到第一个请求完成,第二个请求在计算后也可以得到第一个请求的结果,所以我不必计算两次或并行两次。
重点是,我不会用
public synchronized Object doSomethingExpensive(String x);
因为如果 String x 是其他东西,则 Object 是其他东西。 所以我需要在那个字符串 x 上进行一些同步。
但是 synchronized(x) 是不可能的,因为 java 中的字符串文字...
此外,如果没有字符串而是一个对象作为 x,那么我可能会在第二个请求中得到一个与请求 1 相关的具有相同内容的类似对象,但它们各自是一些其他对象。
是的,所以我的问题是,如何解决这个问题,如何防止并行计算字符串 x 的结果两次,如何同步它并将结果缓存在 HashMap 中。
不知道我是否理解你的问题,如果是为了避免重复计算,这本好书(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());
}
}
}
编辑:
来自评论,使用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。