synchronized底层原理与Monitor密切相关
1.Java对象头
以 32 位虚拟机为例
普通对象
对象的类型,如Student类型,Teacher类型等是由KlassWord来表示的,它是一个指针,指向了对象所从属的Class。即找到类。
其中 Mark Word 结构为
其中,age表示垃圾回收时的分代年龄,具体可见分代收集算法。年龄超过一定岁数就从幸存区调入到老年代。最后两位,biased_lock和01表示偏向锁和加锁状态。其它表示在不同状态下每一位所代表的含义。Normal状态表示对象的正常状态。对它加各种所的时候,其值通常会改变。
数组对象
注意:所以,这里我们也可以想到,在一个Integer对象里,要保存8个字节的对象头,还有4个字节的int类型的数据。总共12字节,而基本数据类型int则只需4个字节。在内存敏感的情况下,建议用基本类型。
2.Monitor(锁)工作原理
synchronized底层原理可以用Monitor工作原理来解释
Monitor被翻译为监视器或者管程
每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的
Mark Word 中就被设置指向 Monitor 对象的指针
当我们线程2执行如下代码时:
synchronized(this){
//处理相关业务
}
1.Thread2线程执行上述代码时,当前对象this会被上一把锁,这是一把重量级锁,this对象头的Mark Word字段指向了操作系统创建的Monitor对象引用地址
MarkWord在没有加任何锁的时候,即Normal状态,标记位为01,一旦获取了锁,就会尝试找一个monitor与之关联,然后把最后两位也即标记位从01改为10,并且把前面的所有位改成指向monitor对象的指针,占用30位。此时由于只有Thread2线程,所以成功获取了锁。理所应当的成为了monitor对象的owner。
owner表示Monitor锁的持有者,而且同一个时刻只能有一个owner
2.Monitor对象只能有一个owner,此时如果有其它线程如Thread-3或Thread-4等线程要获取这把锁就要进入Monitor对象的堵塞队列EntryList中等待Thread2释放锁。
EntryList可以理解位阻塞队列或等待队列。一直等待到其它线程释放了owner的所有权
3.等待锁资源被释放后,Thread-3或Thread-4会互相竞争锁资源,并不能保证谁获取到锁,最终还是有CPU来决定。
4.Monitor对象的WaitSet存放的是,获取到锁的线程,但是由于其它一些原因导致线程进入Waiting状态,又释放了锁资源,待介绍
同样地,对于下图
1.刚开始 Monitor 中 Owner 为 null
2.当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一个 Owner
3.在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入EntryList BLOCKED
4.Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的
5.图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足进入 WAITING 状态的线程,后面讲wait-notify 时会分析
synchronized 必须是进入同一个对象的 monitor 才有上述的效果
不加 synchronized 的对象不会关联监视器,不遵从以上规则
3.字节码角度理解synchronized底层工作原理
public class synchronized1 {
static final Object lock=new Object();
static int counter=0;
public static void main(String[] args){
synchronized (lock){
counter++;
}
}
}
反编译成字节码,具体反编译过程及字节码指令介绍可以参看JVM学习-字节码指令
字节码如下:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: getstatic #2 //<-lock引用(synchronized开始)
3: dup
4: astore_1 //lock引->slot1
5: monitorenter //将lock对象Markword置为monitor指针
6: getstatic #3 // Field counter:I
9: iconst_1
10: iadd
11: putstatic #3 // Field counter:I
14: aload_1 //<-lock引用,拿到刚开astore1刚才存储的临时变量
15: monitorexit //将lock对象MarkWord重置,唤醒EntryList.重新设置markword其它字段,让entryList中正在等待的线程竞争锁
16: goto 24
/*
如果同步代码块中发生了异常,然后就处理下列代码,即19-23行。
最下面的Exception table表第一行时监测6-16行是否发生了异常,即同步代码块中的代码。
如果发生了异常,就跳转到19行执行。
先把异常对象存储进来,然后根据对象引用地址找到monitor,然后也是做一些善后的工作。
把monitor中的状态还原,并唤醒entrylist中的其它线程
*/
19: astore_2
20: aload_1
21: monitorexit
22: aload_2
23: athrow
24: return
Exception table:
from to target type
6 16 19 any
19 22 19 any