add 1 2 3 4 5 6 7 8 private long count = 0 ;public void add () { int idx = 0 ; while (idx++ < 10_000 ) { count += 1 ; } }
add()方法不是线程安全的,主要原因是count的可见性 和count+=1的原子性
可见性的问题可以用volatile 来解决,原子性的问题一般采用互斥锁 来解决
无锁方案 1 2 3 4 5 6 7 8 private AtomicLong atomicCount = new AtomicLong (0 );public void atomicAdd () { int idx = 0 ; while (idx++ < 10_000 ) { atomicCount.getAndIncrement(); } }
无锁方案相对于互斥锁方案,最大的好处是_性能 _
互斥锁方案为了保证互斥性,需要执行加锁、解锁 操作,而加锁、解锁操作本身会消耗性能
拿不到锁的线程会进入阻塞 状态,进而触发线程切换 ,线程切换对性能的消耗也很大
无锁方案则完全没有加锁、解锁的性能消耗,同时能保证互斥性
实现原理
CPU为了解决并发问题,提供了CAS (Compare And Swap)指令
CAS指令包含三个参数:共享变量的内存地址A,用于比较的值B、共享变量的新值C
只有当内存地址A处的值等于B时,才能将内存地址A处的值更新为新值C
CAS指令是一条CPU指令 ,本身能保证原子性
自旋 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class SimulatedCAS { private volatile int count; public void addOne () { int newValue; do { newValue = count + 1 ; } while (count != cas(count, newValue)); } private synchronized int cas (int expect, int newValue) { int curValue = count; if (curValue == expect) { count = newValue; } return curValue; } }
使用CAS解决并发问题,一般都会伴随着自旋 (循环尝试)
首先计算newValue=count+1,如果count!=cas(count, newValue)
说明线程执行完代码1之后,在执行代码2之前,count的值被其他线程更新过,此时采用自旋 (循环尝试)
通过CAS+自旋 实现的无锁方案,完全没有加锁、解锁操作,不会阻塞线程,相对于互斥锁方案来说,性能提升了很多
ABA问题
上面的count==cas(count, newValue),并不能说明执行完代码1之后,在执行代码2之前,count的值没有被其他线程更新过
假设count原本为A,线程T1在执行完代码1之后,执行代码2之前,线程T2将count更新为B,之后又被T3更新回A
count+=1 原子化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public final long getAndIncrement () { return unsafe.getAndAddLong(this , valueOffset, 1L ); } public final long getAndAddLong (Object o, long offset, long delta) { long v; do { v = getLongVolatile(o, offset); } while (!compareAndSwapLong(o, offset, v, v + delta)); return v; } public native long getLongVolatile (Object o, long offset) ;public final native boolean compareAndSwapLong (Object o, long offset, long expected, long x) ;
原子类
原子化的基本类型 相关实现有AtomicBoolean、AtomicInteger和AtomicLong
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 getAndIncrement() getAndDecrement() incrementAndGet() decrementAndGet() getAndAdd(delta) addAndGet(delta) compareAndSet(expect, update) getAndUpdate(func) updateAndGet(func) getAndAccumulate(x,func) accumulateAndGet(x,func)
原子化的对象引用类型
相关实现有AtomicReference、AtomicStampedReference和AtomicMarkableReference,可以实现对象引用的原子化更新
对象引用的更新需要重点关注ABA问题 ,而AtomicStampedReference 和AtomicMarkableReference 可以解决ABA问题
AtomicStampedReference 1 2 3 4 public boolean compareAndSet (V expectedReference, V newReference, int expectedStamp, int newStamp)
通过增加一个版本号 即可解决ABA问题
每次执行CAS操作时,附加再更新一个版本号,只要保证版本号是递增 的,即使A->B->A,版本号也不会回退
AtomicMarkableReference 将版本号简化成一个Boolean值
1 2 3 4 public boolean compareAndSet (V expectedReference, V newReference, boolean expectedMark, boolean newMark)
原子化的数组
相关实现有AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray
利用这些原子类,可以原子化地更新数组里面的每一个元素
原子化的对象属性更新器
相关实现有AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater
利用这些原子类,都可以原子化地更新对象的属性,这三个方法都是利用反射机制 实现的
对象属性必须是volatile 类型,只有这样才能保证可见性
如果对象属性不是volatile类型的,newUpdater会抛出IllegalArgumentException
1 2 3 4 5 6 public static <U> AtomicLongFieldUpdater<U> newUpdater (Class<U> tclass, String fieldName) ;public final boolean compareAndSet (T obj, long expect, long update) public final boolean compareAndSet (T obj, long expect, long update)
原子化的累加器
相关实现有DoubleAccumulator、DoubleAdder、LongAccumulator和LongAdder
这几个原子类仅仅用来执行累加操作,相比于原子化的基本数据类型,速度更快 ,但不支持compareAndSet
如果仅仅需要累加操作,使用原子化的累加器的性能会更好
小结
无锁方案相对于互斥锁方案,性能更好,不会出现死锁问题,但可能出现饥饿 和活锁 问题(由于自旋 )
Java提供的原子类只能够解决一些简单 的原子性问题
所有原子类的方法都是针对单个共享变量 的,如果需要解决多个变量的原子性问题,还是要采用互斥锁 的方案
参考资料 Java并发编程实战