count++
并不是原子操做。由于count++
须要通过读取-修改-写入
三个步骤。count++
并不是原子操做。由于count++
须要通过读取-修改-写入
三个步骤。能够这样作:java
public synchronized void increase() { count++; }
CAS有3个操做数:算法
- 内存值V
- 旧的预期值A
- 要修改的新值B
咱们能够发现CAS有两种状况:数组
- 若是内存值V和咱们的预期值A相等,则将内存值修改成B,操做成功!
若是内存值V和咱们的预期值A不相等,通常也有两种状况:多线程
- 重试(自旋)
- 什么都不作
理解CAS的核心就是:并发
CAS是原子性的,虽然你可能看到比较后再修改(compare and swap)以为会有两个操做,但终究是原子性的!
java.util.concurrent.atomic
包下,整体来看有这么多个
基本类型:高并发
- AtomicBoolean:布尔型
- AtomicInteger:整型
- AtomicLong:长整型
数组:性能
- AtomicIntegerArray:数组里的整型
- AtomicLongArray:数组里的长整型
- AtomicReferenceArray:数组里的引用类型
引用类型:atom
- AtomicReference:引用类型
- AtomicStampedReference:带有版本号的引用类型
- AtomicMarkableReference:带有标记位的引用类型
对象的属性线程
- AtomicIntegerFieldUpdater:对象的属性是整型
- AtomicLongFieldUpdater:对象的属性是长整型
- AtomicReferenceFieldUpdater:对象的属性是引用类型
JDK8新增DoubleAccumulator、LongAccumulator、DoubleAdder、LongAddercode
- 是对AtomicLong等类的改进。好比LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效。
Unsafe里边有几个咱们喜欢的方法(CAS):
// 第一和第二个参数表明对象的实例以及地址,第三个参数表明指望值,第四个参数表明更新值 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);
class Count{ // 共享变量(使用AtomicInteger来替代Synchronized锁) private AtomicInteger count = new AtomicInteger(0); public Integer getCount() { return count.get(); } public void increase() { count.incrementAndGet(); } }
下面的操做均可以正常执行完的,这样会发生什么问题呢??线程C没法得知线程A和线程B修改过的count值,这样是有风险的。
- 如今我有一个变量
count=10
,如今有三个线程,分别为A、B、C- 线程A和线程C同时读到count变量,因此线程A和线程C的内存值和预期值都为10
- 此时线程A使用CAS将count值修改为100
- 修改完后,就在这时,线程B进来了,读取获得count的值为100(内存值和预期值都是100),将count值修改为10
- 线程C拿到执行权,发现内存值是10,预期值也是10,将count值修改为11
要解决ABA的问题,咱们可使用JDK给咱们提供的AtomicStampedReference和AtomicMarkableReference类。简单来讲就是在给为这个对象提供了一个版本,而且这个版本若是被修改了,是自动更新的。
原理大概就是:维护了一个Pair对象,Pair对象存储咱们的对象引用和一个stamp值。每次CAS比较的是两个Pair对象
而LongAdder能够归纳成这样:内部核心数据value分离成一个数组(Cell),每一个线程访问时,经过哈希等算法映射到其中一个数字进行计数,而最终的计数结果,则为这个数组的求和累加。