所谓 Atomic,翻译过来就是原子。原子被认为是操作中最小的单位,一段代码如果是原子的,则表示这段代码在执行过程中,要么执行成功,要么执行失败。原子操作一般都是底层通过 CPU 的指令来实现。而 atomic 包下的这些类,则可以让我们在多线程环境下,通过一种无锁的原子操作来实现线程安全。
atomic 包下的类基本上都是借助 Unsafe 类,通过 CAS 操作来封装实现的。Unsafe 这个类不属于 Java 标准,或者说这个类是 Java 预留的一个后门类,JDK 中,有关提升性能的 concurrent 或者 NIO 等操作,大部分都是借助于这个类来封装操作的。
Java 是种编译型语言,不像 C 语言能支持操作内存,正常情况下都是由 JVM 进行内存的创建回收等操作,但这个类提供了一些直接操作内存相关的底层操作,使得我们也可以手动操作内存,但从类的名字就可以看出,这个类不是安全的,官方也是不建议我们使用的。
原子操作类介绍
在并发编程中很容易出现并发安全的问题,有一个很简单的例子就是多线程更新变量i=1,比如多个线程执行i++操作,就有可能获取不到正确的值,而这个问题,最常用的方法是通过Synchronized进行控制来达到线程安全的目的(关于synchronized可以看这篇文章)。但是由于synchronized是采用的是悲观锁策略,并不是特别高效的一种解决方案。实际上,在J.U.C下的atomic包提供了一系列的操作简单,性能高效,并能保证线程安全的类去更新基本类型变量,数组元素,引用类型以及更新对象中的字段类型。atomic包下的这些类都是采用的是乐观锁策略去原子更新数据,在java中则是使用CAS操作具体实现。
CAS原理
CAS 包含 3 个参数 CAS(V,E,N). V 表示要更新的变量, E 表示预期值, N表示新值.
仅当V值等于E值时, 才会将V的值设为N, 如果V值和E值不同, 则说明已经有其他线程做了更新, 则当前线程什么都不做. 最后, CAS返回当前V的真实值. CAS操作是抱着乐观的态度进行的, 它总是认为自己可以成功完成操作.
当多个线程同时使用CAS操作一个变量时, 只有一个会胜出, 并成功更新, 其余均会失败.失败的线程不会被挂起,仅是被告知失败, 并且允许再次尝试, 当然也允许失败的线程放弃操作.基于这样的原理, CAS操作即时没有锁,也可以发现其他线程对当前线程的干扰, 并进行恰当的处理.
在 JDK8 的 atomic 包下,大概有 16 个类,按照原子的更新方式,大概可以分为 4 类:原子更新普通类型,原子更新数组,原子更新引用,原子更新字段。
CAS操作
使用锁时,线程获取锁是一种悲观锁策略,即假设每一次执行临界区代码都会产生冲突,所以当前线程获取到锁的时候同时也会阻塞其他线程获取该锁。而CAS操作(又称为无锁操作)是一种乐观锁策略,它假设所有线程访问共享资源的时候不会出现冲突,既然不会出现冲突自然而然就不会阻塞其他线程的操作。因此,线程就不会出现阻塞停顿的状态。那么,如果出现冲突了怎么办?无锁操作是使用CAS(compare and swap)又叫做比较交换来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。
CAS的实现需要硬件指令集的支撑,在JDK1.5后虚拟机才可以使用处理器提供的CMPXCHG指令实现。
Synchronized VS CAS
元老级的Synchronized(未优化前)最主要的问题是:在存在线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,因为这是一种互斥同步(阻塞同步)。而CAS并不是武断的间线程挂起,当CAS操作失败后会进行一定的尝试,而非进行耗时的挂起唤醒的操作,因此也叫做非阻塞同步。这是两者主要的区别。
CAS的问题
-
ABA问题
因为CAS会检查旧值有没有变化,这里存在这样一个有意思的问题。比如一个旧值A变为了成B,然后再变成A,刚好在做CAS时检查发现旧值并没有变化依然为A,但是实际上的确发生了变化。解决方案可以沿袭数据库中常用的乐观锁方式,添加一个版本号可以解决。原来的变化路径A->B->A就变成了1A->2B->3C。 -
自旋时间过长
使用CAS时非阻塞同步,也就是说不会将线程挂起,会自旋(无非就是一个死循环)进行下一次尝试,如果这里自旋时间过长对性能是很大的消耗。如果JVM能支持处理器提供的pause指令,那么在效率上会有一定的提升。
原子更新普通类型
atomic 包下提供了三种基本类型的原子更新,分别是 AtomicBoolean,AtomicInteger,AtomicLong,这几个原子类对应于基础类型的布尔,整形,长整形,至于 Java 中其他的基本类型,如 float 等,如果需要,可以参考这几个类的源码自行实现。
AtomicBoolean
主要接口
public final boolean get();
public final boolean compareAndSet(boolean expect, boolean update);
public boolean weakCompareAndSet(boolean expect, boolean update);
public final void set(boolean newValue);
public final void lazySet(boolean newValue);
public final boolean getAndSet(boolean newValue);
这里面的操作都很正常,主要都是用到了 CAS。这个类中的方法不多,基本上上面都介绍了,而内部的计算则是先将布尔转换为数字0/1,然后再进行后续计算。
AtomicLong
主要接口
public final long get();
public final void set(long newValue);
public final void lazySet(long newValue);
public final long getAndSet(long newValue);
public final boolean compareAndSet(long expect, long update);
public final boolean weakCompareAndSet(long expect, long update);
public final long getAndIncrement();
public final long getAndDecrement();
public final long getAndAdd(long delta);
public final long incrementAndGet();
public final long decrementAndGet();
public final long addAndGet(long delta);
public final long getAndUpdate(LongUnaryOperator updateFunction);
public final long updateAndGet(LongUnaryOperator updateFunction);
AtomicInteger
主要接口
// 取得当前值
public final int get();
// 设置当前值
public final void set(int newValue);
// 设置新值,并返回旧值
public final int getAndSet(int newValue);
// 如果当前值为expect,则设置为u
public final boolean compareAndSet(int expect, int u);
// 当前值加1,返回旧值
public final int getAndIncrement();
// 当前值减1,返回旧值
public final int getAndDecrement();
// 当前值增加delta,返回旧值
public final int getAndAdd(int delta);
// 当前值加1,返回新值
public final int incrementAndGet();
// 当前值减1,返回新值
public final int decrementAndGet();
// 当前值增加delta,返回新值
public final int addAndGet(int delta);
代码实现
// 封装了一个int对其加减
private volatile int value;
.......
public final boolean compareAndSet(int expect, int update) {
// 通过unsafe 基于CPU的CAS指令来实现, 可以认为无阻塞.
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
.......
public final int getAndIncrement() {
for (;;) {
// 当前值
int current = get();
// 预期值
int next = current + 1;
if (compareAndSet(current, next)) {
// 如果加成功了, 则返回当前值
return current;
}
// 如果加失败了, 说明其他线程已经修改了数据, 与期望不相符,
// 则继续无限循环, 直到成功. 这种乐观锁, 理论上只要等两三个时钟周期就可以设值成功
// 相比于直接通过synchronized独占锁的方式操作int, 要大大节约等待时间.
}
}
代码案例
AtomicInteger atomicInteger = new AtomicInteger(1);
System.out.println(atomicInteger.incrementAndGet()); // 2
System.out.println(atomicInteger.getAndIncrement()); // 2
System.out.println(atomicInteger.getAndAccumulate(2, (i, j) -> i + j)); // 3
System.out.println(atomicInteger.get()); // 5
System.out.println(atomicInteger.addAndGet(5));
原子更新数组
tomic 包下提供了三种数组相关类型的原子更新,分别是 AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray,对应于整型,长整形,引用类型,要说明的一点是,这里说的更新是指更新数组中的某一个元素的操作。
由于方法和更新基本类型方法相同,这里只简单看下 AtomicIntegerArray 这个类的几个方法,其他的方法类似。
AtomicIntegerArray
主要接口
// 获得数组第i个下标的元素
public final int get(int i);
// 获得数组的长度
public final int length();
// 将数组第i个下标设置为newValue,并返回旧的值
public final int getAndSet(int i, int newValue);
// 进行CAS操作,如果第i个下标的元素等于expect,则设置为update,设置成功返回true
public final boolean compareAndSet(int i, int expect, int update);
// 将第i个下标的元素加1
public final int getAndIncrement(int i);
// 将第i个下标的元素减1
public final int getAndDecrement(int i);
// 将第i个下标的元素增加delta(delta可以是负数)
public final int getAndAdd(int i, int delta);
源码实现
// 数组本身基地址
private static final int base = unsafe.arrayBaseOffset(int[].class);
// 封装了一个数组
private final int[] array;
static {
// 数组中对象的宽度, int类型, 4个字节, scale = 4;
int scale = unsafe.arrayIndexScale(int[].class);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
// 前导0 : 一个数字转为二进制后, 他前面0的个数
// 对于4来讲, 他就是00000000 00000000 00000000 00000100, 他的前导0 就是29
// 所以shift = 2
shift = 31 - Integer.numberOfLeadingZeros(scale);
}
// 获取第i个元素
public final int get(int i) {
return getRaw(checkedByteOffset(i));
}
// 第i个元素, 在数组中的偏移量是多少
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
// base : 数组基地址, i << shift, 其实就是i * 4, 因为这边是int array.
private static long byteOffset(int i) {
// i * 4 + base
return ((long) i << shift) + base;
}
// 根据偏移量从数组中获取数据
private int getRaw(long offset) {
return unsafe.getIntVolatile(array, offset);
}
测试代码
AtomicIntegerArray array = new AtomicIntegerArray(5);
array.set(0, 1); // 设置数组第一个值为1
System.out.println(array.getAndDecrement(0)); // 1
System.out.println(array.addAndGet(0, 5)); // 5
原子更新引用
更新引用类型的原子类包含了AtomicReference(更新引用类型),AtomicReferenceFieldUpdater(抽象类,更新引用类型里的字段),AtomicMarkableReference(更新带有标记的引用类型)这三个类,这几个类能同时更新多个变量。
AtomicReference
与 AtomicInteger 类似, 只是里面封装了一个对象, 而不是 int, 对引用进行修改。
主要接口
public final V get();
public final void set(V newValue);
public final boolean compareAndSet(V expect, V update);
public final V getAndSet(V newValue);
public class AtomicDemo {
private static AtomicReference<User> reference = new AtomicReference<>();
public static void main(String[] args) {
User user1 = new User("a", 1);
reference.set(user1);
User user2 = new User("b",2);
User user = reference.getAndSet(user2);
System.out.println(user);
System.out.println(reference.get());
}
static class User {
private String userName;
private int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
}
输出结果:
User{userName='a', age=1}
User{userName='b', age=2}
原子更新字段
如果更新的时候只更新对象中的某一个字段,则可以使用 atomic 包提供的更新字段类型:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater 和 AtomicStampedReference,前两个顾名思义,就是更新 int 和 long 类型,最后一个是更新引用类型,该类提供了版本号,用于解决通过 CAS 进行原子更新过程中,可能出现的 ABA 问题。
前面这两个类和上面介绍的 AtomicReferenceFieldUpdater 有些相似,都是抽象类,都需要通过 newUpdater 方法进行实例化,并且对字段的要求也是一样的。
AtomicStampedReference
ABA问题
线程一准备用 CAS 将变量的值由 A 替换为 B, 在此之前线程二将变量的值由 A 替换为 C, 线程三又将 C 替换为A, 然后线程一执行 CAS 时发现变量的值仍然为 A, 所以线程一 CAS 成功.
主要接口
// 比较设置 参数依次为:期望值 写入新值 期望时间戳 新时间戳
public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp)
// 获得当前对象引用
public V getReference()
// 获得当前时间戳
public int getStamp()
// 设置当前对象引用和时间戳
public void set(V newReference, int newStamp)
源码分析
// 内部封装了一个Pair对象, 每次对对象操作的时候, stamp + 1
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
private volatile Pair<V> pair;
// 进行cas操作的时候, 会对比stamp的值
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
代码测试
要求:后台使用多个线程对用户充值, 要求只能充值一次.
public class AtomicStampedReferenceDemo {
static AtomicStampedReference<Integer> money=new AtomicStampedReference<Integer>(19,0);
public staticvoid main(String[] args) {
//模拟多个线程同时更新后台数据库,为用户充值
for(int i = 0 ; i < 3 ; i++) {
final int timestamp=money.getStamp();
newThread() {
public void run() {
while(true){
while(true){
Integerm=money.getReference();
if(m<20){
if(money.compareAndSet(m,m+20,timestamp,timestamp+1)){
System.out.println("余额小于20元,充值成功,余额:"+money.getReference()+"元");
break;
}
}else{
//System.out.println("余额大于20元,无需充值");
break ;
}
}
}
}
}.start();
}
//用户消费线程,模拟消费行为
new Thread() {
publicvoid run() {
for(int i=0;i<100;i++){
while(true){
int timestamp=money.getStamp();
Integer m=money.getReference();
if(m>10){
System.out.println("大于10元");
if(money.compareAndSet(m, m-10,timestamp,timestamp+1)){
System.out.println("成功消费10元,余额:"+money.getReference());
break;
}
}else{
System.out.println("没有足够的金额");
break;
}
}
try {Thread.sleep(100);} catch (InterruptedException e) {}
}
}
}.start();
}
}
AtomicIntegerFieldUpdater
能够让普通变量也能够进行原子操作。
主要接口
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
String fieldName);
public int incrementAndGet(T obj);
- Updater只能修改它可见范围内的变量。因为Updater使用反射得到这个变量。如果变量不可见,就会出错。比如如果score申明为private,就是不可行的。
- 为了确保变量被正确的读取,它必须是volatile类型的。如果我们原有代码中未申明这个类型,那么简单得申明一下就行。
- 由于CAS操作会通过对象实例中的偏移量直接进行赋值,因此,它不支持static字段(Unsafe.objectFieldOffset()不支持静态变量)。
测试
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterDemo {
public static class Candidate {
int id;
// 如果直接把int改成atomicinteger, 可能对代码破坏比较大
// 因此使用AtomicIntegerFieldUpdater对score进行封装
volatile int score;
}
// 通过反射实现
public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
// 检查Updater是否工作正确, allScore的结果应该跟score一致
public static AtomicInteger allScore = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
final Candidate stu = new Candidate();
Thread[] t = new Thread[10000];
for (int i = 0; i < 10000; i++) {
t[i] = new Thread() {
public void run() {
if (Math.random() > 0.4) {
scoreUpdater.incrementAndGet(stu);
allScore.incrementAndGet();
}
}
};
t[i].start();
}
for (int i = 0; i < 10000; i++) {
t[i].join();
}
System.out.println("score=" + stu.score);
System.out.println("allScore=" + allScore);
}
}
JDK8之后引入的类型
在JDK8之前,针对原子操作,我们基本上可以通过上面提供的这些类来完成我们的多线程下的原子操作,不过在并发高的情况下,上面这些单一的 CAS + 自旋操作的性能将会是一个问题,所以上述这些类一般用于低并发操作。
而针对这个问题,JDK8又引入了下面几个类:DoubleAdder,LongAdder,DoubleAccumulator,LongAccumulator,这些类是对AtomicLong这些类的改进与增强,这些类都继承自Striped64这个类。
CAS线程安全
说了半天,我们要回归到最原始的问题了:这样怎么实现线程安全呢?请大家自己先考虑一下这个问题,其实我们在语言层面是没有做任何同步的操作的,大家也可以看到源码没有任何锁加在上面,可它为什么是线程安全的呢?这就是Atomic包下这些类的奥秘:语言层面不做处理,我们将其交给硬件—CPU和内存,利用CPU的多处理能力,实现硬件层面的阻塞,再加上volatile变量的特性即可实现基于原子操作的线程安全。所以说,CAS并不是无阻塞,只是阻塞并非在语言、线程方面,而是在硬件层面,所以无疑这样的操作会更快更高效!
为什么需要AtomicInteger原子操作类?
对于Java中的运算操作,例如自增或自减,若没有进行额外的同步操作,在多线程环境下就是线程不安全的。num++解析为num=num+1,明显,这个操作不具备原子性,多线程并发共享这个变量时必然会出现问题。测试代码如下:
public class AtomicIntegerTest {
private static final int THREADS_CONUT = 20;
public static int count = 0;
public static void increase() {
count++;
}
public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_CONUT];
for (int i = 0; i < THREADS_CONUT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
increase();
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}
}
这里运行了20个线程,每个线程对count变量进行1000此自增操作,如果上面这段代码能够正常并发的话,最后的结果应该是20000才对,但实际结果却发现每次运行的结果都不相同,都是一个小于20000的数字。这是为什么呢?
要是换成volatile修饰count变量呢?
顺带说下volatile关键字很重要的两个特性:
1、保证变量在线程间可见,对volatile变量所有的写操作都能立即反应到其他线程中,换句话说,volatile变量在各个线程中是一致的(得益于java内存模型—"先行发生原则");
2、禁止指令的重排序优化;
那么换成volatile修饰count变量后,会有什么效果呢? 试一试:
public class AtomicIntegerTest {
private static final int THREADS_CONUT = 20;
public static volatile int count = 0;
public static void increase() {
count++;
}
public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_CONUT];
for (int i = 0; i < THREADS_CONUT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
increase();
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}
}
结果似乎又失望了,测试结果和上面的一致,每次都是输出小于20000的数字。这又是为什么么? 上面的论据是正确的,也就是上面标红的内容,但是这个论据并不能得出"基于volatile变量的运算在并发下是安全的"这个结论,因为核心点在于java里的运算(比如自增)并不是原子性的。
用了AtomicInteger类后会变成什么样子呢?
把上面的代码改造成AtomicInteger原子类型,先看看效果
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
private static final int THREADS_CONUT = 20;
public static AtomicInteger count = new AtomicInteger(0);
public static void increase() {
count.incrementAndGet();
}
public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_CONUT];
for (int i = 0; i < THREADS_CONUT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
increase();
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
}
}
结果每次都输出20000,程序输出了正确的结果,这都归功于AtomicInteger.incrementAndGet()方法的原子性。
非阻塞同步
同步:多线程并发访问共享数据时,保证共享数据再同一时刻只被一个或一些线程使用。
我们知道,阻塞同步和非阻塞同步都是实现线程安全的两个保障手段,非阻塞同步对于阻塞同步而言主要解决了阻塞同步中线程阻塞和唤醒带来的性能问题,那什么叫做非阻塞同步呢?在并发环境下,某个线程对共享变量先进行操作,如果没有其他线程争用共享数据那操作就成功;如果存在数据的争用冲突,那就才去补偿措施,比如不断的重试机制,直到成功为止,因为这种乐观的并发策略不需要把线程挂起,也就把这种同步操作称为非阻塞同步(操作和冲突检测具备原子性)。在硬件指令集的发展驱动下,使得 "操作和冲突检测" 这种看起来需要多次操作的行为只需要一条处理器指令便可以完成,这些指令中就包括非常著名的CAS指令(Compare-And-Swap比较并交换)。《深入理解Java虚拟机第二版.周志明》第十三章中这样描述关于CAS机制:
所以再返回来看AtomicInteger.incrementAndGet()方法,它的时间也比较简单
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
incrementAndGet()方法在一个无限循环体内,不断尝试将一个比当前值大1的新值赋给自己,如果失败则说明在执行"获取-设置"操作的时已经被其它线程修改过了,于是便再次进入循环下一次操作,直到成功为止。这个便是AtomicInteger原子性的"诀窍"了,继续进源码看它的compareAndSet方法:
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
可以看到,compareAndSet()调用的就是Unsafe.compareAndSwapInt()方法,即Unsafe类的CAS操作。
使用示例如下图,用于标识程序执行过程中是否发生了异常,使用quartz实现高级定制化定时任务(包含管理界面)实现中:
2020-02-12补充如下内容:
- 原子类相比于普通的锁,粒度更细、效率更高(除了高度竞争的情况下)
- 如果对于上面的示例代码中使用了thread.yield()之类的方法不清晰的,可以直接看下面的代码压测:
public class AtomicIntegerTest implements Runnable {
static AtomicInteger atomicInteger = new AtomicInteger(0);
static int commonInteger = 0;
public void addAtomicInteger() {
atomicInteger.getAndIncrement();
}
public void addCommonInteger() {
commonInteger++;
}
@Override
public void run() {
//可以调大10000看效果更明显
for (int i = 0; i < 10000; i++) {
addAtomicInteger();
addCommonInteger();
}
}
public static void main(String[] args) throws InterruptedException {
AtomicIntegerTest atomicIntegerTest = new AtomicIntegerTest();
Thread thread1 = new Thread(atomicIntegerTest);
Thread thread2 = new Thread(atomicIntegerTest);
thread1.start();
thread2.start();
//join()方法是为了让main主线程等待thread1、thread2两个子线程执行完毕
thread1.join();
thread2.join();
System.out.println("AtomicInteger add result = " + atomicInteger.get());
System.out.println("CommonInteger add result = " + commonInteger);
}
}
- 原子类一览图参考如下:
- 如何把普通变量升级为原子变量?主要是AtomicIntegerFieldUpdater<T>类,参考如下代码:
/**
* @description 将普通变量升级为原子变量
**/
public class AtomicIntegerFieldUpdaterTest implements Runnable {
static Goods phone;
static Goods computer;
AtomicIntegerFieldUpdater<Goods> atomicIntegerFieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(Goods.class, "price");
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
phone.price++;
atomicIntegerFieldUpdater.getAndIncrement(computer);
}
}
static class Goods {
//商品定价
volatile int price;
}
public static void main(String[] args) throws InterruptedException {
phone = new Goods();
computer = new Goods();
AtomicIntegerFieldUpdaterTest atomicIntegerFieldUpdaterTest = new AtomicIntegerFieldUpdaterTest();
Thread thread1 = new Thread(atomicIntegerFieldUpdaterTest);
Thread thread2 = new Thread(atomicIntegerFieldUpdaterTest);
thread1.start();
thread2.start();
//join()方法是为了让main主线程等待thread1、thread2两个子线程执行完毕
thread1.join();
thread2.join();
System.out.println("CommonInteger price = " + phone.price);
System.out.println("AtomicInteger price = " + computer.price);
}
}
- 在高并发情况下,LongAdder(累加器)比AtomicLong原子操作效率更高,LongAdder累加器是java8新加入的,参考以下压测代码:
/**
* @description 压测AtomicLong的原子操作性能
**/
public class AtomicLongTest implements Runnable {
private static AtomicLong atomicLong = new AtomicLong(0);
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
atomicLong.incrementAndGet();
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(30);
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
es.submit(new AtomicLongTest());
}
es.shutdown();
//保证任务全部执行完
while (!es.isTerminated()) { }
long end = System.currentTimeMillis();
System.out.println("AtomicLong add 耗时=" + (end - start));
System.out.println("AtomicLong add result=" + atomicLong.get());
}
}
/**
* @description 压测LongAdder的原子操作性能
**/
public class LongAdderTest implements Runnable {
private static LongAdder longAdder = new LongAdder();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
longAdder.increment();
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(30);
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
es.submit(new LongAdderTest());
}
es.shutdown();
//保证任务全部执行完
while (!es.isTerminated()) {
}
long end = System.currentTimeMillis();
System.out.println("LongAdder add 耗时=" + (end - start));
System.out.println("LongAdder add result=" + longAdder.sum());
}
}
在高度并发竞争情形下,AtomicLong每次进行add都需要flush和refresh(这一块涉及到java内存模型中的工作内存和主内存的,所有变量操作只能在工作内存中进行,然后写回主内存,其它线程再次读取新值),每次add()都需要同步,在高并发时会有比较多冲突,比较耗时导致效率低;而LongAdder中每个线程会维护自己的一个计数器,在最后执行LongAdder.sum()方法时候才需要同步,把所有计数器全部加起来,不需要flush和refresh操作。