AtomicInteger
美[əˈtɑːmɪk] 美[ˈɪntɪdʒər]
java并发机制中主要有三个特性需要我们去考虑,原子性、可见性和有序性。synchronized关键字可以保证可见性和有序性却无法保证原子性。而这个AtomicInteger的作用就是为了保证原子性。
为什么出现这个类?
详解java并发原子类AtomicInteger(基于jdk1.8源码分析)
因为在并发下面,打印i++的时候
对于i++的操作,其实可以分解为3个步骤。
(1)从主存中读取a的值
(2)对a进行加1操作
(3)把a重新刷新到主存
有的线程已经把a进行了加1操作,但是还没来得及重新刷入到主存,其他的线程就重新读取了旧值。因为才造成了错误。
那么这个数字直接用static AtomicInteger a = new AtomicInteger()
打印的时候可以使用a.incrementAndGet()
使用了AtomicInteger就不会出现这个问题。
里面有几个方法还需要在意一下:
int getAndIncrement 是先获取旧的值,然后进行自增
int incrementAndGet 是先增加,然后获取新的值
这两个方法有什么区别?
相当于先获取current,然后进行增加,然后进行compareAndSet(current, next) 循环去做 如果成功那么返回这个的current。
对于JDK7的话,《Java并发实现原理》中也是基于JDK7来讲:
public final int getAndIncrement() {
for (; ; ) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))//CAS
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
对于JDK8:
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//如果把var填上值
public final int getAndAddInt(Object obj, long valueOffset, int delta) {
int expect;
//自旋
do {
//获取主存的值
expect = this.getIntVolatile(obj, valueOffset);
//CAS操作
} while(!this.compareAndSwapInt(obj, valueOffset, expect, expect + delta));
//返回旧值
return expect;
}
封装了自旋锁
直接封装unsafe方法中了,保证原子性,unsafe是JDK私有的我们不能调用
JDK7与8的区别主要在于JAVA8把循环都放到了unsafe类里面去。
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
根据方法名字也能知道,第一个先返回当前值,然后+1;
后面的是+1然后返回新值。
public static void main(String[] args) {
AtomicInteger first = new AtomicInteger(5);
int a = first.getAndIncrement();
System.out.println("a value:"+a);
AtomicInteger second = new AtomicInteger(5);
int b = second.incrementAndGet();
System.out.println("b value:"+b);
}
输出:
a value:5
b value:6
当面试官问: 你读过源码么?
那么对于AtomicInteger就可以提到CAS和Unsafe类。
AtomicInteger的优点
1.乐观锁,性能较强,利用CPU自身的特性保证原子性,即CPU的指令集封装compare and swap两个操作为一个指令来保证原子性。
2.适合读多写少模式
但是缺点明显
1.自旋,消耗CPU性能,所以写的操作较多推荐sync
2.仅适合简单的运算,否则会产生ABA问题,自旋的时候,别的线程可能更改value,然后又改回来,此时需要加版本号解决,JDK提供了AtomicStampedReference和AtomicMarkableReference解决ABA问题,提供基本数据类型和引用数据类型版本号支持
CAS操作中将判断“V的值是否仍然为A?”,并且如果是的话就继续执行更新操作,在某些算法中,如果V的值首先由A变为B,再由B变为A,那么CAS将会操作成功
JAVA中的cas主要使用的是Unsafe方法,Unsafe的CAS操作主要是基于硬件平台的汇编指令
每次在执行CAS操作时,线程会根据valueOffset去内存中获取当前值去跟expect的值做对比如果一致则修改并返回true,如果不一致说明有别的线程也在修改此对象的值,则返回false
解决ABA最简单的方案就是给值加一个修改版本号,每次值变化,都会修改它版本号,CAS操作时都对比此版本号。
AtomicStampedReference主要维护包含一个对象引用以及一个可以自动更新的整数"stamp"的pair对象来解决ABA问题。
//关键代码
public class AtomicStampedReference {
private static class Pair {
final T reference; //维护对象引用
final int stamp; //用于标志版本
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static Pair of(T reference, int stamp) {
return new Pair(reference, stamp);
}
}
private volatile Pair pair;
....
/**
* expectedReference :更新之前的原始值
* newReference : 将要更新的新值
* expectedStamp : 期待更新的标志版本
* newStamp : 将要更新的标志版本
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair current = pair; //获取当前pair
return
expectedReference == current.reference && //原始值等于当前pair的值引用,说明值未变化
expectedStamp == current.stamp && // 原始标记版本等于当前pair的标记版本,说明标记未变化
((newReference == current.reference &&
newStamp == current.stamp) || // 将要更新的值和标记都没有变化
casPair(current, Pair.of(newReference, newStamp))); // cas 更新pair
}
}
这个类怎么用:
private static AtomicStampedReference atomicStampedRef =
new AtomicStampedReference<>(1, 0);
public static void main(String[] args){
Thread main = new Thread(() -> {
System.out.println("操作线程" + Thread.currentThread() +",初始值 a = " + atomicStampedRef.getReference());
int stamp = atomicStampedRef.getStamp(); //获取当前标识别
try {
Thread.sleep(1000); //等待1秒 ,以便让干扰线程执行
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean isCASSuccess = atomicStampedRef.compareAndSet(1,2,stamp,stamp +1); //此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败
System.out.println("操作线程" + Thread.currentThread() +",CAS操作结果: " + isCASSuccess);
},"主操作线程");
Thread other = new Thread(() -> {
Thread.yield(); // 确保thread-main 优先执行
atomicStampedRef.compareAndSet(1,2,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);
System.out.println("操作线程" + Thread.currentThread() +",【increment】 ,值 = "+ atomicStampedRef.getReference());
atomicStampedRef.compareAndSet(2,1,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);
System.out.println("操作线程" + Thread.currentThread() +",【decrement】 ,值 = "+ atomicStampedRef.getReference());
},"干扰线程");
main.start();
other.start();
}
操作结果:
操作线程Thread[主操作线程,5,main],初始值 a = 2
操作线程Thread[干扰线程,5,main],【increment】 ,值 = 2
操作线程Thread[干扰线程,5,main],【decrement】 ,值 = 1
操作线程Thread[主操作线程,5,main],CAS操作结果: false
参考:ABA问题以及解决
参考:
https://blog.csdn.net/fenglllle/article/details/81316346