假设多个线程需要对一个变量不停地累加1:
public class HelloWorld{
private int data=0;
//多个线程同时对data变量执行操作:
data++;
}
问题:多个线程直接这样并发对一个data变量进行修改,是线程不安全性的行为,会导致data值的变化不遵照预期的值来改变.
加锁变成线程安全的:
public class HelloWorld {
private int data=0;
public synchronized void add(){
data++;
}
//多个线程同时调用方法:add()
}
每个线程要进入add()方法之前先得尝试加锁,同一时间只有一个线程能加锁,其他线程需要等待锁.
但是对于这种简单的操作,使用加锁的方法,相当于让各个线程串行话,显得太重了.
对于这种简单的data++类的操作,我们可以换一种做法,java并发包下面提供了一系列的Atomic原子类,比如说AtomicInteger.
private AtomicInteger data = new AtomicInteger(0);
public void add(){
data.incrementAndGet();
}
AtomicInteger可以保证多线程并发安全的情况下,高性能的并发更新一个数值.
多个线程并发执行add()方法中的data.incrementAndGet()方法,就是给我们把data的值累加1,接着返回累加后最新的值.
实际上,Atomic原子类底层用的不是传统意义的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性.
CAS(Compare and Set):先比较再设置
假如说有3个线程并发的要修改一个AtomicInteger的值,他们底层的机制如下:
首先,每个线程都会先获取当前的值,接着走一个原子的CAS操作,原子的意思就是这个CAS操作一定是自己完整执行完的,不会被别人打断。
然后CAS操作里,会比较一下说,唉!大兄弟!现在你的值是不是刚才我获取到的那个值啊?
如果是的话,bingo!说明没人改过这个值,那你给我设置成累加1之后的一个值好了!
同理,如果有人在执行CAS的时候,发现自己之前获取的值跟当前的值不一样,会导致CAS失败,失败之后,进入一个无限循环,再次获取值,接着执行CAS操作!
所谓Atomic原子类的原理,没有基于加锁机制串行化,而是基于CAS机制:先获取一个值,然后发起CAS,比较这个值被人改过没?如果没有,就更改值!这个CAS是原子的,别人不会打断你!
Java 8对CAS机制的优化:
大量的线程同时并发修改一个AtomicInteger,可能有很多线程会不停的自旋,进入一个无限重复的循环中。
这些线程不停地获取值,然后发起CAS操作,但是发现这个值被别人改过了,于是再次进入下一个循环,获取值,发起CAS操作又失败了,再次进入下一个循环。
在大量线程高并发更新AtomicInteger的时候,这种问题可能会比较明显,导致大量线程空循环,自旋转,性能和效率都不是特别好。
Java 8新推出一个类:LongAdder,它尝试使用分段CAS以及自动分段迁移的方式来大幅度提升多线程高并发执行CAS操作的性能.
在LongAdder的底层实现中,首先有一个base值,刚开始多线程来不停的累加数值,都是对base进行累加的,比如刚开始累加成了base = 5。
接着如果发现并发更新的线程数量过多,就会开始施行分段CAS的机制,也就是内部会搞一个Cell数组,每个数组是一个数值分段。
这时,让大量的线程分别去对不同Cell内部的value值进行CAS累加操作,这样就把CAS计算压力分散到了不同的Cell分段数值中了!
这样就可以大幅度的降低多线程并发更新同一个数值时出现的无限循环的问题,大幅度提升了多线程并发更新数值的性能和效率!
而且他内部实现了自动分段迁移的机制,也就是如果某个Cell的value执行CAS失败了,那么就会自动去找另外一个Cell分段内的value值进行CAS操作。这样也解决了线程空旋转、自旋不停等待执行CAS操作的问题,让一个线程过来执行CAS时可以尽快的完成这个操作。
最后,如果你要从LongAdder中获取当前累加的总值,就会把base值和所有Cell分段数值加起来返回给你。