美文网首页Java并发编程实战-可爱猪猪解读
【连载】第2章-2.4用锁来保护状态(Vector不一定线程安全

【连载】第2章-2.4用锁来保护状态(Vector不一定线程安全

作者: 可爱猪猪 | 来源:发表于2019-08-22 22:36 被阅读0次

格言:在程序猿界混出点名堂!

《JAVA并发编程实战》解读
【连载】第2章-2.4用锁来保护状态

回顾:上一节主要介绍synchronized关键字和重入锁,这一节分享的是用锁来保护状态。

由于锁能使其保护的代码路径以串行形式访问,因此通过锁来构造一些协议以实现对共享状态的独占访问。

上边是原文的一段话,今天主要谈谈以下几点内容:

协议

题外彩蛋:协议可理解对加锁范围的一种约定,也是对复合操作的一种束缚。

其实这种约定主要约定了哪些操作为一个原子操作,原子操作的范围是什么?比如上一节的因数分解的例子:

public synchronized void service(ServletRequest req, ServletResponse resp){
  BigInteger i = extractFromRequest(req);
  if(i.equals(lastNumber.get())){
    // 如果缓存过,直接返回结果
     encodeIntoResponse(resp, lastFactors.get());
  }else{
   // 没有缓存才计算
    BigInteger[] factors = factor(i);
    lastNumer.set(i);
    lastFactors.set(factors);
    encodeIntoResponse(resp, lastFactors.get());
  }

这种约束就约束到整个Servlet的访问范围。但同时我可以把更新last操作作为原子范围作为一种协议:

public void service(ServletRequest req, ServletResponse resp){
  BigInteger i = extractFromRequest(req);
  if(i.equals(lastNumber.get())){
    // 如果缓存过,直接返回结果
     encodeIntoResponse(resp, lastFactors.get());
  }else{
   // 没有缓存才计算
    BigInteger[] factors = factor(i);
    synchronized(this){
        lastNumer.set(i);
        lastFactors.set(factors);
    }
    encodeIntoResponse(resp, lastFactors.get());
  }

你或许认为上面的代码比第一种写法效率更高,但是第二种写法仍然存在问题,这就是我所说的第二点。

读写同锁保证原子性

上面的代码我们只在写的时候加锁,保证写的原子性,试想如果一个线程set了lastNumer,但没有set lastFactors,另外一个线程可能返回错误的值。
因此,读写同锁保证原子性。
但上述代码在业务代码中,我们可能会考虑进行简单封装,然后调用:

public synchronized void setLast(BigInteger ,BigInteger[] factors){
...
}

public synchronized BigInteger[] getLast(BigInteger i){
  return ...;
}

可能细心的小伙伴已经看出来了,好像一个map的使用,这就是我们要讲的第三点:

对象内部封装可变状态

一种常见的加锁约定是,将所有的可变状态封装在对象的内部。
比如我们知道的Collection下面的实现,Vector或者HashTable等。上面的代码我们进行改造试试看,小编记事本编写,有问题欢迎评论区留言:

@ThreadSafe
public  class SafeCachingFactorizer implements Servlet{
// 上一次计算的数字
private final Map<BigInteger,BigInteger[]> last = new HashTable<BigInteger,BigInteger[]>();
public void service(ServletRequest req, ServletResponse resp){
  BigInteger i = extractFromRequest(req);
  BigInteger[] factors;
  if((factors= last.get(i))!=null){
    // 如果缓存过,直接返回结果
     encodeIntoResponse(resp, factors);
  }else{
   // 没有缓存才计算
    factors = factor(i);
    last.put(i,factors);
    encodeIntoResponse(resp, factors);
  }
}
}

为什么不在所有方法都加上synchronized呢?

  • 如果所有方法都可以加,我们今天就没有机会在这里研究和讨论这个话题了😄
  • 性能考虑,如果所有方法都是同步,就造成该对象所有方法串行执行。
  • 活跃性问题,之前章节讲过活跃性问题,会造成死锁等。后面章节会单独解说死锁。

Vector一定不存在竞态条件?

答案:未必,据协议约定的原子范畴而定。

if(!vector.contains(element)){
  vector.add(element);
}

contains和add本身是原子的,但这个复合操作就是非原子的。

喜欢连载可关注简书或者微信公众号
简书专题:Java并发编程实战-可爱猪猪解读
https://www.jianshu.com/c/ac717321a386
微信公众号:可爱猪猪聊程序

相关文章

网友评论

    本文标题:【连载】第2章-2.4用锁来保护状态(Vector不一定线程安全

    本文链接:https://www.haomeiwen.com/subject/pitasctx.html