1. sync 实现原理
核心的话,就是 monitor ;
monitor 是c 的结构体,对应到java 可以理解为一个类;
monitor 属性如下:
ObjectMonitor() {
_count = 0;
_owner = NULL;//指向获得ObjectMonitor对象的线程或基础锁
_EntryList = NULL ;//处于等待锁block状态的线程,会被加入到entry set;
_WaitSet = NULL;//处于wait状态的线程,会被加入到wait set;
_WaitSetLock = 0 ;
_header = NULL;//markOop对象头
_waiters = 0,//等待线程数
_recursions = 0;//重入次数
_object = NULL;//监视器锁寄生的对象。锁不是平白出现的,而是寄托存储于对象中。
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;// _owner is (Thread *) vs SP/BasicLock
_previous_owner_tid = 0;// 监视器前一个拥有者线程的ID
}
多线程并发时, monitor 会先将线程加入等待队列,获取锁的线程 会把 monitor 的线程属性设置成自己的;
然后获取锁失败的线程 就会被加载到 monitor 的等待队列里面;
持有锁的线程释放锁后,随机唤醒一个线程;
2. sync使用
作用范围:静态代码块、方法
写法如下:
Object lock = new Object();
synchronised(lock){
// 业务代码
}
多线程对锁的竞争,就是对共享资源lock 的竞争; 抢夺lock 对象monintor ,修改锁标识;
对象的内存布局
3. 锁升级
偏向锁
锁竞争中,同一个线程获得锁的概率大,为了让获取锁的代价更低,引入了偏向锁;
当一个线程获取到锁,会在对象头中存储线程id;后续这个线程进入和退出代码块时,不需要再次加锁和释放锁;
偏向锁的获取和撤销
1)获取锁的对象,判断是否处于可偏向的状态 ( lock_1, 且 threadId 为 null )
2)可以偏向,通过cas ,把当前线程id 写入 Markword;
- 写入成功,锁对象升级为偏向锁
- 写失败,说明其他线程已经获取到锁,锁升级为轻量级锁(这个操作需要等到全局安全点,也就是没有线程在执行字节码)
3)如果是已偏向状态,需要检查 markword 中存储的ThreadID 是否等于当前线程的 ThreadID
-
- 如果相等,不需要再次获得锁,可直接执行同步代码块
-
- 如果不相等,说明当前锁偏向于其他线程,需要撤销偏向锁并升级到轻量级锁
轻量级锁
轻量级锁设置失败,会升级为轻量级锁;
相比于偏向锁,轻量级锁会再当线程 创建 锁记录, 将对象头中的锁记录指向当前线程;
也就是说偏向锁比较的是 线程id是否与当前线程一致; 轻量级锁比较的是,锁对象的指针是否指向自己;
重量级锁
轻量级锁升级为重量级锁后,就只能被挂起阻塞来等待唤醒了;
锁的转化
注意点:
-
1)锁的升级是不可逆向的
-
2)偏向锁cas替换threadId 失败后情况?
-
3)轻量级锁到重量级锁的转化?
-
4)轻量级锁和偏向锁都有CAS操作,两者区别?