CAS、synchronized和volatile
一.CAS
CAS
native方法。修改一个值,当前为0,现在要加一,在写回的时候,判断该变量是否还是0,。
ABA问题
CAS会有一个问题,如果该变量还是0,不一定代表他没有被人修改过。比如另一个线程对他加2,然后又被减2,虽然最后还是0,但是他不是最开始的那个0.
解决办法:
- 可以加一个bool表示是否修改过
- 加一个版本号
CAS底层汇编实现
用 AtomicInteger 一步一步查到最后。
java native代码->虚拟机jvm的c++代码->linux的汇编代码lock cmpxchg
lock的意思是后面的指令不能被其他CPU打断,这样就能保证在cmp的时候值是不变的。
jdk1.8 Unsafe类
Unsafe类里面有很多CAS方法,以AtomicInteger用到的getAndAddInt为例。
1 | public final int getAndAddInt(Object var1, long var2, int var4) { |
注意单纯的compareAndSwapInt不会循环,只会compare一次,并返回bool值。
1 | public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); |
二.Synchronized
对象内存布局
对象内存=markword(锁信息 8字节)+class pointer(类型指针,表示属于哪个类 4字节)+instance data(实例数据)+padding(对齐 需要为8的整数倍)。所以new一个什么都没有的Object,new Object()是16个字节(8(markword) + 4(class pointer) + 4(padding))。
注意64位虚拟机开启压缩之后class pointer是4字节,不压缩是8字节
示例
以user{int id,string name}为例
markword 8
classpointer 4(压缩)
int 4
string 4(压缩)
padding 4
所以一共是24字节
markword+class pointer是对象头。
synchronized加锁过程
首先synchronized是锁住对象,不是锁住代码块。
synchronized的锁自动升级过程:
new Obj(无锁)->偏向锁->轻量级锁(自旋锁,自适应自旋)->重量级锁

锁降级的过程:
来源:知乎
重量级锁的降级发生于STW阶段,降级对象就是那些仅仅能被VMThread访问而没有其他JavaThread访问的对象。也就是说只有GC的时候才降级,那对象都没了,降级不降级也没有意义了。
汇编语言实现方式:lock cmpxchg
锁消除
1 | public String test(String a,String b) { |
上面这段代码在实际运行时,JVM会检测出加锁对象都在一个方法里面,所以为了避免反复加锁,JVM不会加上锁。
锁粗化
1 | public String test(String a,String b) { |
JVM检测到这样的代码,循环的反复加锁解锁,JVM会把加锁操作放到循环体外,这样只用加一次锁。
三.volatile
volatile有两个作用:
- 保证线程可见性
- 防止指令重排序
防止指令重排序的实现
JVM的规范是用内存屏障。
在读之前加入一个读屏障,在写之前加入一个写屏障,屏障会保证所有之前写操作都已经结束,并且更新过的数据可见,因为屏障会把数据刷新到缓存,所有线程读到的都是最新的。
单例模式中有这段代码:
1 | if (singleton != null) { |
对singleton加上volatile是防止第二次检查时候,new指令会发生重排序。
new在CPU看来分成三步
- 1、分配空间
- 2、初始化
- 3、把引用赋值
下面是个人理解,网上没有找到说的很明白的:
指令重排序会导致先3后1,另一个线程在第一层检查就会直接return,这样单例得到的对象没有被初始化,就会出现问题。
有了内存屏障,会保证第一层检查之前,singleton的所有写操作全部结束,也就是说初始化singleton的线程会把那三步执行完。这样,即使指令重排序了,也能保证得到的结果是最新的。