前言
上一章总结了synchronized相关的对象头和锁机制,这一章就锁的加锁及升级过程总结一下。
偏向锁
首先看一下偏向锁的几个例子:
1 2 3 4 5 6
| public void testOne() throws InterruptedException { String object = "ob"; System.out.println("thread1:" + ClassLayout.parseInstance(object).toPrintable()); Thread.sleep(10000); }
|
上例虽然并没有使用synchronized,但输出结果可以看到,锁标志仍然是’101’,不过表示偏向线程标识的位都是零说明此时并未偏向任何线程。
这种情况可以理解为此时锁处于“可偏向但还未偏向”的状态。
1 2 3 4 5 6 7 8 9 10 11
| public void testTwo() throws InterruptedException { String object = "hello"; System.out.println("master:" + ClassLayout.parseInstance(object).toPrintable()); threadPoolExecutor.execute(() -> { synchronized (object) { System.out.println("thread1:" + ClassLayout.parseInstance(object).toPrintable()); } }); Thread.sleep(10000); }
|
testTwo()就展示了一个常见的单一线程占用锁资源的场景,可以看到开始锁标识是“可偏向但还未偏向”的状态。
而当进入同步代码块后,锁就偏向了当前线程。
加锁过程
- 首次占用
首先,线程会在自己栈帧中存储一份对象头信息,作为CAS操作的期望值,这块空间被称为Lock Word 。如果CAS执行成功,即当前对象是无锁状态,则直接偏向。
- 重入
当尝试占用时,发现偏向线程即为当前线程,则会在线程栈帧中再添加一个Lock Word ,但其存的对象头信息为空,与首次存的Lock Word 区分开。
这个新增的Lock Word 会作为重入的统计标识,每当退出一次则会清空一个Lock Word 表示一次释放。
- 竞争
如果请求占用的锁已偏向且偏向的不是的当前线程,则会判断被偏向的线程是否存活或已释放锁:
a)线程存活且还在占用,则进行轻量级锁的升级
b)线程未存活或不再占用,则会先修改对象头为无锁状态,然后做轻量级锁的升级
偏向锁不会主动释放,当偏向后,对象头就会一直处于偏向的状态,直到下一个来竞争的线程使其升级。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public void test() throws InterruptedException { String object = "hello"; System.out.println("master:" + ClassLayout.parseInstance(object).toPrintable()); threadPoolExecutor.execute(() -> { synchronized (object) { System.out.println("thread1:" + ClassLayout.parseInstance(object).toPrintable()); } }); Thread.sleep(1000); System.out.println("master:" + ClassLayout.parseInstance(object).toPrintable()); threadPoolExecutor.execute(() -> { synchronized (object) { System.out.println("thread2:" + ClassLayout.parseInstance(object).toPrintable()); } }); Thread.sleep(1000); System.out.println("master:" + ClassLayout.parseInstance(object).toPrintable()); Thread.sleep(10000); }
|
轻量级锁
出现轻量级锁会有两种情况,一种是正常的抢占偏向锁导致的升级,第二种就是从无锁状态直接到轻量级锁状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public void testThree() throws InterruptedException { String object = "123"; System.out.println("master:" + ClassLayout.parseInstance(object).toPrintable()); threadPoolExecutor.execute(() -> { synchronized (object) { System.out.println("thread1:" + ClassLayout.parseInstance(object).toPrintable()); } }); Thread.sleep(100); threadPoolExecutor.execute(() -> { synchronized (object) { System.out.println("thread2:" + ClassLayout.parseInstance(object).toPrintable()); } }); Thread.sleep(10000); }
|
1 2 3 4 5 6 7 8 9 10 11
| public void testFour() throws InterruptedException { String object = "Object"; System.out.println("master:" + ClassLayout.parseInstance(object).toPrintable()); threadPoolExecutor.execute(() -> { synchronized (object) { System.out.println("thread1:" + ClassLayout.parseInstance(object).toPrintable()); } }); Thread.sleep(10000); }
|
加锁过程
轻量级锁的加锁过程与偏向锁有点类似,也是基于Lock Word 去做CAS操作。
首先在Lock Word 中存储一个空的Mark Word 对象,其中会存储指向锁对象的指针
然后将这个空的对象头作为期望值去做CAS操作
CAS成功:表示此时是无锁状态,直接占用,对象头中存储指向Lock Word 的指针,此时两者互相指向
CAS失败:会先判断是否重入,是则在栈帧中增加一个Lock Word 表示重入,否则进行锁升级
重量级锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public void testFive() throws InterruptedException { String object = "hello"; System.out.println("master:" + ClassLayout.parseInstance(object).toPrintable()); threadPoolExecutor.execute(() -> { synchronized (object) { System.out.println("thread1:" + ClassLayout.parseInstance(object).toPrintable()); } }); threadPoolExecutor.execute(() -> { synchronized (object) { System.out.println("thread2:" + ClassLayout.parseInstance(object).toPrintable()); } }); Thread.sleep(10000); }
|
重量级锁结构
重量级锁与偏向锁,轻量级锁就有很大的差别了,当升级为重量级锁后,未占用的线程是处于阻塞状态的,他的实现是基于Monitor 的结构。
Monitor 中维护了一个阻塞队列用来存储被阻塞的线程信息,还有一个等待队列,当线程调用wait()方法后,就会释放锁进入等待队列。
总结
为了清楚的理解synchronized中锁升级及加锁流程,也很是看了很久博客和文档。算是大概理出了
加锁流程,不过还有很多细节没有弄清楚。
1. 测试时,定义的String对象内容如果是Object类似的关键字,就会跳过偏向锁,直接升级为轻量
级锁。(这种情况我了解的是当对象头的HashCode有值导致偏向锁偏向标识的空间被占用无法偏向
才会出现)
2. 偏向锁与轻量级锁都会在线程栈帧中Lock Word存储对象头信息用作CAS操作,这里我理解的是
用无锁状态的对象头当作期望值去尝试CAS,这样当对象头被释放时就能CAS成功
3. 理解synchronized的使用其实只要抓住他锁定的是对象就够了,关于锁升级主要是了解什么操作
会导致重量级锁出现,以尽量避免重量级锁
synchronized相关-对象头&锁机制