JUC 整体概览
原子类
- 基本类型-使用原子的方式更新基本类型
- AtomicInteger:整形原子类
- AtomicLong:长整型原子类
- AtomicBoolean :布尔型原子类
- 引用类型
- AtomicReference:引用类型原子类
- AtomicStampedReference:原子更新引用类型里的字段原子类
- AtomicMarkableReference :原子更新带有标记位的引用类型
- 数组类型-使用原子的方式更新数组里的某个元素
- AtomicIntegerArray:整形数组原子类
- AtomicLongArray:长整形数组原子类
- AtomicReferenceArray :引用类型数组原子类
- 对象的属性修改类型
- AtomicIntegerFieldUpdater:原子更新整形字段的更新器
- AtomicLongFieldUpdater:原子更新长整形字段的更新器
- AtomicReferenceFieldUpdater :原子更新引用类形字段的更新器
- JDK1.8新增类
- DoubleAdder:双浮点型原子类
- LongAdder:长整型原子类
- DoubleAccumulator:类似DoubleAdder,但要更加灵活(要传入一个函数式接口)
- LongAccumulator:类似LongAdder,但要更加灵活(要传入一个函数式接口)
原子类实现原理CAS
CAS其实就是执行一个函数:CAS(V,E,N)直到成功为止。
CAS操作涉及到三个操作数:
V:要读写的内存地址
E:进行比较的值 (预期值)这个期望值是每次自旋的时候从V中读取出来,然后更新的时候再比较这个值和V中的值是否相等如果相等表示没有其它线程来修改,否则被修改过,继续自旋。直到成功为止。
N:拟写入的新值
// 以AtomicInteger为例
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;}
compareAndSwapInt是一个native方法:
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
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);//将Java对象解析成JVM的oop(普通对象指针)
//根据对象p内存地址和内存地址偏移量计算拟修改对象属性的地址
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
//基于cas比较并替换,x表示拟更新的值, addr表示要操作的内存地址, e表示预期值 这是一个原子的方法
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
CAS存在的缺陷
主要存在三个缺陷:循环时间太长、只能保证一个共享变量原子操作、ABA问题。
- 循环时间太长:如果CAS一直不成功呢?如果自旋CAS长时间地不成功,则会给CPU带来非常大的开销。
- 原子类AtomicInteger#getAndIncrement()的方法
- 只能保证一个共享变量原子操作:看了CAS的实现就知道这只能针对一个共享变量,如果是多个共享变量就只能使用锁了。
- ABA问题:CAS需要检查操作值有没有发生改变,如果没有发生改变则更新。但是存在这样一种情况:如果一个值原来是A,变成了B,然后又变成了A,那么在CAS检查的时候会发现没有改变,但是实质上它已经发生了改变,这就是所谓的ABA问题。
ABA问题演示
public static void main(String[] args) {AtomicInteger at = new AtomicInteger(100);AtomicStampedReference<Integer> st = new AtomicStampedReference(100, 1);new Thread(() -> {// 值增1int i = at.incrementAndGet();System.out.println("AtomicInteger after modify : " + i);// 撤回修改 模拟 A -> Bint j = at.decrementAndGet();System.out.println("AtomicInteger after modify redo : " + j);// 值增1 版本号 + 1 为 2st.compareAndSet(st.getReference(), st.getReference() + 1, st.getStamp(), st.getStamp() + 1);System.out.println("AtomicStampedReference after modify : " + i + " version is : " + st.getStamp());// 撤回修改 模拟 A -> B 只不过这个时候我们携带了一个版本号 版本号 + 1 为 3st.compareAndSet(st.getReference(), st.getReference() - 1, st.getStamp(), st.getStamp() + 1);System.out.println("AtomicStampedReference after modify redo : " + j + " version is : " + st.getStamp());}).start();new Thread(() -> {// 更新为102可以成功 这个数据其实是被改过一次at.compareAndSet(at.get(), at.get() + 2);System.out.println("AtomicInteger after modify in other Thread: " + at.get());// 更新为102不可以成功 获取的还是100 因为我们预期的版本号是1 也就是第一次的100 所以修改不成功st.compareAndSet(st.getReference(), st.getReference() + 2, 1, st.getStamp() + 1);// 没有修改成功所以版本号还是 3System.out.println("AtomicStampedReference after modify : " + st.getReference() + " version is : " + st.getStamp());}).start();}