synchronized的用法就不细讲了,相信没人不知道的,今天就来剖析一下原理,加强记忆
对象头
synchronized的同步都是基于对象的,而当前对象的锁信息都是存在于对象头当中,我看先来看一下其构成:
其中MarkWord的详细内容如下:
我们来简述一下锁标志位(具体):
01:未加锁或者已经加了偏向锁,由是否是偏向锁的标志位来决定,主要用来提升 “ 某个锁一直重复被某个线程持有 ” 的情况下的性能。
00:轻量级锁,其原理就是通过cas操作去尝试修改锁指针,指向某个线程的栈帧,主要用来提升 “ 某个锁被多个线程交替持有 ” 的情况下的性能。
10:重量级锁,当轻量级锁修改锁指针失败后,代表着有多个线程在争抢这把锁,这时候就会通过对象监视器去调度线程,保证这个锁下面只有一个线程在执行。
偏向锁
在一开始,锁对象的MarkWord锁标志位为01,但是是否是偏向锁的标志位为0,说明这个锁还未被别人持有且还没有偏向。某一个线程在这种情况下想持有锁,就要通过cas操作去改变线程ID那一栏为当前线程并把是否是偏向锁的标志位改为1:
- 成功,则以后某一线程想获取这这把锁,只要对比MarkWord中记录的线程ID是不是当前线程就行了。如果是则执行代码,如果不是则看看是否是偏向锁的标志位为0(未偏向),在未偏向的情况下其他线程可以继续通过cas操作继续修改MarkWord中记录的线程ID,从而继续使用偏向锁;在已偏向的情况下说明有竞争,则升级为轻量级锁。
- 失败,说明有竞争,则升级为轻量级锁。
轻量级锁
升级到轻量级锁的过程是酱紫的:
1.首先会在当前线程的栈帧中建立一块锁记录(Lock Record)的空间,会把MarkWord的内容复制一份到锁记录中。
2.然后Lock Record中有的owner指针指向MarkWord所在的锁对象。
3.修改MarkWord中指向栈中锁记录的指针(见图2的那片区域)。
修改指针都是用的cas操作,成功修改指针后代表该线程已经持有该锁,把锁标志位改为00。失败,则会通过自旋操作尝试获取锁,如果超过自旋的阈值还是获取不到则升级为重量级锁。
可重入是怎么做到的呢?
每重入一次,Lock Record就会在栈中再申请一次,只不过新的Lock Record中的本来会复制过来的MarkWord为null,释放时,也是一层层去释放的,当释放到最后一层时(也就是该栈中最初的Lock Record),会把Lock Record中的MarkWord通过CAS操作替换回锁对象的MarkWord。
重量级锁
升级为重量级锁后,所标志位变为10,且锁指针指向互斥量,互斥量就是依赖于对象监视器,而对象监视器有以下几部分构成:
- Contention List(cxq):竞争队列,在自旋失败后,线程首先会被放到这里面来等待。
- Entry List:锁资源被释放时,会选取少量线程放到这里面作为执行候选。
- Wait Set:调用 wait 方法被阻塞的线程放到这里面。
- OnDeck:当前在争夺锁资源的线程(最多一个)。
- Owner:当前已得到锁资源的线程。
- !Owner:当前释放锁的线程。
1.线程首先通过自旋尝试获取锁,当获取不到锁时,会通过cas操作插入到Contention List的队尾。
2.当owner线程unlock是,会把Contention List中的线程移到Entry List。
3.从Entry List选一个线程进入OnDeck状态,把锁竞争权利交给他,当然他也有可能被还在自旋的线程拿到锁。
4.获取到锁,并且执行。
5.被wait等方法阻塞,该线程释放锁并转移到Wait Set中。
6.被notify等方法唤醒,重新放到Entry List中。