JDK1.5中增加的一个最主要的支持是Atomic类,如AtomicInteger、AtomicLong等,这些类可帮助最大限度地减少在多线程中对于一些基本操作(例如,增加或减少多个线程之间共享的值)的复杂性,而这些类的实现都依赖于CAS(compare and swap)的算法。
一、CAS
1.CAS原理
CAS的全称是Compare And Swap——比较交换,CAS中有三个核心参数:
- 主内存中存放的V值,所有线程共享。
- 线程上次从内存中读取的V值A存放在线程的帧栈中,每个线程私有。
- 需要写入内存中并改写V值的B值。也就是线程对A值操作后放入到主存V中。
CAS的核心是在将B值写入到V之前要比较A值和V值是否相同,如果不相同证明此时V值已经被其他线程改变,重新将V值赋给A,并重新计算得到B,如果相同,则将B值赋给V。
CAS是一种乐观锁,当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,直到成功为止(JDK代码中采用while循环不断自旋)。由于无锁,因此不可能出现死锁的情况,也就是说无锁操作天生免疫死锁。
source: 全面了解Java中的CAS机制
2.CAS的优劣
cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换。如果采用悲观锁(如Sychronizied独占锁),在进程挂起和恢复执行过程中存在着很大的开销,同时当一个线程正在等待锁时,它不能做任何事。用乐观锁在读多写少的场景下,性能就会比较优越。
CAS之不足:
-
循环时间长开销很大:如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
-
只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
-
ABA问题
如果一开始位置V得到的旧值是A,当进行赋值操作时再次读取发现仍然是A,并不能说明变量没有被其它线程改变过。有可能是其它线程将变量改为了B,后来又改回了A。大部分情况下ABA问题不会影响程序并发的正确性,如果要解决ABA问题,可以用传统的互斥同步。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类AtomicStampedReference
,它可以通过控制变量值的版本来保证CAS的正确性。
source: 面试必问的CAS,你懂了吗
二、Atomic原子包
原子更新基本类型主要包括3个类:
- AtomicBoolean:原子更新布尔类型
- AtomicInteger:原子更新整型
- AtomicLong:原子更新长整型
这3个类的实现原理和使用方式几乎是一样的,提供了原子自增方法、原子自减方法以及原子赋值方法等。原子类的内部几乎是基于Unsafe类中的CAS相关操作的方法实现的,这也同时证明其是基于无锁实现的。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法,注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。
//JDK 1.7的源码,由for的死循环实现,并且直接在AtomicInteger实现该方法,
//JDK1.8后,该方法实现已移动到Unsafe类中,直接调用getAndAddInt方法即可
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
source:JAVA中的CAS
三、Java8对CAS的优化
Java 8推出了一个新的类,LongAdder,他就是尝试使用分段CAS以及自动分段迁移的方式来大幅度提升多线程高并发执行CAS操作的性能!
如果发现并发更新的线程数量过多,就会开始施行分段CAS的机制,也就是内部会搞一个Cell数组,每个数组是一个数值分段。
这时,让大量的线程分别去对不同Cell内部的value值进行CAS累加操作,这样就把CAS计算压力分散到了不同的Cell分段数值中了!
这样就可以大幅度的降低多线程并发更新同一个数值时出现的无限循环的问题,大幅度提升了多线程并发更新数值的性能和效率!
而且他内部实现了自动分段迁移的机制,也就是如果某个Cell的value执行CAS失败了,那么就会自动去找另外一个Cell分段内的value值进行CAS操作。
这样也解决了线程空旋转、自旋不停等待执行CAS操作的问题,让一个线程过来执行CAS时可以尽快的完成这个操作。
最后,如果你要从LongAdder中获取当前累加的总值,就会把base值和所有Cell分段数值加起来返回给你。
source :volatile和CAS
网友评论