【Java并发】JAVA并发编程实战-读书笔记8

为计算结果建立高效、可伸缩的高速缓存

public interface Computable<A,V>{
  V compute(A arg)throws InterruptedException;
}

public class ExpensiveFunction implements Computable<String,BigInteger>{
  public BigInteger compute(String arg){
    // after deep thought
    return new BigInteger(arg);
  }
}

public class Memoizer1<A,V> implements Computable<A,V>{
  private final Map<A,V> cache=new HashMap<A,V>();
  private final Computable<A,V> c;

  public Memoizer1(Computable<A,V> c){
    this.c=c;
  }

  public synchronized V compute(A arg)throws InterruptedException{
    V result=cache.get(arg);
    if(result==null){
      result=c.compute(arg);
      cache.put(arg,result);
    }
    return result;
  }
}

下面是一种改进。

public class Memoizer2<A,V> implements Computable<A,V>{
  private final Map<A,V> cache=new ConcurrentHashMap<A,V>();
  private final Computable<A,V> c;

  public Memoizer2(Computable<A,V> c){
    this.c=c;
  }

  public V comput(A arg)throws InterruptedException{
    V result=cache.get(arg);
    if(result==null){
      result=c.compute(arg);
      cache.put(arg,result);
    }
    return result;
  }
}

但是上面的代码依然存在缺陷,当两个线程同时调用compute 时,会造成他们计算相同的值。

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();//调用c.compute发生在这里
    }

    try{
      return f.get();
    }catch(ExecutionException e){
      throw launderThrowable(e.getCause());
    }
  }
}

上面的例子近乎完美,只存在一个缺陷——两个线程同时计算相同的值,但是远没有 Memoizer2 的严重,仅仅因为 compute 中的 if 代码块是非原子的检查再运行。

缓存一个Future而不是一个值会带来缓存污染的可能性:如果一个计算被取消或者失败,未来尝试对这个值计算都会失败,所以如果计算被取消,就会把Future从缓存中移除。发现异常的时候也会移除。缓存过期的问题可以通过FutureTask的一个子类来完成,他会为每一个结果关联一个过期时间,并周期性地扫描缓存中过期的访问。

下面是Memoizer的最终实现。

public class Memoizer<A,V> implements Computable<A,V>{
  private final ConcurrentMap<A,Future<V>> cache = new ConcurrentMap<A,Future<V>>();
  private final Computable<A,V> c;

  public Memoizer(Computable<A,V> c){
    this.c=c;
  }

  public V compute(final A arg)throws InterruptedException{
    while(true){
      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=cache.putIfAbsent(arg,ft);
        if(f==null){
          f=ft;
          ft.run();
        }
      }

      try{
        return f.get();
      }catch(CancellationException e){
        cache.remove(arg,f);
      }catch(ExecutionException e){
        throw launderThrowable(e.getCause());
      }
    }
  }
}

使用上面的代码分解因式

public class Factorizer implements Servlet{

  private final Computable<BigInteger,BigInteger[]> c = new Computable<BigInteger,BigInteger[]>(){

  public BigInteger[] compute(BigInteger arg){
    return factor(arg);
  }

};

  private final Computable<BigInteger,BigInteger[]> cache = new Memoizer<BigInteger,BigInteger[]>(c);

  public void service(ServletRequest req,ServletResponse resp){
    try{
      BigInteger i=extractFromRequest(req);
      encodeIntoResponse(resp,cache.compute(i));
    }catch(InterruptedException e){
      encodeError(resp,”factorization interrupted”);
    }
  }
}

下面我们总结一下

可变状态,所有并发问题都归结为如何协调访问并发状态,可变状态越少,保证线程安全就越容易。

尽量将域声明为final类型,除非他们需要是可变的。

不可变的对象是线程安全的。

封装使得管理复杂度变得更可行。

用锁来守护每一个可变变量。

对同一不变约束中的所有变量都使用相同的锁。

在运行负荷操作期间持有锁。

在非同步的多线程情况下,访问可变变量的程序是存在隐患的。

不要依赖于可以需要同步的小聪明。

在设计过程中就考虑线程安全,或者在文档中明确地说明他不是线程安全的。

文档化你的同步策略。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值