上一篇文章带大家简单了解了对象头,及mark word的内容,那么本文将来学习,mark word到底有什么作用。其实就是synchronized的原理。
先将64位虚拟机的java对象Mark Word放在这,方便后面查看:
|----------------------------------------------------------------------------------------------|
| Mark Word(64bits) | State |
|----------------------------------------------------------------------------------------------|
| unused:25|identity_hashcode:31|unused:1|age:4|biase_lock:0| lock:01 | Nomal |
|----------------------------------------------------------------------------------------------|
| thread:54| epoch:2 |unused:1|age:4|biase_lock:1| lock:01 | Biased |
|----------------------------------------------------------------------------------------------|
| ptr_to_lock_record:62 | lock:00 | Lightweight Locked |
|----------------------------------------------------------------------------------------------|
| ptr_to_heavyweight_monitor:62 | lock:10 | Heavyweight Locked |
|----------------------------------------------------------------------------------------------|
| | lock:11 | Marked for GC |
|----------------------------------------------------------------------------------------------|
一、Monitor
在介绍Mark Word的时候,提到过Monitor,可以称为监视器或管程,下面我们看下它到底是个什么东西。
每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的Mark Word 中就被设置指向 Monitor 对象的指针。不加 synchronized 的对象不会关联Monitor。
首先通过下图展示管程的组成,以及和java对象的关系:
有一个java对象,上了一把synchronized(重量级锁),当有线程获取到锁时,其对象头中的Mark Word变成了指向Monitor的指针。(原本mark word当中的内容会存储到Monitor当中,释放时会取出这些内容再次放到mark word。)
thread3 来竞争这把锁,此时只有它自己,那么thread3将会被设置为Monitor的Owner,有且只能有一个Owner。
如果thread3持有锁的过程中,如果thread4和thread5也来竞争锁,就会添加到EntryList当中,此时线程将被阻塞(BLOCKED)。
当thread执行完同步代码块当中的内容,会唤醒EntryList当中的线程来竞争锁,此竞争是非公平的。
另外,在WaitSet当中的thread1和thread2,其状态是WAITING,表示他们之前获得过锁,但是条件不满足,此处不讲解,后面在分析wait,notify时讲解。
二、synchronized原理分析
2.1 synchronized字节码分析
在上面了解Monitor后,进入java当中的重点,synchronized的学习。
有如下代码:
static final Object object = new Object();
static int i = 0;
public static void main(String[] args) {
synchronized (object) {
i++;
}
}
其字节码如下所示:
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // 获取锁对象object
3: dup // 拷贝一份
4: astore_1 // 将拷贝的变量存储到 slot 1中
5: monitorenter // 将Monitor指针设置到lock对象的Mark Word
6: getstatic #3 // 获取静态变量i
9: iconst_1 // 准备常数 1
10: iadd // 执行++ 操作
11: putstatic #3 // 将i当前值赋值给静态变量
14: aload_1 // 获取对象锁
15: monitorexit // 重置对象的Mark Word,唤醒EntryList当中的线程
16: goto 24 // 跳转到24 行
19: astore_2 // 将异常存储到 slot 2 中
20: aload_1 // 获取锁对象
21: monitorexit // 重置对象的Mark Word,唤醒EntryList当中的线程
22: aload_2 // 获取slot 2中的异常
23: athrow // 抛出异常
24: return // 方法返回
Exception table:
from to target type
6 16 19 any // 6 ~ 16 行为正常代码运行逻辑,如果在这之间发生了异常,则代码跳转值第19行
19 22 19 any // 19 ~ 22 是异常时,代码执行的逻辑
关于上面字节码的意思都在注释中。
注意:在方法层面上的锁,在字节码当中不会有锁体现,如下:
static final Object object = new Object();
static int i = 0;
public static synchronized void add() {
i++;
}
public static void main(String[] args) {
add();
}
字节码如下:
public static synchronized void add();
Code:
0: getstatic #2 // Field i:I
3: iconst_1
4: iadd
5: putstatic #2 // Field i:I
8: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #3 // Method add:()V
3: return
2.2 轻量级锁
如开篇展示的对象Mark Word,共有5种锁的状态, 我们本小节讲解其中之一,轻量级锁相关的内容。
轻量级锁是指在满足一定的条件内,使用CAS(自旋)来尝试获取对象锁的一种机制,如果超过以下条件,则会膨胀为重量级锁:
1)在jdk1.6前,默认10次,可通过-XX:PreBlockSpin来修改,或者自旋线程数超过CPU核数的一半。
2)jdk1.6之后,引入了自适应自旋锁,次数并非一成不变。根据获取锁的成功率来决定是否能有更长的等待时间。
轻量级锁仍然是使用synchronized,用户其实是无感知的。
2.2.1 轻量级锁的上锁和释放锁
假设当前有一把对象锁lock,两个方法使用同一把锁,且add当中会调用sub()方法,如下所示:
static final Object lock = new Object();
public void add(){
synchronized (lock){
sub();
}
}
public void sub(){
synchronized (lock){
}
}
- 当线程尝试获取这把锁的时候,会创建锁记录(Lock Record),每个线程的栈帧(线程执行到的方法,都会生成对应的栈帧),都会包含一个锁记录的结构,可以用来存储对象的Mark Word,如下所示:
- 让锁记录中 Object Reference 指向锁对象,并尝试用 CAS 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录(如图上的1),并将锁记录的地址存入Object的Mark Word(如图上的2),如下所示:
- 如果CAS成功,对象的Mark Word将会存储Lock Record 地址 和 锁状态 00,如下图所示:
- 如果CAS失败,此时会有两种情况:
- 如果是其他线程已经持有了该轻量级锁,则表示发生竞争,此时进入锁膨胀。
- 如果是自己执行了 synchronized 锁重入(就像我们示例代码中的add()方法),那么再添加一条 Lock Record 作为重入的计数,只不过新添加的Lock Record中没有Object的Mark word内容,为null。如下所示:
当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一,将null的Lock Record删除。
-
当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 CAS 将 Mark Word 的值恢复给对象头。
- 成功,则解锁成功。
- 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程。