【连载】第2章-2.2原子性

作者: 可爱猪猪 | 来源:发表于2019-08-20 23:01 被阅读1次

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

《JAVA并发编程实战》解读
【连载】第2章-2.2原子性

回顾:上一节主要介绍什么是线程安全性,这一节讲的是大家经常听到的原子性。
比如Atomic包、事务的特征ACID,其中的A指的就是原子性。

原子性

先看一下下面线程不安全的例子:

  @NotThreadSafe
 public class UnsafeCountingFactorizer implements Servlet{
   // 线程共享
   private long count = 0;
   public long getCount(){
      return count;
   }
   public void service(ServletRequest req, ServletResponse resp){
     BigInteger I = extractFromRequest(req);
     BigInteger[] factors = factor(i);
     count++;
     encodeIntoResponse(resp,factors);
    }
 }

第1章中我们有提到过i++,是三步完成的非原子操作,如果这个servlet是多线程同时访问,可能丢失计数。

竞态条件

根据上面的例子,引申这个定义:
竞态条件:由于不恰当的执行时序,而出现不正确的结果。

第1章举过这个例子:A线程获取i=0,B线程获取i=0,A线程修改i=1,B线程修改i=1,A线程写入i=1,B线程写入i=1,导致最终结果出错,这个就是竞态条件。

  • 延迟初始化的竞态条件

最常见的竞态条件的类型就是“先检查后执行

延迟初始化导致竞态条件的例子,非线程安全的单例

@NotThreadSafe
public class LazyInitRace{
  private ExpensiveObject instance = null;
  public ExpensiveObject getInstance(){
    if(instance == null)
        instance = new ExpensiveObject();

    return instance; 
 }
}

上面的例子包含了一个竞态条件,这里例子存在的问题我就不解释,大家天天在使用的单例设计模式。

  • 复合操作
    为了避免竞态条件,我们需要将复合操作原子化,即将“先检查后修改”或者“获取-修改-写入”这样的操作封装具有原子性原子性,看一下开篇提到的非线程安全的示例如何进行复核操作原子性:
  @ThreadSafe
 public class UnsafeCountingFactorizer implements Servlet{
   // 线程共享
   private AtomicLong count = new AtomicLong(0);
   public long getCount(){
      return count.get();
   }
   public void service(ServletRequest req, ServletResponse resp){
     BigInteger I = extractFromRequest(req);
     BigInteger[] factors = factor(i);
     count.incrementAndGet();
     encodeIntoResponse(resp,factors);
    }
 }

AtomicLong的incrementAndGet这个操作为原子操作,采用volatile+CAS的方式来更新保证共享变量的原子性。

知识点

  1. 理解原子性竞态条件复合操作

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

相关文章

网友评论

    本文标题:【连载】第2章-2.2原子性

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