为什么可以';在实践中,清单5.18中的Java并发是否可以通过一个锁以原子方式完成?
在Java并发实践中,在第106页,它说“为什么可以';在实践中,清单5.18中的Java并发是否可以通过一个锁以原子方式完成?,java,concurrency,Java,Concurrency,在Java并发实践中,在第106页,它说“Memoizer3容易出现问题[两个线程看到null并开始昂贵的计算],因为在支持映射上执行了一个复合操作(如果不存在,则执行put),而不能使用锁定使其成为原子。”我不明白为什么他们说不能用锁来实现原子化。以下是原始代码: package net.jcip.examples; import java.util.*; import java.util.concurrent.*; /** * Memoizer3 * <p/> * Me
Memoizer3
容易出现问题[两个线程看到null并开始昂贵的计算],因为在支持映射上执行了一个复合操作(如果不存在,则执行put),而不能使用锁定使其成为原子。”我不明白为什么他们说不能用锁来实现原子化。以下是原始代码:
package net.jcip.examples;
import java.util.*;
import java.util.concurrent.*;
/**
* Memoizer3
* <p/>
* Memoizing wrapper using FutureTask
*
* @author Brian Goetz and Tim Peierls
*/
public class Memoizer3 <A, V> implements Computable<A, V> {
private final Map<A, Future<V>> cache
= new ConcurrentHashMap<A, Future<V>>();
private final Computable<A, V> c;
public Memoizer3(Computable<A, V> c) {
this.c = c;
}
public V compute(final A arg) throws InterruptedException {
Future<V> f = cache.get(arg);
if (f == null) {
Callable<V> eval = new Callable<V>() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(eval);
f = ft;
cache.put(arg, ft);
ft.run(); // call to c.compute happens here
}
try {
return f.get();
} catch (ExecutionException e) {
throw LaunderThrowable.launderThrowable(e.getCause());
}
}
}
package net.jcip.examples;
导入java.util.*;
导入java.util.concurrent.*;
/**
*备忘录3
*
*使用FutureTask记忆包装器
*
*@作者Brian Goetz和Tim Peierls
*/
公共类Memoizer3实现可计算{
私有最终映射缓存
=新的ConcurrentHashMap();
私有最终可计算c;
公共备忘录3(可计算c){
这个.c=c;
}
public V compute(最后一个参数)抛出InterruptedException{
Future f=cache.get(arg);
如果(f==null){
Callable eval=新的Callable(){
public V call()抛出InterruptedException{
返回c.compute(arg);
}
};
FutureTask ft=新的FutureTask(eval);
f=英尺;
cache.put(arg,ft);
ft.run();//对c.compute的调用发生在这里
}
试一试{
返回f.get();
}捕获(执行例外){
抛出laundertowable.laundertowable(e.getCause());
}
}
}
为什么像这样的东西不起作用
...
public V compute(final A arg) throws InterruptedException {
Future<V> f = null;
FutureTask<V> ft = null;
synchronized(this){
f = cache.get(arg);
if (f == null) {
Callable<V> eval = new Callable<V>() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
ft = new FutureTask<V>(eval);
f = ft;
cache.put(arg, ft);
}
}
if (f==ft) ft.run(); // call to c.compute happens here
...
。。。
public V compute(最后一个参数)抛出InterruptedException{
未来f=null;
FutureTask ft=null;
已同步(此){
f=cache.get(arg);
如果(f==null){
Callable eval=新的Callable(){
public V call()抛出InterruptedException{
返回c.compute(arg);
}
};
ft=新的未来任务(eval);
f=英尺;
cache.put(arg,ft);
}
}
如果(f==ft)ft.run();//调用c.compute发生在这里
...
当然可以通过使用锁定使其原子化,想象一下最原始的情况:您的整个函数都有一个全局锁,然后所有函数都是单线程的,因此是线程安全的。我认为它们可能是指其他东西,或者存在普遍的误解
您的代码甚至可以通过使用ConcurrentHashMap的putIfAbsent方法进行改进,如下所示:
public V compute(final A arg) throws InterruptedException {
Future<V> f = cache.get(arg);
if (f == null) {
final Callable<V> eval = new Callable<V>() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
final FutureTask<V> ft = new FutureTask<V>(eval);
final Future<V> previousF = cache.putIfAbsent(arg, ft);
if (previousF == null) {
f = ft;
ft.run();
} else {
f = previousF; // someone else will do the compute
}
}
return f.get();
}
public V compute(最后一个参数)抛出InterruptedException{
Future f=cache.get(arg);
如果(f==null){
最终可调用评估=新可调用(){
public V call()抛出InterruptedException{
返回c.compute(arg);
}
};
最终未来任务ft=新未来任务(评估);
最终未来previousF=cache.putIfAbsent(arg,ft);
如果(上一个f==null){
f=英尺;
ft.run();
}否则{
f=previousF;//其他人将进行计算
}
}
返回f.get();
}
f
最后要么是之前添加的值,要么是初始值,可能需要额外创建一个可调用函数,但不会多次调用计算。是的,作者在本文中给出了一个可行的解决方案。