2020-02-01 2.1 原子量工具包(java.util.concurrent.atomic)

本文是Java线程安全和并发编程知识总结的一部分。

2.1 原子量工具包(java.util.concurrent.atomic

原子量提供如下便利:

  1. 该包提供的原子类,可以封装单个状态变量,并提供了一系列简单常用的原子操作。这些操作的支持直接来自处理器对原子量的支持,因此效率非常高,其性能并不比volatile关键词差。
  2. 最关键的,原子类实际上是利用CPU对原子量的支持,基于非阻塞同步机制实现的,在原子量提供的方法可以满足需求的情况下,可以取代锁,且性能高于锁机制。如果对应CPU架构不支持原子量,则JVM会采用锁机制来模拟实现。

原子量的缺点在于:

  1. 原子量依赖CPU的支持,目前能提供的基本就是如下几类复合操作:
  • 测试并设置
  • 获取并递增
  • 比较并交换
  • 关联加载
  • 条件存储
  1. 原子量是JDK5.0以后引入的,需要5.0以上版本支持;不过这个对现在的开发者而言,应当不存在问题。

先列举一个线程不安全的场景:

/**
 * @author xx
 * 2020年1月31日 下午4:08:45
 */
public class Sample8 {
    
    /**
     * 表示某个方法的累计执行次数
     */
    private long visitTimes = 0L;
    
    /**
     * 使用原子量来统计访问次数,且没有递增之外的复合操作,因此是线程安全的。
     * 2020年1月31日 下午4:38:11 xx 添加此方法
     * @param args
     */
    public void startCalc() {
        this.visitTimes++;
        System.out.println("当前第 " +this.visitTimes + " 次访问。");
        
        // 做业务操作。
    }
}

该方法看起来非常简单,实际上线程不安全,是因为它访问并修改了成员变量 visitTimes。

  1. 看起来似乎只是 this.visitTimes++; 的一次操作,对CPU来说,实际上是三个操作构成的一组符合操作:先读取 visitTimes,再递增1,最后写入visitTimes。存在复合操作,由无原子性保护,因此线程不安全。
  2. 在多线程的场景下,存在内存可见性问题,可能各个线程读取到的值都不一样。因此线程不安全。

如果用 volatile 来改进这段代码:

/**
 * @author xx
 * 2020年1月31日 下午4:08:45
 */
public class Sample8 {
    
    /**
     * 表示某个方法的累计执行次数
     */
    private volatile long visitTimes = 0L;
    
    /**
     * 使用原子量来统计访问次数,且没有递增之外的复合操作,因此是线程安全的。
     * 2020年1月31日 下午4:38:11 xx 添加此方法
     * @param args
     */
    public void startCalc() {
        this.visitTimes++;
        System.out.println("当前第 " +this.visitTimes + " 次访问。");
        
        // 做业务操作。
    }
}

该方法仍然线程不安全:

  • 通过 volatile 修饰词修饰 visitTimes变量,可以解决内存可见性问题。
  • 但该修饰词无法解决复合操作的原子性问题,仍然线程不安全。

我们使用原子量来改进这段代码:

/**
 * @author xx
 * 2020年1月31日 下午4:08:45
 */
public class Sample8 {
    
    /**
     * 表示某个方法的累计执行次数
     */
    private AtomicLong visitTimes = new AtomicLong(0);
    
    /**
     * 使用原子量来统计访问次数,且没有递增之外的复合操作,因此是线程安全的。
     * 2020年1月31日 下午4:38:11 xx 添加此方法
     * @param args
     */
    public void startCalc() {
        long currentVisitTimes = this.visitTimes.incrementAndGet();
        System.out.println("当前第 " +currentVisitTimes + " 次访问。");
        
        // 做业务操作。
    }
}

这段代码是线程安全的:

  1. 原子量封装了一些简单的操作,比如这个逻辑用到的递增后获取。
  2. 这个业务,只和单状态属性相关,可以使用原子量。

你可能感兴趣的:(2020-02-01 2.1 原子量工具包(java.util.concurrent.atomic))