- CAS
- 对象在内存中的布局
- MarkWord 信息
- synchronized 锁升级
- ObjectMonitor
- 其他概念
- 相关问题
compare and swap,比较和交换
线程基于 CAS 修改数据的方式:先获取主内存数据,在修改之前,先比较数据是否一致,如果一致修改主内存数据,如果不一致,放弃这次修改
存在的问题
- CAS 只能对一个变量的修改实现原子性,一段代码中多个变量则无法保证未经 CAS 修饰的变量的原子性
- 存在 ABA 问题(解决方案:加版本号)
- CAS 执行次数过多,但是依旧无法实现对数据的修改,CPU 会一直调度这个线程,造成对 CPU 的性能损耗(轻量锁不一定比重量锁好的原因)
- MarkWord 对象标记:
包含一系列的标记位,比如轻量级锁的标记位、偏向锁标记位、gc记录信息等等。在32位系统占4字节,在64位系统中占8字节。 - Class Point:
用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在32位系统占4字节,在64位系统中占8字节。 - 实例数据(对象里面的属性)
- 补齐填充(会补齐为 8 的整数倍,缓存行对齐)
涨姿势啦!Java程序员装X必备词汇之Mark Word!
在 java 中查看对象布局
MarkWord 信息Object o = new Object(); System.out.println(ClassLayout.parseInstance(o).toPrintable()); org.openjdk.jol jol-core 0.9
上面图片中红色框记录了MarkWord 的信息,看标志位 01 对照上面的表可以看到当前对象处于无锁态
synchronized 锁升级-
偏向锁是否启动:偏向锁是延迟开启的,并且在开启偏向锁之后,默认不存在无锁状态
- synchronized在偏向锁升级到轻量锁时,会涉及到偏向锁撤销,需要等到一个安全点,stw,才可以撤销,并发偏向锁撤销比较消耗资源
- 在程序启动时,偏向锁有一个延迟开启的操作,因为项目启动时,ClassLoader会加载.class文件,这里会涉及到synchronized操作,
- 为了避免启动时,涉及到偏向锁撤销,导致启动效率变慢,所以程序启动时,默认不是开启偏向锁的。
// 关闭延迟开启偏向锁 -XX:BiasedLockingStartupDelay=0
-
无锁态、匿名偏向:没有线程拿锁。
-
偏向锁:没有线程的竞争,只有一个线程再获取锁资源。
线程竞争锁资源时,发现当前synchronized没有线程占用锁资源,并且锁是偏向锁,使用CAS的方式,设置o的线程ID为当前线程,获取到锁资源,下次当前线程再次获取时,只需要判断是偏向锁,并且线程ID是当前线程ID即可,直接获得到锁资源。 -
轻量级锁:偏向锁出现竞争时,会升级到轻量级锁(触发偏向锁撤销)。一个线程竞争到了(记录Lock Record)其他线程开始自旋
轻量级锁的状态下,线程会基于CAS的方式,尝试获取锁资源,CAS的次数是基于自适应自旋锁实现的,JVM会自动的基于上一次获取锁是否成功,来决定这次获取锁资源要CAS多少次。 -
重量级锁:轻量级锁CAS一段次数后,没有拿到锁资源,升级为重量级锁(其实CAS操作是在重量级锁时执行的)。
重量级锁就是线程拿不到锁,进入等待队列,不需要消耗 CPU 资源。
涉及 ObjectMonitor 一般是到达了重量级锁才会涉及到。在到达重量级锁之后,重量级锁的指针会指向ObjectMonitor 对象。
ObjectMonitor() {
_header = NULL;
_count = 0; // 抢占锁资源的线程个数
_waiters = 0, // 调用wait的线程个数。
_recursions = 0; // 可重入锁标记,
_object = NULL;
_owner = NULL; // 持有锁的线程
_WaitSet = NULL; // wait的线程 (双向链表)
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ; // 假定的继承人(锁释放后,被唤醒的线程,有可能拿到锁资源)
_cxq = NULL ; // 挂起线程存放的位置。(单向链表)
FreeNext = NULL ;
_EntryList = NULL ; // _cxq会在一定的机制下,将_cxq里的等待线程扔到当前_EntryList里。 (双向链表)
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
地址:
http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/runtime/objectMonitor.hpp
编译器优化:
锁消除:线程在执行一段 synchronized 代码块时,发现没有共享数据的操作,自动帮你把 synchronized 去掉。
锁膨胀:在一个多次循环的操作中频繁的获取和释放锁资源,synchronized 在编译时,可能会优化到循环外部。
- 单例模式下DCL是否需要加 volatile (半初始化,指令重排)
需要
class T{
int n = 1;
}
T t = new T();
正常创建过程:
(1)申请一块内存空间,new 对象,此时 n 为初始值 0
(2)调用构造方法,n = 1
(3)t 与 新建的对象建立关联
申请内存,初始化,关联是正常顺序,如果CPU对指令重排,可能会造成
申请内存,关联,初始化,在还没有初始化时,其他线程来获取数据,导致获取到的数据虽然有地址引用,但是内部的数据还没初始化,都是默认值,导致使用时,可能出现与预期不符的结果
//DCL 双重校验加锁
public class Single {
private volatile static Single instance ; //volatile 修饰,防止指令重排
private Single(){}
public static Single getInstance(){
if(instance==null){
synchronized (Single.class){
if(instance==null){
instance = new Single();
}
}
}
return instance;
}
}



