01 监视器锁(monitor)/重量级锁
monitor对象存在于每个Java对象的对象头中(存储指针的指向),synchronized锁便是通过这种方式获取锁的。
监视器锁本质是依赖于底层操作系统的Mutex Lock(互斥锁)来实现。操作系统实现线程间的切换需要从用户态转换到内核态,成本非常高,状态间的转换需要相对比较长的时间,有可能切换时间比代码执行时间还要长,导致Synchronized效率低,这种依赖于操作系统Mutex Lock实现的锁称为重量级锁
每个对象都有一个monitor与之关联,对象与其monitor间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个monitor被某个线程持有后,它便处于锁定状态。在Java虚拟机中,monitor由C++的ObjectMonitor实现。ObjectMonitor中有两个队列,分别为 _WaitSet和 _EntryList,用来保存ObjectWaiter对象列表(每个等待锁的线程都会被封装成ObjectWaiter对象)。
ObjectWaiter重要属性
_owner 指向持有ObjectMonitor对象的线程
_WaitSet 存放处于wait状态的线程队列
_EntryList 存放处于等待锁阻塞状态的线程队列
_recursions 锁重入次数
_count 记录所对象是否被锁定,count=0表示锁没被占用,同步代码进去+1,出去减1
当线程获取对象锁时,先判断_count是否等于0,等于0说明锁没被占用,就可获取锁,如果大于0,说明被其他线程占用,就进入_EntryList阻塞,当多个线程同时访问一段同步代码时,先会进入 _EntryList集合,当线程获取到对象的monitor后进入 _owner区域并把monitor中的_owner变量设置为当前线程,同时monitor中的计数器count加1,若线程调用wait()方法,将释放当前持有的monitor,_owner变量恢复为null,count减1,同时该线程进入 _WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor并复位变量的值,以便其他线程进入获取monitor。
synchronized底层实现,加在方法上和加在同步代码块中编译后的区别、类锁、对象锁。
对象锁
(1) 这个对象内部得有一个标志位(state变量),记录有没有被某个线程占用
最简单state有0和1两个取值,0表示没有被占用,1表示有某个线程占用了这个锁
(2) 如果这个对象被某个线程占用,记录这个线程的thread ID
(3) 这个对象维护一个thread id list,记录其他所有阻塞的、等待获取拿这个锁的线程。在当前线程释放锁之后从这个thread id list里面取一个线程唤醒
在对象头里,有一块数据叫Mark Word。在64位机器上,Mark Word是8字节(64位)的,这64位中有2个重要字段:锁标志位和占用该锁的thread ID。因为不同版本的JVM实现,对象头的数据结构会有各种差异
对象头是实现synchronized对象锁的基础,synchronized使用的锁对象是存储在Java对象头里,它是轻量级锁和偏向锁的关键
从字节码层面解释,同步代码快
有如下代码
public class Test {
public static void main(String[] args) {
}
Object object = new Object();
public void test() {
synchronized (object) {
System.out.println(object);
}
}
}
先编译,再反编译
E:\java\ideaProjects\jdbcTest\target\classes\com\test>javap -c Test.class
Compiled from "Test.java"
public class com.test.Test {
java.lang.Object object;
public com.test.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class java/lang/Object
8: dup
9: invokespecial #1 // Method java/lang/Object."<init>":()V
12: putfield #3 // Field object:Ljava/lang/Object;
15: return
public static void main(java.lang.String[]);
Code:
0: return
public void test();
Code:
0: aload_0
1: getfield #3 // Field object:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: aload_0
11: getfield #3 // Field object:Ljava/lang/Object;
14: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
17: aload_1
18: monitorexit
19: goto 27
22: astore_2
23: aload_1
24: monitorexit
25: aload_2
26: athrow
27: return
Exception table:
from to target type
7 19 22 any
22 25 22 any
}
从字节码中可以看到有一个监视器进入指令monitorenter和两个监视器退出指令monitorexit
有两个退出一个是正常退出,一个是异常退出,如果代码有异常,则就只会有一个monitorexit
从字节码层面解释–同步方法
如下代码进行反编译
public class Test {
public static void main(String[] args) {
}
public synchronized void test() {
System.out.println("123");
}
}
可以看到如下结构,其中ACC_SYNCHRONIZED就是同步方法的锁
调用指令会先检查方法的ACC_SYNCHRONIZED访问标志知否被设置,如果设置了,必须先持有monitor锁才能先访问
public synchronized void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String 123
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 15: 0
line 16: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/test/Test;
}
从字节码层面解释—同步静态代码块
代码
public class Test {
public static void main(String[] args) {
}
public static synchronized void test() {
System.out.println("123");
}
}
反编译之后,可以看到,相对于同步方法,静态同步方法多了ACC_STATIC
public static synchronized void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String 123
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 15: 0
line 16: 8
}