一、CAS底层原理
1、CAS概述
1、CAS(Compare And Swap)比较并交换,是一条CPU的原子指令,它的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的
。
2、CAS并发原语提现在Java语言中就是sun.miscUnSaffe类中的各个方法,调用UnSafe类中的CAS方法,JVM会帮我实现CAS汇编指令
。这是一种完全依赖于硬件
功能,通过它实现了原子操作。由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也即是说CAS是一条原子指令,不会造成所谓的数据不一致的问题
。
3、CAS有3个操作数,内存位置值V,旧的预期值A,要修改的更新值B。当且仅当旧的预期值A和内存值V相等时,将内存值V修改为B,否则什么都不做或重来
。
4、简述:
如果线程的期望值跟物理内存的真实值一样,就更新值到物理内存当中,并返回true
。 如果线程的期望值跟物理内存的真实值不一样,返回是false,那么本次修改失败,那么此时需要重新获得主物理内存的新值
。
public class CASTest1 {
public static void main ( String [ ] args) {
AtomicInteger atomicInteger = new AtomicInteger ( 5 ) ;
System . out. println ( "是否更新成功:" + atomicInteger. compareAndSet ( 5 , 10 ) + ",值为:" + atomicInteger. get ( ) ) ;
System . out. println ( "是否更新成功:" + atomicInteger. compareAndSet ( 5 , 20 ) + ",值为:" + atomicInteger. get ( ) ) ;
}
}
2、CAS使用示例
1、如果不使用CAS,在高并发下,多线程同时修改一个变量的值时需要synchronized加锁(可能有人说可以用Lock加锁,Lock底层的AQS也是基于CAS进行获取锁的)。
2、Java中为我们提供了AtomicInteger原子类(底层基于CAS进行更新数据的),不需要加锁就在多线程并发场景下实现数据的一致性。
public class VolatileTest5 {
volatile int num = 0 ;
AtomicInteger atomicInteger = new AtomicInteger ( ) ;
public synchronized void addNum ( ) {
num ++ ;
}
public void addAtomicInteger ( ) {
atomicInteger. getAndIncrement ( ) ;
}
}
查看atomicInteger.getAndIncrement()方法的源代码如下:发现有一个Unsafe类
3、getAndIncrement源码分析
1、调用过程如下:
2、流程分析:
假设线程A和线程B两个线程同时执行getAndAddInt操作(分别跑在不同CPU上)。 AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存。 线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。 线程B也通过getIntVolatile(var1, var2)方法获取到value值3,此时刚好线程B没有被挂起并执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为4,线程B执行完成。 此时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值数字3和主内存的值数字4不一致,说明该值已经被其它线程抢先一步修改过了,那A线程本次修改失败,只能重新读取重新来一遍了
。 线程A重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。
二、Unsafe类
1、Unsafe类概述
1、是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中
,其内部方法操作可以像C的指针
一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
2、注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务
。
3、Unsafe对象不能直接调用,要通过反射才能获得
public class Test {
public static void main ( String [ ] args) throws Exception {
Field theUnsafe = Unsafe . class . getDeclaredField ( "theUnsafe" ) ;
theUnsafe. setAccessible ( true ) ;
Unsafe unsafe = ( Unsafe ) theUnsafe. get ( null ) ;
System . out. println ( unsafe) ;
}
}
2、使用Unsafe进行CAS操作
public class Test2 {
public static void main ( String [ ] args) throws Exception {
Field theUnsafe = Unsafe . class . getDeclaredField ( "theUnsafe" ) ;
theUnsafe. setAccessible ( true ) ;
Unsafe unsafe = ( Unsafe ) theUnsafe. get ( null ) ;
System . out. println ( unsafe) ;
long id = unsafe. objectFieldOffset ( Student . class . getDeclaredField ( "id" ) ) ;
long name = unsafe. objectFieldOffset ( Student . class . getDeclaredField ( "name" ) ) ;
Student s = new Student ( ) ;
unsafe. compareAndSwapInt ( s, id, 0 , 1 ) ;
unsafe. compareAndSwapObject ( s, name, null , "张三" ) ;
System . out. println ( s. getName ( ) ) ;
}
}
@Data
class Student {
private Integer id;
private String name;
}
3、Unsafe与CAS
1、从源码中发现,内部使用自旋的方式进行CAS更新(while循环进行CAS更新,如果更新失败,则循环再次重试)。
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;
}
public final long getAndAddLong ( Object var1, long var2, long var4) {
long var6;
do {
var6 = this . getLongVolatile ( var1, var2) ;
} while ( ! this . compareAndSwapLong ( var1, var2, var6, var6 + var4) ) ;
return var6;
}
public final int getAndSetInt ( Object var1, long var2, int var4) {
int var5;
do {
var5 = this . getIntVolatile ( var1, var2) ;
} while ( ! this . compareAndSwapInt ( var1, var2, var5, var4) ) ;
return var5;
}
public final long getAndSetLong ( Object var1, long var2, long var4) {
long var6;
do {
var6 = this . getLongVolatile ( var1, var2) ;
} while ( ! this . compareAndSwapLong ( var1, var2, var6, var4) ) ;
return var6;
}
public final Object getAndSetObject ( Object var1, long var2, Object var4) {
Object var5;
do {
var5 = this . getObjectVolatile ( var1, var2) ;
} while ( ! this . compareAndSwapObject ( var1, var2, var5, var4) ) ;
return var5;
}
2、Unsafe只提供了3种CAS方法,都是native方法且是原子操作的:
compareAndSwapObject
compareAndSwapInt
compareAndSwapLong
public final native boolean compareAndSwapObject ( Object var1, long var2, Object var4, Object var5) ;
public final native boolean compareAndSwapInt ( Object var1, long var2, int var4, int var5) ;
public final native boolean compareAndSwapLong ( Object var1, long var2, long var4, long var6) ;
4、Unsafe底层
1、compareAndSwapXXXXX()方法来实现CAS操作,它是一个本地方法,实现位于unsafe.cpp中。
UNSAFE_ENTRY ( jboolean, Unsafe_CompareAndSwapInt ( JNIEnv * env, jobject unsafe, jobject obj, jlong offset, jint e, jint x) )
UnsafeWrapper ( "Unsafe_CompareAndSwapInt" ) ;
oop p = JNIHandles:: resolve ( obj) ;
jint* addr = ( jint * ) index_oop_from_field_offset_long ( p, offset) ;
return ( jint) ( Atomic:: cmpxchg ( x, addr, e) ) == e;
UNSAFE_END
2、通过调用Atomic中的函数cmpxchg来进行比较交换。其中参数x是即将更新的值,参数e是原内存的值。
unsigned Atomic:: cmpxchg ( unsigned int exchange_value, volatile unsigned int * dest, unsigned int compare_value) {
assert ( sizeof ( unsigned int ) == sizeof ( jint) , "more work to do" ) ;
return ( unsigned int ) Atomic:: cmpxchg ( ( jint) exchange_value, ( volatile jint* ) dest, ( jint) compare_value) ;
}
3、根据操作系统类型调用不同平台下的重载函数,这个在预编译期间编译器会决定调用哪个平台下的重载函数。
inline jint Atomic:: cmpxchg ( jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os:: is_MP ( ) ;
__asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP ( mp)
cmpxchg dword ptr [ edx] , ecx
}
}
4、总结:
CAS是靠硬件实现的从而在硬件层面提升效率,最底层还是交给硬件来保证原子性和可见性
。 实现方式是基于硬件平台的汇编指令,在intel的CPU中(X86机器上),使用的是汇编指令cmpxchg指令
。 核心思想就是:比较要更新变量的值V和预期值E(compare),相等才会将V的值设为新值N(swap)如果不相等自旋再来
。
5、CAS缺点
1、循环时间长开销大:
方法中有一个do while
,如果CAS失败,会一直进行尝试,如果长时间不成功,可能会给CPU带来很大的开销。
2、只能保证一个共享变量的原子操作:
当对一个共享变量执行操作时,可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。 也可以把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i = 2,j = a,合并一下ij = 2a,然后用CAS来操作ij。 从Java 1.5开始,JDK提供了AtomicReference类(原子引用)来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。
3、ABA问题:
因为CAS需要在操作值的时候,检查值有没有发生变化,比如没有发生变化则更新,比如线程1从内存位置V中取出A,线程2也从内存中取出A,线程2将值变成了B,然后又将值改回了A,这时候线程1进行CAS操作发现内存中值仍然是A,然后线程1修改成功。使用CAS进行检查时则会发现它的值没有发生变化,但是实际上却变化了。 ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A->B->A就会变成1A->2B->3A。 从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
6、ABA问题示例
public class ABAQuestion {
static AtomicReference < String > reference = new AtomicReference < > ( "A" ) ;
public static void main ( String [ ] args) throws InterruptedException {
new Thread ( ( ) -> {
System . out. println ( "线程 " + Thread . currentThread ( ) . getName ( ) + " change A -> B " + reference. compareAndSet ( reference. get ( ) , "B" ) ) ;
System . out. println ( "线程 " + Thread . currentThread ( ) . getName ( ) + " change B -> A " + reference. compareAndSet ( reference. get ( ) , "A" ) ) ;
} , "t1" ) . start ( ) ;
Thread . sleep ( 1000 ) ;
new Thread ( ( ) -> {
System . out. println ( "线程 " + Thread . currentThread ( ) . getName ( ) + " change A -> C " + reference. compareAndSet ( reference. get ( ) , "C" ) ) ;
} , "t2" ) . start ( ) ;
}
}
分析:主线程仅能判断出共享变量的值与最初值A是否相同,不能感知到这种从A改为B又改回A的情况。
7、解决ABA问题
1、可以使用AtomicStampedReference与AtomicMarkableReference
原子引用类来解决ABA问题。
2、区别:
AtomicStampedReference
本质是有一个int值作为版本号,每次更改前先取到这个int值的版本号,等到修改的时候,比较当前版本号与当前线程持有的版本号是否一致,如果一致,则进行修改,并修改版本号(通常+1能明确知道被修改过几次)。 AtomicMarkableReference
不再用int标识引用,而是将一个boolean值作是否有更改的标记,本质就是它的版本号只有两个,true和false,修改的时候在这两个版本号之间来回切换,这样做并不能解决ABA的问题,只是会降低ABA问题发生的几率而已
。
public class SolveABAQuestion {
static AtomicStampedReference < String > reference1 = new AtomicStampedReference < > ( "A" , 1 ) ;
public static void main ( String [ ] args) throws InterruptedException {
new Thread ( ( ) -> {
int stamp = reference1. getStamp ( ) ;
System . out. println ( "线程 " + Thread . currentThread ( ) . getName ( ) + " 初始版本号 " + stamp) ;
try {
Thread . sleep ( 1000 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
boolean oneFlag = reference1. compareAndSet ( "A" , "B" , reference1. getStamp ( ) , reference1. getStamp ( ) + 1 ) ;
System . out. println ( "线程 " + Thread . currentThread ( ) . getName ( ) + " change A -> B " + oneFlag + " 最新值为 " + reference1. getReference ( ) + " 版本号为 " + reference1. getStamp ( ) ) ;
boolean twoFlag = reference1. compareAndSet ( "B" , "A" , reference1. getStamp ( ) , reference1. getStamp ( ) + 1 ) ;
System . out. println ( "线程 " + Thread . currentThread ( ) . getName ( ) + " change B -> A " + twoFlag + " 最新值为 " + reference1. getReference ( ) + " 版本号为 " + reference1. getStamp ( ) ) ;
} , "t1" ) . start ( ) ;
new Thread ( ( ) -> {
int stamp = reference1. getStamp ( ) ;
System . out. println ( "线程 " + Thread . currentThread ( ) . getName ( ) + " 初始版本号 " + stamp) ;
try {
Thread . sleep ( 2000 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
boolean flag = reference1. compareAndSet ( "A" , "C" , stamp, stamp + 1 ) ;
System . out. println ( "线程 " + Thread . currentThread ( ) . getName ( ) + " change A -> C " + flag + " 最新值为 " + reference1. getReference ( ) + " 版本号为 " + reference1. getStamp ( ) ) ;
} , "t2" ) . start ( ) ;
}
}
三、原子类
1、概述
1、从JDK1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。
2、因为变量的类型有很多种,所以在Atomic包里一共提供了16个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。 Atomic包里的类基本都是使用Unsafe实现的包装类。
2、基本类型原子类
1、基本类型原子类:AtomicBoolean、AtomicInteger、AtomicLong
2、以AtomicInteger为例的常用API:
public final int get()
:获取当前的值。 public final int getAndSet(int newValue)
:获取当前的值,并设置新的值 public final int getAndIncrement()
:获取当前的值,并加1 public final int incrementAndGet()
:将当前值加1,并返回 public final int getAndDecrement()
:获取当前的值,并减1 public final int decrementAndGet()
:将当前值减1,并返回 public final int getAndAdd(int delta)
:获取当前的值,并加上预期的值 public final int addAndGet(int delta)
:加上预期的值,并返回 public final boolean compareAndSet(int expect, int update)
:如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
3、数组类型原子类
1、数组类型原子类:
AtomicIntegerArray
:原子更新整型数组里的元素 AtomicLongArray
:原子长整型数组里的元素 AtomicReferenceArray
:原子引用类型数组里的元素
4、引用类型原子类
1、基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用引用类型原子类。
2、引用类型原子类:
AtomicReference
:原子更新引用类型 AtomicStampedReference
:携带版本号的引用类型原子类,可以解决ABA问题,用int值作为版本号
AtomicMarkableReference
:原子更新带有标记位的引用类型对象,用boolean值作是否有更改的标记,并不能很好的解决ABA问题
public class AtomicMarkableReferenceTest {
static AtomicMarkableReference < String > reference1 = new AtomicMarkableReference < > ( "A" , false ) ;
public static void main ( String [ ] args) throws InterruptedException {
new Thread ( ( ) -> {
boolean marked = reference1. isMarked ( ) ;
System . out. println ( "线程 " + Thread . currentThread ( ) . getName ( ) + " 初始标记 " + marked) ;
try {
Thread . sleep ( 1000 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
boolean flag = reference1. compareAndSet ( "A" , "B" , marked, ! marked) ;
System . out. println ( "线程 " + Thread . currentThread ( ) . getName ( ) + " change A -> B " + flag + " 最新值为 " + reference1. getReference ( ) + " 标记为 " + reference1. isMarked ( ) ) ;
} , "t1" ) . start ( ) ;
new Thread ( ( ) -> {
boolean marked = reference1. isMarked ( ) ;
System . out. println ( "线程 " + Thread . currentThread ( ) . getName ( ) + " 初始标记 " + marked) ;
try {
Thread . sleep ( 2000 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
boolean flag = reference1. compareAndSet ( "A" , "C" , marked, ! marked) ;
System . out. println ( "线程 " + Thread . currentThread ( ) . getName ( ) + " change A -> C " + flag + " 最新值为 " + reference1. getReference ( ) + " 版本号为 " + reference1. isMarked ( ) ) ;
} , "t2" ) . start ( ) ;
}
}
5、对象属性修改原子类
1、保护的是对象的属性(或成员变量)的安全性,以一种线程安全的方式操作非线程安全对象内的某些字段。在内部通过Unsafe类的native方法保证操作的原子性
。
2、对象属性修改原子类:
AtomicIntegerFieldUpdater
:基于反射的工具类,可以原子性的更新指定对象中int类型字段 AtomicLongFieldUpdater
:基于反射的工具类,可以原子性的更新指定对象中long类型字段 AtomicReferenceFieldUpdater
:基于反射的工具类,可以原子性的更新指定对象中引用类型字段。
3、使用规则:
属性字段必须是volatile类型,用于保证可见性,否则报错java.lang.IllegalArgumentException: Must be volatile type
属性类型必须和原子类中的类型一致
。 属性字段必须非private、protected(如果是当前类是可以的)
。 属性字段只能是实例变量,不能是类变量(static)
。 属性字段不能是final变量,因为这样的字段不可修改
。 如果要处理Integer和Long类型,则需要使用AtomicReferenceFieldUpdater
。
public class AtomicIntegerFieldUpdaterTest {
public static void main ( String [ ] args) throws InterruptedException {
User user = new User ( ) ;
for ( int i = 1 ; i <= 10000 ; i++ ) {
int j = i;
new Thread ( ( ) -> {
user. addScore ( user) ;
} , String . valueOf ( i) ) . start ( ) ;
}
Thread . sleep ( 5000 ) ;
System . out. println ( "结果:" + user. score) ;
}
}
class User {
private String name;
public volatile int score;
AtomicIntegerFieldUpdater < User > fieldUpdater = AtomicIntegerFieldUpdater . newUpdater ( User . class , "score" ) ;
public void addScore ( ) {
score = score + 1 ;
}
public void addScore ( User user) {
fieldUpdater. incrementAndGet ( user) ;
}
}
四、原子增强类
1、概述
1、JDK 1.8新增的部分,是对AtomicLong等类的改进。比如LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效(减少乐观锁的重试次数
)。
2、包含如下几个类:
DoubleAccumulator
:一个或多个变量共同维护使用提供的函数更新的运行double值 DoubleAdder
:一个或多个变量共同保持初始为零的double和 LongAccumulator
:一个或多个变量共同维护使用提供的函数更新的运行long值 LongAdder
:一个或多个变量共同保持初始为零的long和
2、常用方法以LongAdder为例
方法 说明 LongAdder() 只有一个无参的构造器,会构造一个sum=0的实例对象 void add(long x) 增量计算,+x void increment() 自增(+1) void decrement() 自减(-1) long sum() 返回当前的总和,不存在并发的情况下,会返回精确值,存在并发下,不保证精确值 void reset() 重置为零,可用于替代重新new一个LongAdder,只能在没有并发情况下使用 long sumThenReset() 计算sum的和并重置sum为0
3、性能比较
public class compareAtomicLongAndLongAdder {
private static final int ADD_COUNT = 10000 ;
private static final int THREAD_COUNT = 50 ;
public static void main ( String [ ] args) throws InterruptedException {
long startTime, endTime;
CompareResource compareResource = new CompareResource ( ) ;
CountDownLatch c1 = new CountDownLatch ( THREAD_COUNT) ;
CountDownLatch c2 = new CountDownLatch ( THREAD_COUNT) ;
CountDownLatch c3 = new CountDownLatch ( THREAD_COUNT) ;
CountDownLatch c4 = new CountDownLatch ( THREAD_COUNT) ;
startTime = System . currentTimeMillis ( ) ;
for ( int i = 1 ; i <= THREAD_COUNT; i++ ) {
new Thread ( ( ) -> {
try {
for ( int j = 1 ; j <= 100 * ADD_COUNT; j++ ) {
compareResource. addSynchronizedNum ( ) ;
}
} finally {
c1. countDown ( ) ;
}
} , String . valueOf ( i) ) . start ( ) ;
}
c1. await ( ) ;
endTime = System . currentTimeMillis ( ) ;
System . out. println ( "使用Synchronized方式计算耗时:" + ( endTime - startTime) + " 毫秒" + " 最后结果: " + compareResource. number) ;
startTime = System . currentTimeMillis ( ) ;
for ( int i = 1 ; i <= THREAD_COUNT; i++ ) {
new Thread ( ( ) -> {
try {
for ( int j = 1 ; j <= 100 * ADD_COUNT; j++ ) {
compareResource. addAtomicLongNum ( ) ;
}
} finally {
c2. countDown ( ) ;
}
} , String . valueOf ( i) ) . start ( ) ;
}
c2. await ( ) ;
endTime = System . currentTimeMillis ( ) ;
System . out. println ( "使用AtomicLong方式计算耗时:" + ( endTime - startTime) + " 毫秒" + " 最后结果: " + compareResource. atomicLong. get ( ) ) ;
startTime = System . currentTimeMillis ( ) ;
for ( int i = 1 ; i <= THREAD_COUNT; i++ ) {
new Thread ( ( ) -> {
try {
for ( int j = 1 ; j <= 100 * ADD_COUNT; j++ ) {
compareResource. addLongAdderNum ( ) ;
}
} finally {
c3. countDown ( ) ;
}
} , String . valueOf ( i) ) . start ( ) ;
}
c3. await ( ) ;
endTime = System . currentTimeMillis ( ) ;
System . out. println ( "使用LongAdder方式计算耗时:" + ( endTime - startTime) + " 毫秒" + " 最后结果: " + compareResource. longAdder. sum ( ) ) ;
startTime = System . currentTimeMillis ( ) ;
for ( int i = 1 ; i <= THREAD_COUNT; i++ ) {
new Thread ( ( ) -> {
try {
for ( int j = 1 ; j <= 100 * ADD_COUNT; j++ ) {
compareResource. addLongAccumulatorNum ( ) ;
}
} finally {
c4. countDown ( ) ;
}
} , String . valueOf ( i) ) . start ( ) ;
}
c4. await ( ) ;
endTime = System . currentTimeMillis ( ) ;
System . out. println ( "使用LongAccumulator方式计算耗时:" + ( endTime - startTime) + " 毫秒" + " 最后结果: " + compareResource. longAccumulator. get ( ) ) ;
}
}
class CompareResource {
public int number = 0 ;
public synchronized void addSynchronizedNum ( ) {
number ++ ;
}
AtomicLong atomicLong = new AtomicLong ( 0 ) ;
public void addAtomicLongNum ( ) {
atomicLong. getAndIncrement ( ) ;
}
LongAdder longAdder = new LongAdder ( ) ;
public void addLongAdderNum ( ) {
longAdder. increment ( ) ;
}
LongAccumulator longAccumulator = new LongAccumulator ( ( x, y) -> x + y, 0 ) ;
public void addLongAccumulatorNum ( ) {
longAccumulator. accumulate ( 1 ) ;
}
}
五、LongAdder原理分析
1、架构
2、Striped64类
1、LongAdder只有一个空构造器,其本身也没有什么特殊的地方,所有复杂的逻辑都在它的父类Striped64中
2、Striped64类实现一些核心操作,处理64位数据。它只有一个空构造器,初始化时,通过Unsafe获取到类字段的偏移值,以便后续CAS操作
。
abstract class Striped64 extends Number {
Striped64 ( ) {
}
private static final sun. misc. Unsafe UNSAFE;
private static final long BASE;
private static final long CELLSBUSY;
private static final long PROBE;
static {
try {
UNSAFE = sun. misc. Unsafe. getUnsafe ( ) ;
Class < ? > sk = Striped64 . class ;
BASE = UNSAFE. objectFieldOffset
( sk. getDeclaredField ( "base" ) ) ;
CELLSBUSY = UNSAFE. objectFieldOffset
( sk. getDeclaredField ( "cellsBusy" ) ) ;
Class < ? > tk = Thread . class ;
PROBE = UNSAFE. objectFieldOffset
( tk. getDeclaredField ( "threadLocalRandomProbe" ) ) ;
} catch ( Exception e) {
throw new Error ( e) ;
}
}
}
几个重要的成员变量或方法的定义
static final int NCPU = Runtime . getRuntime ( ) . availableProcessors ( ) ;
transient volatile Cell [ ] cells;
transient volatile long base;
transient volatile int cellsBusy;
final boolean casCellsBusy ( ) {
return UNSAFE. compareAndSwapInt ( this , CELLSBUSY, 0 , 1 ) ;
}
static final int getProbe ( ) {
return UNSAFE. getInt ( Thread . currentThread ( ) , PROBE) ;
}
static final int advanceProbe ( int probe) {
probe ^= probe << 13 ;
probe ^= probe >>> 17 ;
probe ^= probe << 5 ;
UNSAFE. putInt ( Thread . currentThread ( ) , PROBE, probe) ;
return probe;
}
内部 Cell
类(也就是槽),每个Cell对象存有一个value值,可以通过Unsafe来CAS操作它的值
@sun.misc.Contended static final class Cell {
volatile long value;
Cell ( long x) { value = x; }
final boolean cas ( long cmp, long val) {
return UNSAFE. compareAndSwapLong ( this , valueOffset, cmp, val) ;
}
private static final sun. misc. Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
UNSAFE = sun. misc. Unsafe. getUnsafe ( ) ;
Class < ? > ak = Cell . class ;
valueOffset = UNSAFE. objectFieldOffset
( ak. getDeclaredField ( "value" ) ) ;
} catch ( Exception e) {
throw new Error ( e) ;
}
}
}
3、LongAdder的基本原理
1、LongAdder的基本思想是分散热点
,将value值分散到一个Cell数组
中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率也就小很多,如果要获取真正的long值,只要将各个槽中的变量值累加返回。
2、sum()会将所有Cell数组中的value和base累加作为返回值,核心思想就是将AtomicLong的一个value的更新压力分散到多个value上进行更新
。
3、LongAdder原理
:
在无竞争的情况,跟AtomicLong一样,对同一个base进行操作(无并发,单线程下直接CAS操作更新base值,直接累加到变量base上)。 当出现竞争关系时则是采用化整为零的做法,以空间换时间,用一个数组cells,将一个value拆分进这个数组cells。多个线程需要同时对value进行操作时,可以对线程id进行hash得到hash值,再根据hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作。当所有线程操作完毕,将数组cells的所有值和无竞争值base都加起来作为最终结果
。
4、
最终结果
=
B
a
s
e
+
∑
i
=
n
n
C
e
l
l
[
i
]
最终结果 = Base + \sum_{i=n}^{n}Cell[i]
最终结果 = B a se + i = n ∑ n C e ll [ i ]
4、LongAdder源码之add方法分析
public void add ( long x) {
Cell [ ] as; long b, v; int m; Cell a;
if ( ( as = cells) != null || ! casBase ( b = base, b + x) ) {
boolean uncontended = true ;
if ( as == null || ( m = as. length - 1 ) < 0 ||
( a = as[ getProbe ( ) & m] ) == null || ! ( uncontended = a. cas ( v = a. value, v + x) ) )
longAccumulate ( x, null , uncontended) ;
}
}
1、流程说明:
如果Cells数组为空,尝试CAS更新base字段,成功则退出。 如果Cells数组为空,CAS更新base字段失败,表示出现竞争,uncontended为true,调用longAccumulate。 如果Cells数组不为空,当前线程通过getProbe&m得到的Cell为空,uncontended为true,调用longAccumulate。 如果Cells数组不为空,当前线程通过getProbe&m得到的Cell不为空,CAS更新Cell中的值,成功则返回,否则uncontended设置为false,取反之后进入if,调用longAccumulate。
2、流程图如下:
5、LongAdder源码之longAccumulate方法分析
1、方法入参说明:
long x:需要增加的值,一般默认都是1 LongBinaryOperator fn:默认传递的null,表示相加,在LongAccumulator可以自定义计算
boolean wasUncontended:竞争标识,false表示有竞争,只有cells初始化之后,并且当前线程CAS竞争修改失败,才会是false
。
2、总体结构:
final void longAccumulate ( long x, LongBinaryOperator fn, boolean wasUncontended) {
int h;
if ( ( h = getProbe ( ) ) == 0 ) {
ThreadLocalRandom . current ( ) ;
h = getProbe ( ) ;
wasUncontended = true ;
}
boolean collide = false ;
for ( ; ; ) {
Cell [ ] as; Cell a; int n; long v;
if ( ( as = cells) != null && ( n = as. length) > 0 ) {
}
else if ( cellsBusy == 0 && cells == as && casCellsBusy ( ) ) {
boolean init = false ;
try {
if ( cells == as) {
Cell [ ] rs = new Cell [ 2 ] ;
rs[ h & 1 ] = new Cell ( x) ;
cells = rs;
init = true ;
}
} finally {
cellsBusy = 0 ;
}
if ( init)
break ;
}
else if ( casBase ( v = base, ( ( fn == null ) ? v + x :
fn. applyAsLong ( v, x) ) ) )
break ;
}
}
最复杂的一段代码分析:Cell数组已经初始化的情况
,流程图如下:
Cell [ ] as; Cell a; int n; long v;
if ( ( as = cells) != null && ( n = as. length) > 0 ) {
if ( ( a = as[ ( n - 1 ) & h] ) == null ) {
if ( cellsBusy == 0 ) {
Cell r = new Cell ( x) ;
if ( cellsBusy == 0 && casCellsBusy ( ) ) {
boolean created = false ;
try {
Cell [ ] rs; int m, j;
if ( ( rs = cells) != null &&
( m = rs. length) > 0 &&
rs[ j = ( m - 1 ) & h] == null ) {
rs[ j] = r;
created = true ;
}
} finally {
cellsBusy = 0 ;
}
if ( created)
break ;
continue ;
}
}
collide = false ;
}
else if ( ! wasUncontended)
wasUncontended = true ;
else if ( a. cas ( v = a. value, ( ( fn == null ) ? v + x : fn. applyAsLong ( v, x) ) ) )
break ;
else if ( n >= NCPU || cells != as)
collide = false ;
else if ( ! collide)
collide = true ;
else if ( cellsBusy == 0 && casCellsBusy ( ) ) {
try {
if ( cells == as) {
Cell [ ] rs = new Cell [ n << 1 ] ;
for ( int i = 0 ; i < n; ++ i)
rs[ i] = as[ i] ;
cells = rs;
}
} finally {
cellsBusy = 0 ;
}
collide = false ;
continue ;
}
h = advanceProbe ( h) ;
}
6、最终通过sum方法将结果统计
1、sum方法会将当前时刻所有Cell数组中的value和base累加作为返回值
。
2、此返回值不是绝对准确的,因为调用这个方法时,可能还有其他线程正在进行计数累加。
3、方法的返回时刻和调用时刻不是同一个点,在有并发的情况下,这个值只是近似准确的计数值。
4、高并发时,除非全局加锁,否则得不到程序运行中某个时刻绝对准确的值。
public long sum ( ) {
Cell [ ] as = cells; Cell a;
long sum = base;
if ( as != null ) {
for ( int i = 0 ; i < as. length; ++ i) {
if ( ( a = as[ i] ) != null )
sum += a. value;
}
}
return sum;
}
7、AtomicLong与LongAdder对比
1、AtomicLong:
线程安全,可允许一些性能损耗,对结果要求精确时可使用。 是多个线程针对单个热点值value进行原子操作。
2、LongAdder:
当需要在高并发下有较好的性能表现,且对值的精确度要求不高时可以使用。 是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作。
8、伪共享原理
1、@sun.misc.Contended
:对某字段加上该注解则表示该字段会单独占用一个缓存行(Cache Line)
。这里的缓存行是指CPU缓存(L1、L2、L3)的存储单元,常见的缓存行大小为64字节。
2、单独使用一个缓存行有什么作用 — 避免伪共享
:
当一个CPU要修改某共享变量A时会先锁定自己缓存里A所在的缓存行,并且把其他CPU缓存上相关的缓存行设置为无效
。但如果被锁定或失效的缓存行里,还存储了其他不相干的变量B,其他线程此时就访问不了B,或者由于缓存行失效需要重新从内存中读取加载到缓存里,这里造成了开销,所以让共享变量A单独使用一个缓存行就不会影响到其他线程的访问。
3、缓存和内存的速度比较:
从CPU到 大约需要的时间周期 寄存器 1 cycle(4GHz 的 CPU 约为0.25ns) L1 3-4 cycle L2 10-20 cycle L3 40-45 cycle 内存 120-240 cycle
4、因为CPU与内存的速度差异很大,需要靠预读数据至缓存来提升效率。而缓存以缓存行为单位,每个缓存行对应着一块内存,一般是64 byte(8个long),缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中,CPU要保证数据的一致性,如果某个CPU核心更改了数据,其他CPU核心对应的整个缓存行必须失效。
因为Cell是数组形式,在内存中是连续存储的,一个Cell为24字节(16字节的对象头和8字节的value),因为此缓存行可以存下2个的Cell对象,这样就会出现问题:Core-0要修改Cell[0],Core-1要修改Cell[1] 无论谁修改成功,都会导致对方Core的缓存行失败,比如Core-0中Cell[0] = 6000,Cell[1]=8000
要累加Cell[0] = 6001,Cell[1]=8000
,这时会让Core-1的缓存行失效。 @sun.misc.Contended
用来解决这个问题,它的原理是在使用此注解的对象或字段的前后各增加128字节大小的padding,从而让CPU将对象预读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效。