为什么可以';在实践中,清单5.18中的Java并发是否可以通过一个锁以原子方式完成?

为什么可以';在实践中,清单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

在Java并发实践中,在第106页,它说“
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
最后要么是之前添加的值,要么是初始值,可能需要额外创建一个可调用函数,但不会多次调用计算。

是的,作者在本文中给出了一个可行的解决方案。