
JUC-03_CAS.Unsafe和原子类
-
线程安全的实现方法有哪些?
-
什么是CAS?
-
CAS使用示例,结合AtomicInteger给出示例?
-
CAS会有哪些问题?
-
针对这这些问题,Java提供了哪几个解决的?
-
AtomicInteger底层实现? CAS+volatile
-
请阐述你对Unsafe类的理解?
-
说说你对Java原子类的理解? 包含12个,4组分类,说说作用和使用场景。
-
AtomicStampedReference是什么?
-
AtomicStampedReference是怎么解决ABA的? 内部使用Pair来存储元素值及其版本号
-
java中还有哪些类可以解决ABA的问题? AtomicMarkableReference
线程安全的实现方法
-
阻塞同步:加互斥锁,synchronized,Reentrant Lock
-
非阻塞同步:CAS,atomicXXX
-
无同步方案:栈封闭,ThreadLock,可重入代码
什么是CAS?
全称CompareAndSwap,对比交换,是一条cpu级别的原子指令,基于汇编平台的原子指令也就是CAS靠硬件实现的,传入期望值和修改值,期望值和原来的旧值对比,若一致,替换成修改值,返回修改之前的值。
用CAS我们可以实现无锁级别的方案,较之互斥锁如synchronized,CAS大幅提高并发效率。
若没有CAS,我们在并发情况下多个线程同时修改一个变量是需要synchronized加锁的
-
cas是乐观锁的一种实现
-
synchronized是悲观锁的一种思想
java还提供了AtomicXXX原子类,底层就是基于cas实现变量的修改,所以具备原子性,在并发场景下可以安全使用。
ABA问题
CAS是比较期望值,拿期望值跟原来的旧值相比较,若相同则替换为新值,不同则不替换,但是他无法感知两次操作替换成了一样的值之间的问题
-
A替换为B
-
B替换为A
-
CAS无法感知期间的B,仍然认为A还是A,期间没有发生变化。
解决:提供版本号,在每次CAS操作时带上版本号,版本号+1,这样就是1A 2B 3A
jdk1.5开始,提供AtomicStampedReference来解决ABA问题,在每次CAS时比较二者引用,随后比较预期标志,若全部相等,则原子操作同时替换引用和标志。
自旋使用CAS导致开销大
轻量级锁的自旋实现依赖CAS操作,但是过多的循环次数会给cpu带来非常大的执行开销。jvm若支持pause指令效率有一定提升,
只能保证一个共享变量的原子性操作
CAS只能保证单个值的原子性操作,若想比较多个值,可以使用AtomicReference类保证引用对象的原子性
Unsafe类
sun.misc包下的一个类,主要执行一些低级别,不安全操作的类,在旧版jdk无法直接使用它们,现在可以用反射来创建一个Unsafe对象,
**作用:**提供直接访问系统内存资源,自主管理内存资源等,广泛运用于CAS
Unsafe类如其名,都是一些不安全的操作,用错可能导致boom

