2020-02-01 2.1 原子量工具包(java.util.
2020-02-01 本文已影响0人
FredWorks
本文是Java线程安全和并发编程知识总结的一部分。
2.1 原子量工具包(java.util.concurrent.atomic
)
原子量提供如下便利:
- 该包提供的原子类,可以封装单个状态变量,并提供了一系列简单常用的原子操作。这些操作的支持直接来自处理器对原子量的支持,因此效率非常高,其性能并不比volatile关键词差。
- 最关键的,原子类实际上是利用CPU对原子量的支持,基于非阻塞同步机制实现的,在原子量提供的方法可以满足需求的情况下,可以取代锁,且性能高于锁机制。如果对应CPU架构不支持原子量,则JVM会采用锁机制来模拟实现。
原子量的缺点在于:
- 原子量依赖CPU的支持,目前能提供的基本就是如下几类复合操作:
- 测试并设置
- 获取并递增
- 比较并交换
- 关联加载
- 条件存储
- 原子量是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。
- 看起来似乎只是
this.visitTimes++;
的一次操作,对CPU来说,实际上是三个操作构成的一组符合操作:先读取 visitTimes,再递增1,最后写入visitTimes。存在复合操作,由无原子性保护,因此线程不安全。- 在多线程的场景下,存在内存可见性问题,可能各个线程读取到的值都不一样。因此线程不安全。
如果用 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 + " 次访问。");
// 做业务操作。
}
}
这段代码是线程安全的:
- 原子量封装了一些简单的操作,比如这个逻辑用到的递增后获取。
- 这个业务,只和单状态属性相关,可以使用原子量。