目录
- 1.它做了什么
- 2.什么是Monitor
- 如何减少用户态和内核态的切换
- 3.对象头和内置锁 (ObjectMonitor)
- 3.1对象头
- 3.2内置锁 (ObjectMonitor)
- 3.3wait方法底层
- 3.4notify 方法的底层实现
- 4.总结
1.它做了什么
使用synchronized的修饰的代码块如下,那么jvm是如何编译它的,做了什么,我们可以通过 javap -v
进行反编译下;
public class MyTest1 {
public void method1() throws InterruptedException {
Object object = new Object();
synchronized (object){
System.out.println("mingJing is the most handsome");
}
}
public void method2() throws InterruptedException {
Object object = new Object();
synchronized (object){
System.out.println("mingJing is the most handsome");
throw new RuntimeException();
}
}
public synchronized void method3(){
System.out.println("mingJing is the most handsome");
}
}
编译结果如下
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/bin/javap -v /Users/mingjing/Documents/project/java_concurrency/build/classes/java/main/com/mingjing/concurrency/MyTest1.class
Classfile /Users/mingjing/Documents/project/java_concurrency/build/classes/java/main/com/mingjing/concurrency/MyTest1.class
Last modified 2021-7-2; size 1053 bytes
MD5 checksum 984cf67a767e9074756355f0028e7b22
Compiled from "MyTest1.java"
public class com.mingjing.concurrency.MyTest1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #2.#29 // java/lang/Object."<init>":()V
#2 = Class #30 // java/lang/Object
#3 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream;
#4 = String #33 // mingJing is the most handsome
#5 = Methodref #34.#35 // java/io/PrintStream.println:(Ljava/lang/String;)V
#6 = Class #36 // java/lang/RuntimeException
#7 = Methodref #6.#29 // java/lang/RuntimeException."<init>":()V
#8 = Class #37 // com/mingjing/concurrency/MyTest1
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/mingjing/concurrency/MyTest1;
#16 = Utf8 method1
#17 = Utf8 object
#18 = Utf8 Ljava/lang/Object;
#19 = Utf8 StackMapTable
#20 = Class #37 // com/mingjing/concurrency/MyTest1
#21 = Class #30 // java/lang/Object
#22 = Class #38 // java/lang/Throwable
#23 = Utf8 Exceptions
#24 = Class #39 // java/lang/InterruptedException
#25 = Utf8 method2
#26 = Utf8 method3
#27 = Utf8 SourceFile
#28 = Utf8 MyTest1.java
#29 = NameAndType #9:#10 // "<init>":()V
#30 = Utf8 java/lang/Object
#31 = Class #40 // java/lang/System
#32 = NameAndType #41:#42 // out:Ljava/io/PrintStream;
#33 = Utf8 mingJing is the most handsome
#34 = Class #43 // java/io/PrintStream
#35 = NameAndType #44:#45 // println:(Ljava/lang/String;)V
#36 = Utf8 java/lang/RuntimeException
#37 = Utf8 com/mingjing/concurrency/MyTest1
#38 = Utf8 java/lang/Throwable
#39 = Utf8 java/lang/InterruptedException
#40 = Utf8 java/lang/System
#41 = Utf8 out
#42 = Utf8 Ljava/io/PrintStream;
#43 = Utf8 java/io/PrintStream
#44 = Utf8 println
#45 = Utf8 (Ljava/lang/String;)V
{
public com.mingjing.concurrency.MyTest1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/mingjing/concurrency/MyTest1;
public void method1() throws java.lang.InterruptedException;
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: aload_1
9: dup
10: astore_2
11: monitorenter
12: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #4 // String mingJing is the most handsome
17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: aload_2
21: monitorexit
22: goto 30
25: astore_3
26: aload_2
27: monitorexit
28: aload_3
29: athrow
30: return
Exception table:
from to target type
12 22 25 any
25 28 25 any
LineNumberTable:
line 11: 0
line 12: 8
line 13: 12
line 14: 20
line 15: 30
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 this Lcom/mingjing/concurrency/MyTest1;
8 23 1 object Ljava/lang/Object;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 25
locals = [ class com/mingjing/concurrency/MyTest1, class java/lang/Object, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
Exceptions:
throws java.lang.InterruptedException
public void method2() throws java.lang.InterruptedException;
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: aload_1
9: dup
10: astore_2
11: monitorenter
12: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #4 // String mingJing is the most handsome
17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: new #6 // class java/lang/RuntimeException
23: dup
24: invokespecial #7 // Method java/lang/RuntimeException."<init>":()V
27: athrow
28: astore_3
29: aload_2
30: monitorexit
31: aload_3
32: athrow
Exception table:
from to target type
12 31 28 any
LineNumberTable:
line 18: 0
line 19: 8
line 20: 12
line 21: 20
line 22: 28
LocalVariableTable:
Start Length Slot Name Signature
0 33 0 this Lcom/mingjing/concurrency/MyTest1;
8 25 1 object Ljava/lang/Object;
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 28
locals = [ class com/mingjing/concurrency/MyTest1, class java/lang/Object, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
Exceptions:
throws java.lang.InterruptedException
public synchronized void method3();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #4 // String mingJing is the most handsome
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 26: 0
line 27: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/mingjing/concurrency/MyTest1;
}
SourceFile: "MyTest1.java"
Process finished with exit code 0
在每个方法中都会有个monitorenter和monitorexit配套使用,有时monitorexit可能会有多个。
2.什么是Monitor
JVM的同步,是基于进入与退出监视器对象(Monitor)来实现的,也即是管程;每个对象实例都会有个Monitor对象,这是JVM分配的。Monitor对象会和java对象一同创建并销毁。Monitor对象是由C++来实现的。
当多个线程同时访问一段同步代码时,这些线程会被放到一个EntryList集合中,处于堵塞状态的线程都会被放到该列表当中(wait方法可查)。接下来,当线程获取到对象的Monitor时,Monitor是依赖于底层操作系统的mutex lock来实现互斥的,线程获取mutex成功,则会持有该mutex,这时其他线程无法再获取到该mutex。
如果线程调用了wait方法,那么该线程就会释放掉所有的mutex,并且该线程会进入到WaitSet集合中,等待下一次被其他线程调用notify或者notifyAll唤醒。如果当前线程顺利执行完毕,那么它也会释放掉所持有的mutex。
总结一下:同步锁在这种实现方式当中,因为Monitor是依赖于底层的操作系统实现,线程被阻塞后便会进入到内核调度状态,这样就存在用户态与内核态之前的切换,所以会增加性能开销。通过对象互斥锁的概念来保证共享数据操作的完整性。每个对象都对应于一个可称为「互斥锁」的标记,这个标记用于保证在任何时刻,只能有一个线程访问该对象。
如何减少用户态和内核态的切换
自旋。其原理是:当发生对Monitor的争抢时,若Owner能够在很短的时间内释放掉锁,则那些正在争抢的线程就可以稍微等待下(空转)。 在Owner线程释放锁之后,争抢线程可能会立刻释放锁,从而避免了系统阻塞。不过,当Owner运行时间超过了临界值后,争抢线程自旋一段时间后依然无法获取到锁,这时争抢线程则会停止自旋进入到阻塞状态。
3.对象头和内置锁 (ObjectMonitor)
因为我在学习的过程中发现很多地方都关联到了对象头的知识点,例如 JDK 中的 synchronized 锁优化 和 JVM 中对象年龄升级等等。要深入理解这些知识的原理,了解对象头的概念很有必要,而且可以为后面分享 synchronized 原理和 JVM 知识的时候做准备。
3.1对象头
请参考这篇文章
《Java 对象的内存布局》
3.2内置锁 (ObjectMonitor)
通常所说的对象的内置锁,是对象头 Mark Word 中的重量级锁指针指向的 monitor 对象,该对象是在 HotSpot 底层 C++ 语言编写的 (openjdk 里面看),简单看一下代码:
//结构体如下
ObjectMonitor::ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0; //线程的重入次数
_object = NULL;
_owner = NULL; //标识拥有该monitor的线程
_WaitSet = NULL; //等待线程组成的双向循环链表,_WaitSet是第一个节点
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; //多线程竞争锁进入时的单向链表
FreeNext = NULL ;
_EntryList = NULL ; //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
ObjectMonitor 队列之间的关系转换可以用下图表示:
对象内置锁ObjectMonitor流程:
● 所有期待获得锁的线程,在锁已经被其它线程拥有的时候,这些期待获得锁的线程就进入了对象锁的entry set区域。
● 所有曾经获得过锁,但是由于其它必要条件不满足而需要wait的时候,线程就进入了对象锁的wait set区域 。
● 在wait set区域的线程获得Notify/notifyAll通知的时候,随机的一个Thread(Notify)或者是全部的Thread(NotifyALL)从对象锁的wait set区域进入了entry set中。
既然提到了waitSet 和EntryList(_cxq 队列后面会说),那就看一下底层的 wait 和 notify 方法,其实在java的wait(long time)方法中也提到了
3.3wait方法底层
wait 方法底层的实现过程如下,可以访问openJDK:http://hg.openjdk.java.net/jdk8u/hs-dev/hotspot/file/ae5624088d86/src/share/vm/runtime/synchronizer.cpp
//1.调用ObjectSynchronizer::wait方法
void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
/*省略 */
//2.获得Object的monitor对象(即内置锁)
ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
//3.调用monitor的wait方法
monitor->wait(millis, true, THREAD);
/*省略*/
}
//4.在wait方法中调用addWaiter方法
inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
/*省略*/
if (_WaitSet == NULL) {
//_WaitSet为null,就初始化_waitSet
_WaitSet = node;
node->_prev = node;
node->_next = node;
} else {
//否则就尾插
ObjectWaiter* head = _WaitSet ;
ObjectWaiter* tail = head->_prev;
assert(tail->_next == head, "invariant check");
tail->_next = node;
head->_prev = node;
node->_next = head;
node->_prev = tail;
}
}
//5.然后在ObjectMonitor::exit释放锁,接着 thread_ParkEvent->park 也就是wait
通过 object 获得内置锁 (objectMonitor),通过内置锁将 Thread 封装成 ojectWaiter 对象,然后 addWaiter 将它插入以_waitSet 为首结点的等待线程链表中去,最后释放锁。
3.4notify 方法的底层实现
//1.调用ObjectSynchronizer::notify方法
void ObjectSynchronizer::notify(Handle obj, TRAPS) {
/*省略*/
//2.调用ObjectSynchronizer::inflate方法
ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);
}
//3.通过inflate方法得到ObjectMonitor对象
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
/*省略*/
if (mark->has_monitor()) {
ObjectMonitor * inf = mark->monitor() ;
assert (inf->header()->is_neutral(), "invariant");
assert (inf->object() == object, "invariant") ;
assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is inva;lid");
return inf
}
/*省略*/
}
//4.调用ObjectMonitor的notify方法
void ObjectMonitor::notify(TRAPS) {
/*省略*/
//5.调用DequeueWaiter方法移出_waiterSet第一个结点
ObjectWaiter * iterator = DequeueWaiter() ;
//6.后面省略是将上面DequeueWaiter尾插入_EntrySet的操作
/**省略*/
}
通过 object 获得内置锁 (objectMonitor),调用内置锁的 notify 方法,通过waitset 结点移出等待链表中的首结点,将它置于EntrySet 中去,等待获取锁。注意:notifyAll 根据 policy 不同可能移入EntryList 或者cxq 队列中,此处不详谈。
4.总结
上面所介绍的通过 synchronzied 实现同步用到了对象的内置锁 (ObjectMonitor),而在 ObjectMonitor 的函数调用中会涉及到 Mutex lock 等特权指令,那么这个时候就存在操作系统用户态和核心态的转换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,这也是为什么早期的 synchronized 效率低的原因。在 jdk1.6 之后,从 jvm 层面做了很大的优化,下一篇我们主要介绍做了哪些优化。