Unsafe类中通过while循环不断尝试CAS操作来更新(自旋)
且只提供三个CAS操作方法
public final native boolean compareAndSwapObject(Object paramObject1,long paramLong,Object paramObject2,Object paramObject3);
public final native boolean compareAndSwapInt(Object paramObject,long paramLong,int paramInt1,int paramInt2);
public final native boolean compareAndSwapLong(Object paramObject,long paramLong1,long paramLong2,long paramLong3);
Unsafe底层:调用Atomic::cmpxchg
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
在windows实现下会判断是否是多处理器,若是,添加lock前缀来直接进行一个处理器的总线锁,(多处理器一般用缓存锁替代总线锁)
更多功能参考:来自美团技术团队:https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html
-
操作堆外内存:DirectByteBuffer:NIO广泛实现
-
Unsafe.allocateMemory分配内存
-
Unsafe.setMemory进行内存初始化
-
而后构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,分配的堆外内存一起被释放。
-
(期间创建Cleaner对象来辅助释放堆外内存还涉及到虚引用和引用队列的使用)
-
-
CAS原子操作
-
线程调度:挂起,恢复,锁机制等实现方法
-
Class相关:主要提供Class和它的静态字段的操作相关方法,包含静态字段内存定位、定义类、定义匿名类、检验&确保初始化等。
-
对象操作:此部分主要包含对象成员属性相关操作及非常规的对象实例化方式等相关方法。
-
数组相关:这部分主要介绍与数据操作相关的arrayBaseOffset与arrayIndexScale这两个方法,两者配合起来使用,即可定位数组中每个元素在内存中的位置。
-
内存屏障:避免代码重排序(volatile,synchronized),StampedLock(还记得它吗,可实现乐观锁和悲观锁,乐观锁缓解线程饥饿现象,其中对乐观锁校验锁的操作前就需要使用内存屏障,保证数据一致性。)
-
系统相关:包含获取系统信息的方法:NIO使用
//返回系统指针的大小。返回值为4(32位系统)或 8(64位系统)。
public native int addressSize();
//内存页的大小,此值为2的幂次方。
public native int pageSize();
原子类
AtomicInteger常用API
public final int get():获取当前的值
public final int getAndSet(int newValue):获取当前的值,并设置新的值
public final int getAndIncrement():获取当前的值,并自增
public final int getAndDecrement():获取当前的值,并自减
public final int getAndAdd(int delta):获取当前的值,并加上预期的值
void lazySet(int newValue): 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
使用Atomic原子类的好处是可以直接保证并发原子性,因为它们底部都是采用了CAS机制来保证了数据一致。
在并发场景下,共享变量的自增 i++ 操作并不能保证原子性,因为它们的指令实际上是三个,就算加了volatile也只能保证可见性和变量初始化时的禁重排。
对于i++操作的指令无法禁重排,只能加synchronized
i++
-
读值
-
值+1
-
赋值
但是用了Atomic原子类就可以保证。
Atomic底层是使用volatile和CAS来保证数据的原子性
-
volatile保证数据并发下立即可见
-
CAS保证数据更新操作原子
-
基本类型(参考AtomicInteger)
-
AtomicInteger
-
AtomicBoolean
-
AtomicLong
-
-
数组类型(常用get(int index) 和 compareAndSet(int i,E expect,E update),符合预期设置到数组[i]位置)
-
AtomicIntegerArray
-
AtomicLongArray
-
AtomicReferenceArray
-
使用:
import java.util.concurrent.atomic.AtomicIntegerArray;
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
AtomicIntegerArray array = new AtomicIntegerArray(new int[] { 0, 0 });
System.out.println(array);
System.out.println(array.getAndAdd(1, 2));
System.out.println(array);
}
}
-
引用类型
-
AtomicReference
-
AtomicStampedReference:内部使用Pair来存储元素值及其版本号
-
-
AtomicMarkableReference:原子更新带有标记位的引用类型
使用:
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
public static void main(String[] args){
// 创建两个Person对象,它们的id分别是101和102。
Person p1 = new Person(101);
Person p2 = new Person(102);
// 新建AtomicReference对象,初始化它的值为p1对象
AtomicReference ar = new AtomicReference(p1);
// 通过CAS设置ar。如果ar的值为p1的话,则将其设置为p2。
ar.compareAndSet(p1, p2);
Person p3 = (Person)ar.get();
System.out.println("p3 is "+p3);
System.out.println("p3.equals(p1)="+p3.equals(p1));
}
}
class Person {
volatile long id;
public Person(long id) {
this.id = id;
}
public String toString() {
return "id:"+id;
}
}
-
原子更新类
-
AtomicIntegerFieldUpdater
-
AtomicLongFieldUpdater
-
AtomicReferenceFieldUpdater
-
原子更新类都是抽象类,基于反射来原子地更新字段的值
-
每次使用必需使用静态方法newUpdater()创建一个更新器,并且设置想要更新的类和属性
-
更新类的字段必需用public volatile修饰
-
不能修改static和final的字段
-
AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改基本类型int/long
-
Integer/Long等包装类类型只能用AtomicReferencefieldUpdater
AtomicStampedReference解决ABA
内部维护一个Pair对象,Pair对象包括一个对象引用,和一个int型的版本号
在CAS中:
-
比较Pair对象,若引用和版本号都没变,则代表CAS成功,创建一个新的Pair对象引用。
-
调用Unsafe类的compareAndSwapObject来更新整个Pair对象为新的引用。