源码位置:OpenJDK / jdk8u / jdk8u / hotspot
view src/share/vm/runtime/objectMonitor.cpp @ 9531:69087d08d473
一、Synchronized
反编译
在 Java 中,使用 synchronized
关键字同步的代码在编译后会转换为特定的字节码指令。这些指令主要涉及到获取和释放监视器(monitor)。监视器是同步的底层实现机制,用于控制对共享资源的访问。
当你对 Java 类使用 synchronized
关键字并进行编译后,可以通过反编译工具(如 javap
)查看其字节码内容。下面是一个简单的示例,展示了如何使用 synchronized
以及其可能的反编译结果。
1.1 Java 代码示例
java
public class SynchronizedExample {
public synchronized void syncMethod() {
// 同步方法内容
System.out.println("syncMethod 同步方法内容");
}
public void syncBlock() {
synchronized (this) {
// 同步代码块内容
System.out.println("syncBlock 同步方法内容");
}
}
}
1.2 反编译结果示例
bash
javac SynchronizedExample.java
然后,使用 javap
命令反编译:
bash
javap -c -v SynchronizedExample
这里 -c 选项显示方法的字节码,-s 显示内部签名,-verbose 显示额外的详细信息。
反编译结果:
shell
% javap -c -s -verbose SynchronizedExample
Classfile SynchronizedExample.class
Last modified 2024-10-23; size 651 bytes
MD5 checksum 8fb1933eb8319d08ff95d1793f85a04c
Compiled from "SynchronizedExample.java"
public class SynchronizedExample
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #23 // syncMethod 同步方法内容
#4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = String #26 // syncBlock 同步方法内容
#6 = Class #27 // SynchronizedExample
#7 = Class #28 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 syncMethod
#13 = Utf8 syncBlock
#14 = Utf8 StackMapTable
#15 = Class #27 // SynchronizedExample
#16 = Class #28 // java/lang/Object
#17 = Class #29 // java/lang/Throwable
#18 = Utf8 SourceFile
#19 = Utf8 SynchronizedExample.java
#20 = NameAndType #8:#9 // "<init>":()V
#21 = Class #30 // java/lang/System
#22 = NameAndType #31:#32 // out:Ljava/io/PrintStream;
#23 = Utf8 syncMethod 同步方法内容
#24 = Class #33 // java/io/PrintStream
#25 = NameAndType #34:#35 // println:(Ljava/lang/String;)V
#26 = Utf8 syncBlock 同步方法内容
#27 = Utf8 SynchronizedExample
#28 = Utf8 java/lang/Object
#29 = Utf8 java/lang/Throwable
#30 = Utf8 java/lang/System
#31 = Utf8 out
#32 = Utf8 Ljava/io/PrintStream;
#33 = Utf8 java/io/PrintStream
#34 = Utf8 println
#35 = Utf8 (Ljava/lang/String;)V
{
public SynchronizedExample();
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 1: 0
public synchronized void syncMethod();
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 syncMethod 同步方法内容
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 4: 0
line 5: 8
public void syncBlock();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #5 // String syncBlock 同步方法内容
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
LineNumberTable:
line 8: 0
line 10: 4
line 11: 12
line 12: 22
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 17
locals = [ class SynchronizedExample, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
SourceFile: "SynchronizedExample.java"
1.3 反编译结果解释
类定义和常量池
shell
Classfile SynchronizedExample.class
Last modified 2024-10-23; size 651 bytes
MD5 checksum 8fb1933eb8319d08ff95d1793f85a04c
Compiled from "SynchronizedExample.java"
public class SynchronizedExample
minor version: 0
major version: 52 // Java 8 对应的版本号
flags: ACC_PUBLIC, ACC_SUPER // 类的访问标志,public 类型,继承了超类
Constant pool: // 常量池,存储各种常量和符号引用
// 常量池条目,包括类、方法、字段引用和字符串常量等
构造方法
shell
{
public SynchronizedExample();
descriptor: ()V // 方法描述符,表示无参数,返回void
flags: ACC_PUBLIC // 方法的访问标志,public 类型
Code:
stack=1, locals=1, args_size=1 // 操作栈深度为1,局部变量表大小为1,参数数量为1(this)
0: aload_0 // 将this引用压入操作栈顶
1: invokespecial #1 // 调用超类(Object类)的构造方法
4: return // 从方法返回
LineNumberTable: // 行号表,用于调试
line 1: 0 // 源代码行号与字节码指令的对应关系
同步方法 syncMethod
shell
public synchronized void syncMethod();
descriptor: ()V // 方法描述符,无参数,返回void
flags: ACC_PUBLIC, ACC_SYNCHRONIZED // 方法的访问标志,public 和 synchronized
Code:
stack=2, locals=1, args_size=1 // 操作栈深度为2,局部变量表大小为1,参数数量为1(this)
0: getstatic #2 // 获取静态字段System.out的值
3: ldc #3 // 从常量池中加载字符串"syncMethod 同步方法内容"
5: invokevirtual #4 // 调用PrintStream.println方法打印字符串
8: return // 从方法返回
LineNumberTable:
line 4: 0
line 5: 8 // 源代码行号与字节码指令的对应关系
同步代码块 syncBlock
shell
public void syncBlock();
descriptor: ()V // 方法描述符,无参数,返回void
flags: ACC_PUBLIC // 方法的访问标志,public
Code:
stack=2, locals=3, args_size=1 // 操作栈深度为2,局部变量表大小为3,参数数量为1(this)
0: aload_0 // 将this引用压入操作栈顶
1: dup // 复制栈顶值
2: astore_1 // 将复制的this引用存储到局部变量1
3: monitorenter // 进入同步块,获取监视器锁
4: getstatic #2 // 获取静态字段System.out的值
7: ldc #5 // 从常量池中加载字符串"syncBlock 同步方法内容"
9: invokevirtual #4 // 调用PrintStream.println方法打印字符串
12: aload_1 // 将局部变量1的值(this引用)压入栈顶
13: monitorexit // 退出同步块,释放监视器锁
14: goto 22 // 正常完成同步块,跳转到指令22
17: astore_2 // 捕获任何异常,存储到局部变量2
18: aload_1 // 将局部变量1的值(this引用)压入栈顶
19: monitorexit // 释放监视器锁
20: aload_2 // 将局部变量2的值(捕获的异常)压入栈顶
21: athrow // 抛出异常
22: return // 从方法返回
Exception table:
from to target type
4 14 17 any // 指定同步块内发生异常时的处理范围和跳转目标
17 20 17 any
LineNumberTable:
line 8: 0
line 10: 4
line 11: 12
line 12: 22 // 源代码行号与字节码指令的对应关系
StackMapTable: number_of_entries = 2 // 栈图表,用于验证和处理异常
源文件属性
plaintext
SourceFile: "SynchronizedExample.java" // 源文件属性,记录了这个类是从哪个源文件编译而来的
这个反编译结果展示了类的结构、方法、同步机制(同步方法和同步代码块)以及异常处理机制。希望这些注释能帮助你更好地理解 Java 字节码。
加锁解锁源码入口
shell
3: monitorenter // 进入同步块,获取监视器锁,对应源码入口:ObjectMonitor::enter
.....
13: monitorexit // 退出同步块,释放监视器锁,对应源码入口:ObjectMonitor::exit
二、源码解析
2.1 头文件 objectMonitor.hpp
代码位置:OpenJDK / jdk8u / jdk8u / hotspot
view src/share/vm/runtime/objectMonitor.hpp @ 9531:69087d08d473
这段代码是Java虚拟机(JVM)中ObjectMonitor
类的定义,主要用于实现Java对象的监视器机制,包括同步块(synchronized blocks)和方法(synchronized methods)的支持。下面是对关键部分的详细解释,注释加在代码行后面:
cpp
#ifndef SHARE_VM_RUNTIME_OBJECTMONITOR_HPP
#define SHARE_VM_RUNTIME_OBJECTMONITOR_HPP
// 防止头文件重复包含
#include "runtime/os.hpp" // 包含操作系统相关的头文件
#include "runtime/park.hpp" // 包含线程挂起和唤醒相关的头文件
#include "runtime/perfData.hpp" // 包含性能数据收集相关的头文件
class ObjectWaiter : public StackObj {
// ObjectWaiter类定义,用于表示等待在ObjectMonitor上的线程
public:
enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;
// 线程状态枚举,包括未定义、就绪、运行、等待、进入、竞争队列等状态
enum Sorted { PREPEND, APPEND, SORTED } ;
// 等待线程在队列中的插入方式,包括前插、后插、排序
ObjectWaiter * volatile _next; // 指向下一个等待线程的指针
ObjectWaiter * volatile _prev; // 指向前一个等待线程的指针
Thread* _thread; // 指向实际的线程对象
jlong _notifier_tid; // 唤醒此等待线程的线程ID
ParkEvent * _event; // 用于线程挂起和唤醒的事件
volatile int _notified ; // 标记此线程是否已被唤醒
volatile TStates TState ; // 线程的当前状态
Sorted _Sorted ; // 线程在等待队列中的插入方式
bool _active ; // 标记是否启用争用监控
public:
ObjectWaiter(Thread* thread); // 构造函数
void wait_reenter_begin(ObjectMonitor *mon); // 等待重新进入开始
void wait_reenter_end(ObjectMonitor *mon); // 等待重新进入结束
};
class ObjectMonitor {
// ObjectMonitor类定义,用于实现Java对象的监视器机制
public:
// 省略部分代码...
ObjectMonitor() {
_header = NULL; // 初始化对象头指针为NULL
_count = 0; // 初始化引用计数为0
_waiters = 0, // 初始化等待线程计数为0
_recursions = 0; // 初始化递归计数为0【跟AQS的state一样】
_object = NULL; // 初始化指向被监视对象的指针为NULL
_owner = NULL; // 初始化拥有者(锁持有者)为NULL【跟AQS的exclusiveOwnerThread一样】
_WaitSet = NULL; // 初始化等待集合为NULL
_WaitSetLock = 0 ; // 初始化等待集合的锁为0
_Responsible = NULL ;// 初始化负责线程为NULL
_succ = NULL ;// 初始化后继线程为NULL
_cxq = NULL ;// 初始化竞争队列为NULL【类似于AQS里面的同步队列(这里是单向队列),拿锁失败方cxq里面】
FreeNext = NULL ;// 初始化空闲链表的下一个节点为NULL
_EntryList = NULL ;// 初始化入口列表为NULL【类似于AQS里面的同步队列(这里是双向队列),释放锁,可能会将cxq排队的节点扔到EntryList】
_SpinFreq = 0 ; // 初始化自旋频率为0
_SpinClock = 0 ; // 初始化自旋时钟为0
OwnerIsThread = 0 ; // 初始化拥有者是否为线程的标记为0
_previous_owner_tid = 0; // 初始化前一个拥有者的线程ID为0
}
// 省略部分代码...
};
#endif // SHARE_VM_RUNTIME_OBJECTMONITOR_HPP
这段代码主要定义了ObjectMonitor
类和ObjectWaiter
类。ObjectMonitor
是实现Java同步机制的核心,用于管理对象锁的获取、释放、等待和通知操作。ObjectWaiter
类代表等待在ObjectMonitor
上的线程,用于管理线程等待和唤醒的逻辑。整个实现涉及到复杂的并发控制逻辑,以确保Java多线程程序的正确同步执行。
2.2 加锁源码 objectMonitor.cpp
2.2.1 加锁源码 ObjectMonitor::enter
源码位置:OpenJDK / jdk8u / jdk8u / hotspot
view src/share/vm/runtime/objectMonitor.cpp @ 9531:69087d08d473
2.2.1.1 CAS尝试获取锁
cpp
void ATTR ObjectMonitor::enter(TRAPS) {
// 以下代码按顺序检查最常见的情况,并减少SPARC和IA32处理器上RTS->RTO缓存行升级
Thread * const Self = THREAD ; // 获取当前线程
void * cur ;
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ; // CAS原子操作:尝试将_owner从NULL设置为Self,尝试加锁,返回旧值
// 判断是否加锁成功,旧值为null,代表CAS加锁成功
if (cur == NULL) {
assert (_recursions == 0 , "invariant") ; // 断言:递归计数为0
assert (_owner == Self, "invariant") ; // 断言:所有者是当前线程
// 考虑:设置或断言 OwnerIsThread == 1
return ; // 获取锁成功,直接返回
}
2.2.1.2 重入锁
cpp
// CAS加锁失败,但是持有锁的线程是自己,则计数加一(重入锁)
if (cur == Self) {
// TODO-修复:检查整数溢出! BUGID 6557169
_recursions ++ ; // 递归获取锁,增加递归计数
return ;
}
2.2.1.3 从轻量级锁升级为重量级锁,更新状态
cpp
// 下面这段代码处理了一个特殊情况,即当前线程已经拥有锁,但是由于锁的状态或表示方式需要更新(例如,从轻量级锁升级为重量级锁),因此需要进行相应的状态更新操作。
// 检查当前线程(Self)是否已经拥有了锁,如果当前线程已经是锁的所有者,那么cur将会是当前线程的地址。
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error"); // 断言:如果不为0,说明内部状态有错误,因为按照逻辑,当线程第一次进入这个分支时,递归计数应该是0。
_recursions = 1 ; // 这行代码将递归计数设置为1。这是因为当前线程已经拥有了锁,但是由于某种原因(例如,锁的所有者信息从一个轻量级锁(BasicLockObject)转换为了一个完整的线程指针),需要重新标记这个锁的状态,表明当前线程正通过递归方式再次获取这个锁。
// 将所有者从线程特定的栈上BasicLockObject地址转换为完整的"Thread *"
_owner = Self ;
OwnerIsThread = 1 ; // 表示锁的所有者确实是一个线程(而不是其他可能的状态,如轻量级锁状态)
return ;// 最后,由于当前线程已经成功地"再次"获取了锁(实际上是确认了它已经拥有锁),方法执行结束,返回到调用者。
}
// 我们遇到了真正的竞争
// 这个注释表明代码已经进入了一个状态,其中当前线程无法立即获取锁,因为锁被其他线程持有。
/*
这是一个断言,用于确保当前线程(Self)的_Stalled字段为0。
_Stalled字段通常用于指示线程是否因为等待某个资源而被阻塞。
这个断言的目的是确保在进入竞争状态之前,线程不应该已经处于阻塞状态。
*/
assert (Self->_Stalled == 0, "invariant") ; // 断言:线程未被阻塞,
/*
这行代码将当前线程的_Stalled字段设置为当前ObjectMonitor对象的地址。
intptr_t(this)将this指针(指向当前ObjectMonitor对象)转换为一个整数类型。
这个操作实际上是在标记线程正在等待这个特定的ObjectMonitor对象(即,正在尝试获取这个特定的锁)。
设置_Stalled字段可以帮助其他部分的代码(如调试器或线程分析工具)了解线程当前的状态和它正在等待的资源。
*/
Self->_Stalled = intptr_t(this) ; // 设置线程的_Stalled字段为当前监视器,标记线程正在等待特定的锁(由当前的ObjectMonitor对象表示)。
2.2.1.4 自旋锁
下面这段代码实现了一种优化策略,称为"自旋锁"(spin lock)。在多线程竞争锁的情况下,它允许线程在一定时间内进行自旋等待,而不是立即进入阻塞状态。重量级锁支持自旋锁能力
cpp
if (Knob_SpinEarly && TrySpin (Self) > 0) {
// 自旋加锁成功
assert (_owner == Self , "invariant") ; // 断言:所有者是当前线程
assert (_recursions == 0 , "invariant") ; // 断言:递归计数为0
assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ; // 断言:对象标记正确
Self->_Stalled = 0 ; // 重置_Stalled字段
return ;// 加锁成功,直接返回
}
-
条件判断 :
if (Knob_SpinEarly && TrySpin (Self) > 0)
Knob_SpinEarly
是一个配置选项,用于控制是否启用早期自旋。TrySpin(Self)
是一个方法,尝试通过自旋来获取锁。如果成功获取锁,它会返回一个正值。- 这个条件检查是否允许自旋,以及自旋是否成功获取了锁。
-
断言检查:
assert (_owner == Self, "invariant") ;
确保锁的所有者现在是当前线程。assert (_recursions == 0, "invariant") ;
确保递归计数为0,因为这是首次获取锁。assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
确保对象的标记正确地指向这个 ObjectMonitor。
-
重置状态 :
Self->_Stalled = 0 ;
- 将线程的
_Stalled
字段重置为0,表示线程不再处于等待状态。
- 将线程的
-
返回 :
return ;
- 如果自旋成功获取了锁,就直接返回,不需要进行后续的锁获取操作。
这段代码的主要目的是:
- 在锁竞争的早期阶段尝试通过自旋来获取锁,这可以避免线程立即进入阻塞状态。
- 如果自旋成功,它可以显著减少线程切换的开销,提高性能。
- 通过断言确保锁的状态正确,这对于维护代码的正确性和可靠性很重要。
- 如果自旋成功,立即返回,避免不必要的后续操作。
自旋锁是一种在短期锁竞争中非常有效的优化策略,特别是在多核处理器系统中。然而,它需要谨慎使用,因为过度自旋可能会浪费 CPU 时间。这就是为什么有 Knob_SpinEarly
这样的配置选项,允许根据具体情况调整自旋策略。
2.2.1.5 状态断言判断
cpp
assert (_owner != Self , "invariant") ;
- 确保当前线程(
Self
)不是锁的所有者。这意味着当前线程正在尝试获取它尚未拥有的锁。
cpp
assert (_succ != Self , "invariant") ;
- 确保当前线程不是下一个预定获取锁的线程(
_succ
)。这是为了避免潜在的死锁或优先级倒置问题。
cpp
assert (Self->is_Java_thread() , "invariant") ;
- 确保当前线程确实是一个Java线程。在JVM中,可能存在不同类型的线程(例如,VM线程、编译器线程等),而这段代码逻辑仅适用于Java线程。
cpp
JavaThread * jt = (JavaThread *) Self ;
- 将当前线程(
Self
)强制转换为JavaThread
类型的指针。这是基于前面的断言,确保了Self
确实是一个Java线程。
cpp
assert (!SafepointSynchronize::is_at_safepoint(), "invariant") ;
- 确保当前不处于安全点(safepoint)。在安全点期间,所有Java线程都会被挂起,以便进行垃圾收集或其他全局操作。如果当前处于安全点,那么执行锁操作是不安全的。
cpp
assert (jt->thread_state() != _thread_blocked , "invariant") ;
- 确保Java线程的状态不是阻塞(
_thread_blocked
)状态。这是为了确保线程在尝试获取锁时是处于可运行状态。
cpp
assert (this->object() != NULL , "invariant") ;
- 确保当前监视器(锁)关联的对象不是
NULL
。每个监视器都与一个Java对象相关联,这个断言确保了关联对象的存在。
cpp
assert (_count >= 0, "invariant") ;
- 确保锁的计数器(
_count
)是非负的。这个计数器可能用于跟踪锁的状态或持有情况。
总的来说,这些断言是为了确保在执行锁获取或释放操作之前,当前线程和锁的状态满足一系列预期的条件,以保证操作的正确性和安全性。这些不变性条件对于维护JVM的多线程同步机制的健壮性至关重要。
2.2.1.6 记录待争抢锁线程数
cpp
// 防止在STW(Stop-The-World)时期发生监视器的收缩。参见deflate_idle_monitors()和is_busy()函数。
// 确保在存在争用的情况下,对象-监视器关系保持稳定。
Atomic::inc_ptr(&_count); // 没拿到锁,_count+1,用于跟踪监视器的使用情况或争用程度。有多少线程抢锁
2.2.1.7 监视器事件对象信息
cpp
// JFR_ONLY宏用于条件编译,只有在启用Java Flight Recorder (JFR)时才会编译此行。
JFR_ONLY(JfrConditionalFlushWithStacktrace<EventJavaMonitorEnter> flush(jt);) // 创建一个条件性刷新对象,可能会记录当前线程(jt)的栈追踪。
EventJavaMonitorEnter event; // 创建一个监视器进入事件对象,用于记录此次监视器进入的相关信息。
if (event.should_commit()) { // 检查是否应该提交(记录)这个事件,可能基于JFR的配置。
event.set_monitorClass(((oop)this->object())->klass()); // 设置事件的monitorClass字段,表示被监视对象的类。
event.set_address((uintptr_t)(this->object_addr())); // 设置事件的address字段,表示被监视对象的内存地址。
}
这段代码主要用于在线程进入监视器(即获取锁)时,记录相关的性能和调试信息。它确保了在高并发情况下的正确性,并为Java Flight Recorder提供了详细的事件信息,这对于性能分析和问题诊断非常有用。
cpp
{ // 更改Java线程状态以指示在监视器进入时被阻塞。
JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this); // 创建一个状态对象,表示当前线程因尝试获取监视器而被阻塞。
Self->set_current_pending_monitor(this); // 设置当前线程正在尝试进入的监视器。
DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt); // DTrace探针,用于监视争用的进入事件。
if (JvmtiExport::should_post_monitor_contended_enter()) { // 检查是否应该发布监视器争用进入事件。
JvmtiExport::post_monitor_contended_enter(jt, this); // 发布监视器争用进入事件。
// 当前线程尚未拥有监视器,并且尚未出现在任何队列中,这些队列会使其成为后继者。
// 这意味着JVMTI_EVENT_MONITOR_CONTENDED_ENTER事件处理程序不能意外地消费一个为此ObjectMonitor关联的ParkEvent准备的unpark()。
}
OSThreadContendState osts(Self->osthread()); // 创建一个表示操作系统线程争用状态的对象。
ThreadBlockInVM tbivm(jt); // 创建一个表示线程在VM中被阻塞的对象。
2.2.1.8 如果前面的几次操作都没拿到锁,再次尝试或者排队,拿到锁后执行完加锁内容再释放锁 == EnterI (THREAD)==
cpp
// TODO-FIXME: 将以下的for(;;)循环更改为直线代码。
for (;;) { // 无限循环,直到成功获取监视器或线程被挂起。
jt->set_suspend_equivalent(); // 设置线程为挂起等效状态。
// 通过handle_special_suspend_equivalent_condition()或java_suspend_self()清除挂起等效状态。
// 如果前面的几次操作都没拿到锁,执行EnterI函数
EnterI (THREAD) ; // 尝试进入监视器。【重要☆☆☆☆☆】
if (!ExitSuspendEquivalent(jt)) break ; // 如果线程不再处于挂起等效状态,则退出循环。
//
// 我们已经获取了争用的监视器,但在我们等待的时候,另一个线程挂起了我们。我们不想在被挂起时进入监视器,
// 因为这会让挂起我们的线程感到意外。
//
_recursions = 0 ; // 重置递归计数。
_succ = NULL ; // 清除后继者。
exit (false, Self) ; // 退出监视器。【重要☆☆☆☆☆】
jt->java_suspend_self(); // 自我挂起。
}
2.2.1.9 删除监视器
cpp
Self->set_current_pending_monitor(NULL); // 清除当前线程正在尝试进入的监视器。
// 我们清除了待处理的监视器信息,因为我们刚刚通过了进入-检查挂起的舞蹈,并且我们现在自由且清晰地拥有了监视器,
// 即它不再是待处理的。ThreadBlockInVM的析构函数可以在这个块的末尾进入安全点。如果我们在那个安全点期间进行线程转储,
// 那么这个线程将显示为已经"锁定"了监视器,但操作系统和java.lang.Thread的状态仍然会报告该线程被阻塞,试图获取它。
}
2.2.1.10 释放锁后状态恢复
cpp
// 已释放锁
Atomic::dec_ptr(&_count); // 使用原子操作减少_count指针的值,代表争抢锁资源的线程数减一
assert (_count >= 0, "invariant"); // 断言_count的值不会变成负数,确保程序的逻辑正确性。
Self->_Stalled = 0; // 将当前线程的_Stalled标志设置为0,表示当前线程不再被阻塞。
// 必须将_recursions设置为0或断言_recursions已经是0。
// 这是因为在退出监视器时,递归锁的计数应该是0,表示没有嵌套的进入。
assert (_recursions == 0, "invariant");
assert (_owner == Self, "invariant"); // 断言当前线程(Self)是这个监视器的拥有者。
assert (_succ != Self, "invariant"); // 断言当前线程(Self)不是这个监视器的下一个等待者(_succ)。
// 断言被监视对象的标记(mark)与当前监视器编码后的标记相同。
// 这是检查监视器与对象之间关系正确性的一种方式。
assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant");
这段代码主要用于在线程退出监视器(即释放锁)时,更新监视器的状态并进行一系列的正确性检查。通过这些断言,可以确保监视器的状态与预期一致,从而避免潜在的并发问题。这些检查是并发编程中常用的技术,用于确保程序的健壮性和正确性。
java
// 调用DTrace监控探针,报告有线程进入了竞争状态。这里的this指的是当前的监控器对象。
DTRACE_MONITOR_PROBE(contended__entered, this, object(), jt);
// 检查是否需要发布监控器竞争进入事件(JVMTI_EVENT_MONITOR_CONTENDED_ENTERED)。
// JVMTI是Java虚拟机工具接口,用于提供关于虚拟机内部状态的信息。
if (JvmtiExport::should_post_monitor_contended_entered()) {
// 发布监控器竞争进入事件。这里的jt是指当前线程。
JvmtiExport::post_monitor_contended_entered(jt, this);
// 此处的注释说明,当前线程已经拥有了监控器,并且在监控器进入协议的剩余部分不会调用park()。
// 因此,即使JVMTI_EVENT_MONITOR_CONTENDED_ENTERED事件处理程序消耗了由刚刚退出监控器的线程发出的unpark(),也没有关系。
}
// 检查是否应该提交事件。
if (event.should_commit()) {
// 设置事件的前一个拥有者。
event.set_previousOwner((uintptr_t)_previous_owner_tid);
// 提交事件。
event.commit();
}
// 检查是否有记录竞争锁尝试的计数器存在。
if (ObjectMonitor::_sync_ContendedLockAttempts != NULL) {
// 如果存在,增加计数器的值,表示有更多的竞争锁尝试发生。
ObjectMonitor::_sync_ContendedLockAttempts->inc();
}
这段代码主要做了以下几件事情:
- 监控器竞争报告:通过DTrace监控探针报告线程进入竞争状态。
- 事件发布:如果JVMTI配置为需要发布监控器竞争进入事件,则进行发布。
- 事件提交:如果事件需要被提交,则设置事件的前一个拥有者并提交事件。
- 竞争尝试计数:如果存在记录竞争锁尝试的计数器,则增加计数器的值。
这段代码的目的是在监控器(锁)竞争的情况下,提供对事件的监控和报告,以便于分析和调试多线程应用程序的性能和同步问题。
2.2.2 前面enter操作拿锁失败,走这个函数 ObjectMonitor::EnterI
这段代码是Java虚拟机(JVM)中ObjectMonitor
类的EnterI
方法的实现,用于线程尝试获取对象监视器。这个方法包含了多个步骤,包括尝试获取锁、自旋等待、入队等待和最终获取锁。下面是对关键代码行的详细解释:
2.2.2.1 获取当前抢锁的线程
cpp
void ATTR ObjectMonitor::EnterI (TRAPS) {
Thread * Self = THREAD ; // 获取当前线程对象,Self是当前抢锁线程
assert (Self->is_Java_thread(), "invariant") ; // 断言当前线程是Java线程
assert (((JavaThread *) Self)->thread_state() == _thread_blocked, "invariant") ; // 断言当前线程状态为阻塞状态
2.2.2.2 执行一次CAS尝试拿锁
cpp
// 执行一次CAS尝试拿锁
if (TryLock (Self) > 0) { // 尝试获取锁
// 如果成功获取锁,进行断言检查并返回
return ;
}
DeferredInitialize () ; // 延迟初始化,准备自旋等待或入队等待
2.2.2.3 拿锁失败,尝试自旋等待获取锁
cpp
// 尝试自旋等待获取锁
if (TrySpin (Self) > 0) {
// 如果自旋成功获取锁,进行断言检查并返回
return ;
}
2.2.2.4 没拿到锁,封装成Node节点,准备将线程入队等待
cpp
// 自旋失败,准备将线程入队等待
ObjectWaiter node(Self) ; // 创建一个等待节点,将线程封装成ObjectWaiter,就是Node
Self->_ParkEvent->reset() ; // 重置线程的ParkEvent
node._prev = (ObjectWaiter *) 0xBAD ; // 设置前驱节点为无效值
node.TState = ObjectWaiter::TS_CXQ ; // 设置节点状态为竞争队列状态,表示一会要将节点放到cxq单向列表里
2.2.2.5 基于CAS方式,将封装好的Node放入cxq单向列表,入队成功或者拿到锁
cpp
for (;;) { // 死循环尝试将节点入队到竞争队列
node._next = nxt = _cxq ;
if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ; // 使用CAS操作入队
// 入队前再次尝试获取锁
if (TryLock (Self) > 0) {
// 如果成功获取锁,进行断言检查并返回
return ;
}
}
2.2.2.6 设置当前线程为负责线程,负责处理潜在的竞争条件
用于处理线程在尝试获取对象监视器时的同步逻辑。下面是对这行代码的详细解释:
cpp
// 设置当前线程为负责线程,负责处理潜在的竞争条件
if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
}
-
SyncFlags & 16
:这是一个位操作,用于检查SyncFlags
变量的第5位(从0开始计数)是否被设置。SyncFlags
是一个用于控制同步行为的标志位变量。如果SyncFlags
的第5位为0,表示当前没有启用某种特定的同步策略或优化。 -
nxt == NULL && _EntryList == NULL
:这两个条件检查当前对象监视器的竞争队列(_cxq
)是否为空(通过nxt
变量表示,它是尝试将当前线程节点加入竞争队列时的下一个节点),以及入口列表(_EntryList
)是否为空。这两个条件一起确保当前没有其他线程正在等待获取这个对象监视器。 -
Atomic::cmpxchg_ptr (Self, &_Responsible, NULL)
:这是一个CAS原子的比较并交换操作,尝试将_Responsible
变量的值从NULL
设置为当前线程对象Self
。_Responsible
变量用于标记哪个线程负责处理潜在的竞争条件和避免线程饥饿。这个操作确保在多个线程同时尝试成为负责线程时,只有一个线程能成功。
整体来看,这行代码的作用是在没有其他线程等待获取对象监视器(即没有竞争),并且当前没有启用特定的同步策略时,当前线程尝试将自己设置为负责处理潜在竞争的线程。这是一种优化措施,旨在减少不必要的同步开销,并在没有竞争的情况下提高性能。
2.2.2.7 未拿到锁,已入队列,再尝试一次获取锁,如果还未拿到锁,挂起、唤醒则继续抢锁
cpp
// 死循环等待获取锁
for (;;) {
if (TryLock (Self) > 0) break ; // 尝试获取锁
// 【挂起】
// 如果当前线程是负责线程或者特定的同步标志被设置,使用定时等待
if (_Responsible == Self || (SyncFlags & 1)) {
// RecheckInterval是等待的时间间隔,这允许线程在等待一段时间后自动唤醒,重新尝试获取锁。
Self->_ParkEvent->park ((jlong) RecheckInterval) ; // 负责线程使用定时等待,负责线程有责任定期检查锁的状态,以避免死锁或饥饿情况。
} else {
Self->_ParkEvent->park() ; // 非负责线程使用无限等待
}
// 【唤醒】继续抢锁
if (TryLock(Self) > 0) break ; // 再次尝试获取锁
// 如果在尝试获取锁失败后设置了自旋标志(Knob_SpinAfterFutile的第1位),则进行一次自旋尝试。如果自旋尝试成功,也通过break语句退出循环。
if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;
}
2.2.2.8 获取锁成功后续操作
cpp
// 获取锁成功,从等待队列中移除当前线程
UnlinkAfterAcquire (Self, &node) ;
if (_succ == Self) _succ = NULL ; // 清除后继线程标记
if (_Responsible == Self) {
_Responsible = NULL ; // 清除负责线程标记
OrderAccess::fence(); // 确保内存操作的可见性
}
// 在退出方法前,确保之前对监视器元数据的更新对其他线程可见
if (SyncFlags & 8) {
OrderAccess::fence() ;
}
return ;
}
这个方法的核心逻辑是尝试获取锁,如果直接尝试失败,则进行自旋等待,如果自旋也失败,则将线程加入等待队列并阻塞等待。在等待过程中,线程会定期尝试重新获取锁,直到成功为止。此外,方法中还包含了多处断言和内存屏障(OrderAccess::fence()
),用于确保操作的正确性和内存可见性。
2.2.3 尝试获取锁 TryLock
cpp
int ObjectMonitor::TryLock (Thread * Self) {
for (;;) {
void * own = _owner ;
if (own != NULL) return 0 ;
if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {
// Either guarantee _recursions == 0 or set _recursions = 0.
assert (_recursions == 0, "invariant") ;
assert (_owner == Self, "invariant") ;
// CONSIDER: set or assert that OwnerIsThread == 1
return 1 ;
}
// The lock had been free momentarily, but we lost the race to the lock.
// Interference -- the CAS failed.
// We can either return -1 or retry.
// Retry doesn't make as much sense because the lock was just acquired.
if (true) return -1 ;
}
}
cpp
int ObjectMonitor::TryLock (Thread * Self) {
- 定义
TryLock
方法,接收一个指向当前线程对象的指针Self
作为参数。
cpp
for (;;) {
- 使用无限循环来不断尝试获取锁。
cpp
void * own = _owner ;
- 获取当前锁的拥有者,并将其存储在局部变量
own
中。如果_owner
为NULL
,表示锁当前没有被任何线程持有。
cpp
if (own != NULL) return 0 ;
- 如果
own
不为NULL
,即锁已被其他线程持有,则立即返回0,表示当前线程未能获取锁。
cpp
if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {
- 使用原子的比较并交换(CAS)操作尝试将
_owner
从NULL
设置为当前线程Self
。如果CAS操作成功(即_owner
原来就是NULL
),则当前线程成功获取了锁。
cpp
assert (_recursions == 0, "invariant") ;
- 断言
_recursions
(递归锁计数)为0,确保在获取锁时没有递归锁定发生。
cpp
assert (_owner == Self, "invariant") ;
- 断言
_owner
现在确实是当前线程Self
,确保锁的拥有者正确设置。
cpp
return 1 ;
- 返回1,表示当前线程成功获取了锁。
cpp
if (true) return -1 ;
- 如果CAS操作失败(即在尝试获取锁的过程中锁被其他线程获取),这里直接返回-1。虽然这里使用了
if (true)
,实际上这是一个确定的路径,意味着在CAS操作失败后不再进行重试,直接返回-1。
cpp
}
}
- 循环结束。
总结来说,TryLock
方法尝试通过原子操作获取对象监视器的锁。如果锁当前未被持有,当前线程将尝试获取锁。如果成功,返回1;如果锁已被其他线程持有,或者在尝试获取锁的过程中锁被其他线程获取,则返回-1。这个方法通过CAS操作确保了锁获取的原子性和线程安全。
2.2.4 尝试获取锁 TrySpin
这段代码是ObjectMonitor
类中的TrySpin_VaryDuration
方法的实现,用于尝试通过自旋(spin-waiting)获取对象监视器的锁。这种方法在多线程编程中用于减少上下文切换的开销,尤其是当锁的持有时间非常短时。下面是对这段代码的详细解释:
cpp
int ObjectMonitor::TrySpin_VaryDuration (Thread * Self) {
- 定义
TrySpin_VaryDuration
方法,接收一个指向当前线程对象的指针Self
作为参数。
cpp
int ctr = Knob_FixedSpin ;
if (ctr != 0) {
while (--ctr >= 0) {
if (TryLock (Self) > 0) return 1 ; // 尝试获取锁,如果成功则返回1
SpinPause () ; // 执行自旋暂停,以减少CPU使用率
}
return 0 ; // 固定自旋次数结束,未能获取锁,返回0
}
- 如果
Knob_FixedSpin
(一个预设的自旋次数)不为0,则执行固定次数的自旋尝试。每次自旋都会尝试获取锁,如果成功则立即返回1。如果在固定次数内未能获取锁,则返回0。
cpp
for (ctr = Knob_PreSpin + 1; --ctr >= 0 ; ) {
if (TryLock(Self) > 0) {
int x = _SpinDuration ;
if (x < Knob_SpinLimit) {
if (x < Knob_Poverty) x = Knob_Poverty ;
_SpinDuration = x + Knob_BonusB ;
}
return 1 ; // 成功获取锁,增加自旋持续时间,然后返回1
}
SpinPause () ; // 执行自旋暂停
}
- 如果
Knob_FixedSpin
为0,则执行另一种自旋逻辑,自旋次数由Knob_PreSpin
决定。在每次自旋中尝试获取锁,如果成功,则根据当前的自旋持续时间(_SpinDuration
)和预设的限制(Knob_SpinLimit
、Knob_Poverty
)调整自旋持续时间,并返回1。
cpp
ctr = _SpinDuration ;
if (ctr < Knob_SpinBase) ctr = Knob_SpinBase ;
if (ctr <= 0) return 0 ;
if (Knob_SuccRestrict && _succ != NULL) return 0 ;
if (Knob_OState && NotRunnable (Self, (Thread *) _owner)) {
return 0 ; // 检查是否应该继续自旋
}
- 根据
_SpinDuration
设置自旋次数,如果小于Knob_SpinBase
则使用Knob_SpinBase
。如果自旋次数小于等于0,或者满足某些条件(如有后继线程或锁的拥有者不可运行),则不进行自旋,直接返回0。
cpp
while (--ctr >= 0) {
// 自旋主循环
if ((ctr & 0xFF) == 0) {
if (SafepointSynchronize::do_call_back()) {
goto Abort ; // 检查是否有安全点,如果有则中断自旋
}
}
// 省略部分代码...
if (ox == NULL) {
ox = (Thread *) Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
if (ox == NULL) {
// 自旋成功获取锁
return 1 ;
}
// 省略部分代码...
}
// 省略部分代码...
}
- 在自旋循环中,周期性地检查是否有安全点(safepoint)等待,如果有,则中断自旋。在每次迭代中,尝试使用CAS操作获取锁。如果成功,则调整自旋持续时间并返回1。
cpp
Abort:
if (MaxSpin >= 0) Adjust (&_Spinner, -1) ;
if (sss && _succ == Self) {
_succ = NULL ;
if (TryLock(Self) > 0) return 1 ;
}
return 0 ;
- 如果自旋被中断(
Abort
标签),则执行一些清理操作,如减少当前自旋者的计数。如果当前线程是后继线程(_succ
),则清除后继线程标记,并尝试最后一次获取锁。如果成功,则返回1;否则,返回0。
总结来说,TrySpin_VaryDuration
方法通过自旋尝试获取锁,自旋次数和持续时间可以根据之前的尝试结果动态调整。这种方法旨在平衡锁获取的延迟和CPU资源的使用,特别是在高并发环境下。
2.2.5 释放锁 ObjectMonitor::exit
cpp
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * Self = THREAD ;
if (THREAD != _owner) {
if (THREAD->is_lock_owned((address) _owner)) {
// Transmute _owner from a BasicLock pointer to a Thread address.
// We don't need to hold _mutex for this transition.
// Non-null to Non-null is safe as long as all readers can
// tolerate either flavor.
assert (_recursions == 0, "invariant") ;
_owner = THREAD ;
_recursions = 0 ;
OwnerIsThread = 1 ;
} else {
// NOTE: we need to handle unbalanced monitor enter/exit
// in native code by throwing an exception.
// TODO: Throw an IllegalMonitorStateException ?
TEVENT (Exit - Throw IMSX) ;
assert(false, "Non-balanced monitor enter/exit!");
if (false) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
return;
}
}
if (_recursions != 0) {
_recursions--; // this is simple recursive enter
TEVENT (Inflated exit - recursive) ;
return ;
}
// Invariant: after setting Responsible=null an thread must execute
// a MEMBAR or other serializing instruction before fetching EntryList|cxq.
if ((SyncFlags & 4) == 0) {
_Responsible = NULL ;
}
#if INCLUDE_JFR
// get the owner's thread id for the MonitorEnter event
// if it is enabled and the thread isn't suspended
if (not_suspended && EventJavaMonitorEnter::is_enabled()) {
_previous_owner_tid = JFR_THREAD_ID(Self);
}
#endif
for (;;) {
assert (THREAD == _owner, "invariant") ;
if (Knob_ExitPolicy == 0) {
// release semantics: prior loads and stores from within the critical section
// must not float (reorder) past the following store that drops the lock.
// On SPARC that requires MEMBAR #loadstore|#storestore.
// But of course in TSO #loadstore|#storestore is not required.
// I'd like to write one of the following:
// A. OrderAccess::release() ; _owner = NULL
// B. OrderAccess::loadstore(); OrderAccess::storestore(); _owner = NULL;
// Unfortunately OrderAccess::release() and OrderAccess::loadstore() both
// store into a _dummy variable. That store is not needed, but can result
// in massive wasteful coherency traffic on classic SMP systems.
// Instead, I use release_store(), which is implemented as just a simple
// ST on x64, x86 and SPARC.
OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock
OrderAccess::storeload() ; // See if we need to wake a successor
if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
TEVENT (Inflated exit - simple egress) ;
return ;
}
TEVENT (Inflated exit - complex egress) ;
// Normally the exiting thread is responsible for ensuring succession,
// but if other successors are ready or other entering threads are spinning
// then this thread can simply store NULL into _owner and exit without
// waking a successor. The existence of spinners or ready successors
// guarantees proper succession (liveness). Responsibility passes to the
// ready or running successors. The exiting thread delegates the duty.
// More precisely, if a successor already exists this thread is absolved
// of the responsibility of waking (unparking) one.
//
// The _succ variable is critical to reducing futile wakeup frequency.
// _succ identifies the "heir presumptive" thread that has been made
// ready (unparked) but that has not yet run. We need only one such
// successor thread to guarantee progress.
// See http://www.usenix.org/events/jvm01/full_papers/dice/dice.pdf
// section 3.3 "Futile Wakeup Throttling" for details.
//
// Note that spinners in Enter() also set _succ non-null.
// In the current implementation spinners opportunistically set
// _succ so that exiting threads might avoid waking a successor.
// Another less appealing alternative would be for the exiting thread
// to drop the lock and then spin briefly to see if a spinner managed
// to acquire the lock. If so, the exiting thread could exit
// immediately without waking a successor, otherwise the exiting
// thread would need to dequeue and wake a successor.
// (Note that we'd need to make the post-drop spin short, but no
// shorter than the worst-case round-trip cache-line migration time.
// The dropped lock needs to become visible to the spinner, and then
// the acquisition of the lock by the spinner must become visible to
// the exiting thread).
//
// It appears that an heir-presumptive (successor) must be made ready.
// Only the current lock owner can manipulate the EntryList or
// drain _cxq, so we need to reacquire the lock. If we fail
// to reacquire the lock the responsibility for ensuring succession
// falls to the new owner.
//
if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
return ;
}
TEVENT (Exit - Reacquired) ;
} else {
if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock
OrderAccess::storeload() ;
// Ratify the previously observed values.
if (_cxq == NULL || _succ != NULL) {
TEVENT (Inflated exit - simple egress) ;
return ;
}
// inopportune interleaving -- the exiting thread (this thread)
// in the fast-exit path raced an entering thread in the slow-enter
// path.
// We have two choices:
// A. Try to reacquire the lock.
// If the CAS() fails return immediately, otherwise
// we either restart/rerun the exit operation, or simply
// fall-through into the code below which wakes a successor.
// B. If the elements forming the EntryList|cxq are TSM
// we could simply unpark() the lead thread and return
// without having set _succ.
if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
TEVENT (Inflated exit - reacquired succeeded) ;
return ;
}
TEVENT (Inflated exit - reacquired failed) ;
} else {
TEVENT (Inflated exit - complex egress) ;
}
}
guarantee (_owner == THREAD, "invariant") ;
ObjectWaiter * w = NULL ;
int QMode = Knob_QMode ;
if (QMode == 2 && _cxq != NULL) {
// QMode == 2 : cxq has precedence over EntryList.
// Try to directly wake a successor from the cxq.
// If successful, the successor will need to unlink itself from cxq.
w = _cxq ;
assert (w != NULL, "invariant") ;
assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
ExitEpilog (Self, w) ;
return ;
}
if (QMode == 3 && _cxq != NULL) {
// Aggressively drain cxq into EntryList at the first opportunity.
// This policy ensure that recently-run threads live at the head of EntryList.
// Drain _cxq into EntryList - bulk transfer.
// First, detach _cxq.
// The following loop is tantamount to: w = swap (&cxq, NULL)
w = _cxq ;
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
assert (w != NULL , "invariant") ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
// Append the RATs to the EntryList
// TODO: organize EntryList as a CDLL so we can locate the tail in constant-time.
ObjectWaiter * Tail ;
for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
if (Tail == NULL) {
_EntryList = w ;
} else {
Tail->_next = w ;
w->_prev = Tail ;
}
// Fall thru into code that tries to wake a successor from EntryList
}
if (QMode == 4 && _cxq != NULL) {
// Aggressively drain cxq into EntryList at the first opportunity.
// This policy ensure that recently-run threads live at the head of EntryList.
// Drain _cxq into EntryList - bulk transfer.
// First, detach _cxq.
// The following loop is tantamount to: w = swap (&cxq, NULL)
w = _cxq ;
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
assert (w != NULL , "invariant") ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
// Prepend the RATs to the EntryList
if (_EntryList != NULL) {
q->_next = _EntryList ;
_EntryList->_prev = q ;
}
_EntryList = w ;
// Fall thru into code that tries to wake a successor from EntryList
}
w = _EntryList ;
if (w != NULL) {
// I'd like to write: guarantee (w->_thread != Self).
// But in practice an exiting thread may find itself on the EntryList.
// Lets say thread T1 calls O.wait(). Wait() enqueues T1 on O's waitset and
// then calls exit(). Exit release the lock by setting O._owner to NULL.
// Lets say T1 then stalls. T2 acquires O and calls O.notify(). The
// notify() operation moves T1 from O's waitset to O's EntryList. T2 then
// release the lock "O". T2 resumes immediately after the ST of null into
// _owner, above. T2 notices that the EntryList is populated, so it
// reacquires the lock and then finds itself on the EntryList.
// Given all that, we have to tolerate the circumstance where "w" is
// associated with Self.
assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
// If we find that both _cxq and EntryList are null then just
// re-run the exit protocol from the top.
w = _cxq ;
if (w == NULL) continue ;
// Drain _cxq into EntryList - bulk transfer.
// First, detach _cxq.
// The following loop is tantamount to: w = swap (&cxq, NULL)
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
TEVENT (Inflated exit - drain cxq into EntryList) ;
assert (w != NULL , "invariant") ;
assert (_EntryList == NULL , "invariant") ;
// Convert the LIFO SLL anchored by _cxq into a DLL.
// The list reorganization step operates in O(LENGTH(w)) time.
// It's critical that this step operate quickly as
// "Self" still holds the outer-lock, restricting parallelism
// and effectively lengthening the critical section.
// Invariant: s chases t chases u.
// TODO-FIXME: consider changing EntryList from a DLL to a CDLL so
// we have faster access to the tail.
if (QMode == 1) {
// QMode == 1 : drain cxq to EntryList, reversing order
// We also reverse the order of the list.
ObjectWaiter * s = NULL ;
ObjectWaiter * t = w ;
ObjectWaiter * u = NULL ;
while (t != NULL) {
guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;
t->TState = ObjectWaiter::TS_ENTER ;
u = t->_next ;
t->_prev = u ;
t->_next = s ;
s = t;
t = u ;
}
_EntryList = s ;
assert (s != NULL, "invariant") ;
} else {
// QMode == 0 or QMode == 2
_EntryList = w ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
}
// In 1-0 mode we need: ST EntryList; MEMBAR #storestore; ST _owner = NULL
// The MEMBAR is satisfied by the release_store() operation in ExitEpilog().
// See if we can abdicate to a spinner instead of waking a thread.
// A primary goal of the implementation is to reduce the
// context-switch rate.
if (_succ != NULL) continue;
w = _EntryList ;
if (w != NULL) {
guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
}
}
2.2.5.1 检查锁
cpp
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * Self = THREAD ;
- 定义
exit
方法,接收一个表示线程是否未被挂起的布尔值not_suspended
和一个TRAPS
参数,用于异常处理。Self
是当前尝试释放锁的线程。
cpp
if (THREAD != _owner) {
- 检查当前线程是否是锁的拥有者。
cpp
if (THREAD->is_lock_owned((address) _owner)) {
- 如果当前线程不是锁的直接拥有者,检查是否由于锁重入(递归锁定)导致当前线程间接拥有锁。
cpp
_owner = THREAD ;
_recursions = 0 ;
OwnerIsThread = 1 ;
- 如果当前线程间接拥有锁,更新锁的拥有者为当前线程,并重置递归计数。
cpp
} else {
assert(false, "Non-balanced monitor enter/exit!");
return;
}
}
- 如果当前线程既不是直接拥有者也不是间接拥有者,说明存在未平衡的监视器进入/退出,这是一个错误情况。
2.2.5.2 重入锁释放
cpp
if (_recursions != 0) {
_recursions--; // this is simple recursive enter
return ;
}
- 如果
_recursions
不为0,说明当前线程之前递归地获取了锁,现在递减递归计数并直接返回。
2.2.5.3 清除负责线程标记,准备释放锁
cpp
if ((SyncFlags & 4) == 0) {
_Responsible = NULL ;
}
- 清除负责线程标记,准备释放锁。
2.2.5.4 释放锁。
cpp
for (;;) {
- 使用无限循环尝试释放锁并唤醒等待队列中的线程。
cpp
if (Knob_ExitPolicy == 0) {
OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock
- 使用
OrderAccess::release_store_ptr
原子操作释放锁,将_owner
设置为NULL
。
cpp
if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
return ;
}
- 如果入口列表(
_EntryList
)和条件变量队列(_cxq
)都为空,或者已经有一个后继线程(_succ
),则直接返回。
2.2.5.5 尝试重新获取锁以确保后继线程的唤醒。如果CAS操作失败,说明锁已经被其他线程获取,当前线程可以安全退出。
cpp
if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
return ;
}
- 尝试重新获取锁以确保后继线程的唤醒。如果CAS操作失败,说明锁已经被其他线程获取,当前线程可以安全退出。
2.2.5.6 根据不同的队列管理策略,决定如何处理等待线程的唤醒和队列的重组
2.2.5.6.1 QMode == 2 && _cxq != NULL,从_cxq唤醒线程
下面这段代码是ObjectMonitor
类的exit
方法中处理等待队列(_cxq
)和入口列表(_EntryList
)的部分。这里主要根据不同的队列管理策略(QMode
),决定如何处理等待线程的唤醒和队列的重组。下面是对这段代码的详细解释:
cpp
ObjectWaiter * w = NULL ;
int QMode = Knob_QMode ;
- 定义一个指向
ObjectWaiter
对象的指针w
,用于遍历等待队列。QMode
是一个配置参数,决定队列管理的策略。
cpp
if (QMode == 2 && _cxq != NULL) {
- 如果
QMode
为2,表示条件变量队列(_cxq
)优先于入口列表(_EntryList
)。如果_cxq
不为空,则尝试直接从_cxq
唤醒后继线程。
cpp
w = _cxq ;
assert (w != NULL, "invariant") ;
assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
ExitEpilog (Self, w) ;
return ;
- 将
w
指向_cxq
的头部,确保w
不为空且其状态为TS_CXQ
(表示线程在_cxq
队列中)。然后调用ExitEpilog
方法处理线程的唤醒逻辑,并退出。
2.2.5.6.2 QMode == 3 && _cxq != NULL,_cxq转移到_EntryList尾部
当QMode
等于3且_cxq
(条件变量队列)不为空时,这段代码的目的是将_cxq
中的线程以批量方式迅速转移到_EntryList
(入口列表)中。这种策略确保最近运行的线程位于_EntryList
的头部。下面是对这段代码的详细解释:
cpp
w = _cxq ;
- 将
w
指向_cxq
的头部,准备进行队列转移。
cpp
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
- 使用一个无限循环和
Atomic::cmpxchg_ptr
操作尝试将_cxq
置为NULL
,同时保留原队列的头部指针w
。这相当于将整个_cxq
队列从监视器对象中分离出来。
cpp
ObjectWaiter * q = NULL ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
- 遍历从
_cxq
分离出来的队列,将每个等待者的状态从TS_CXQ
(表示线程在条件变量队列中)更改为TS_ENTER
(表示线程准备进入锁的竞争)。同时,重建双向链表的链接,为将这些线程追加到_EntryList
做准备。
cpp
ObjectWaiter * Tail ;
for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
- 遍历
_EntryList
以找到其尾部。
cpp
if (Tail == NULL) {
_EntryList = w ;
} else {
Tail->_next = w ;
w->_prev = Tail ;
}
- 如果
_EntryList
为空,则直接将从_cxq
分离出来的队列设置为_EntryList
。否则,将这个队列追加到_EntryList
的尾部。
这种处理方式确保了最近运行的线程(即那些刚从_cxq
转移到_EntryList
的线程)位于_EntryList
的头部,从而有机会更快地获得锁。这对于提高线程调度的公平性和效率是有益的。
2.2.5.6.3 QMode == 4 && _cxq != NULL,_cxq转移到_EntryList头部
当QMode
等于4且_cxq
(条件变量队列)不为空时,这段代码的目的是将_cxq
中的线程以批量方式迅速转移到_EntryList
(入口列表)中,并确保这些线程位于_EntryList
的头部。这种策略旨在确保最近运行的线程能够优先获得锁。下面是对这段代码的详细解释:
cpp
w = _cxq ;
- 将
w
指向_cxq
的头部,准备进行队列转移。
cpp
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
- 使用一个无限循环和
Atomic::cmpxchg_ptr
操作尝试将_cxq
置为NULL
,同时保留原队列的头部指针w
。这相当于将整个_cxq
队列从监视器对象中分离出来。
cpp
ObjectWaiter * q = NULL ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
- 遍历从
_cxq
分离出来的队列,将每个等待者的状态从TS_CXQ
(表示线程在条件变量队列中)更改为TS_ENTER
(表示线程准备进入锁的竞争)。同时,重建双向链表的链接,为将这些线程插入到_EntryList
的头部做准备。
cpp
if (_EntryList != NULL) {
q->_next = _EntryList ;
_EntryList->_prev = q ;
}
_EntryList = w ;
- 如果
_EntryList
不为空,则将从_cxq
分离出来的队列插入到_EntryList
的头部。这样做是为了确保最近运行的线程(即那些刚从_cxq
转移到_EntryList
的线程)能够优先获得锁。
这种处理方式与QMode
为3时的主要区别在于,QMode
为4时是将_cxq
的线程插入到_EntryList
的头部,而不是追加到尾部。这有助于确保最近等待的线程能够更快地获得锁,从而提高了锁的获取效率和线程调度的公平性。
2.2.5.7 如果_EntryList和_cxq都为空,则跳出循环,否则_cxq非空,则将_cxq
置为NULL
,然后将原_cxq
中的线程转移到_EntryList
主要处理线程退出监视器时的逻辑,包括处理等待队列(_cxq
)和入口列表(_EntryList
)。下面是对这段代码的详细解释:
cpp
w = _EntryList ;
- 将
w
指向入口列表(_EntryList
)的头部,准备检查是否有等待线程需要被唤醒。
cpp
if (w != NULL) {
- 如果入口列表不为空,说明有线程正在等待获取锁。
cpp
assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
- 断言等待线程的状态为
TS_ENTER
,表示线程准备进入锁的竞争。然后调用ExitEpilog
方法处理线程的唤醒逻辑,并退出当前方法。
cpp
w = _cxq ;
if (w == NULL) continue ;
- 如果_EntryList入口列表为空,则检查条件变量队列(
_cxq
)。如果_cxq
也为空,继续执行循环的下一次迭代。
cpp
for (;;) {
// 这行代码执行一个原子的比较并交换操作。Atomic::cmpxchg_ptr函数的参数分别是期望值NULL、目标内存地址&_cxq和新值w。这个操作的含义是:如果_cxq当前的值等于NULL(期望值),则将_cxq设置为w;否则,不做任何改变。操作完成后,返回_cxq操作前的值。
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
// 如果u(_cxq操作前的值)等于w,说明_cxq已经成功设置为NULL,因为这意味着在执行cmpxchg_ptr操作时,_cxq的值就是w,满足了将_cxq设置为NULL的条件。此时,跳出循环。
if (u == w) break ;
// 如果u不等于w,说明_cxq在执行cmpxchg_ptr操作时的值不是w,可能是其他线程已经修改了_cxq的值。此时,将w更新为_cxq操作前的值u,然后继续循环尝试。
w = u ;
}
- 使用一个无限循环和
Atomic::cmpxchg_ptr
操作尝试将_cxq
置为NULL
,同时保留原队列的头部指针w
。这相当于将整个_cxq
队列从监视器对象中分离出来。 - 这个循环的目的是确保_cxq能够被安全地设置为NULL,同时保留原队列的头部指针w,以便后续操作。
cpp
TEVENT (Inflated exit - drain cxq into EntryList) ;
assert (w != NULL, "invariant") ;
assert (_EntryList == NULL, "invariant") ;
- 记录一个事件,表示正在将
_cxq
中的线程转移到_EntryList
。然后断言w
不为空且_EntryList
为空,确保状态的一致性。
这段代码的主要目的是在线程退出监视器时,检查并处理等待队列,确保等待获取锁的线程能够被正确唤醒。如果入口列表不为空,则直接处理唤醒逻辑;如果入口列表为空但条件变量队列不为空,则将条件变量队列中的线程转移到入口列表中,准备唤醒这些线程。这样做确保了线程在释放锁时能够正确地管理等待队列,维护了锁的公平性和效率。
2.2.5.8 在QMode(队列管理模式)为1时,它将_cxq中的线程以反转顺序转移到_EntryList中,同时将_cxq从一个后进先出(LIFO)的单向链表(SLL)转换为一个双向链表(DLL)。如果QMode不是1,即为0或2,它则直接将_cxq转移到_EntryList,但保持原有顺序。下面是对这段代码的详细解释:
cpp
if (QMode == 1) {
- 当
QMode
等于1时,意味着需要将_cxq
转移到_EntryList
,并且反转顺序。
cpp
ObjectWaiter * s = NULL ;
ObjectWaiter * t = w ;
ObjectWaiter * u = NULL ;
- 初始化三个指针:
s
用于构建新的双向链表,t
用于遍历原始的_cxq
链表,u
用于临时存储下一个节点。
cpp
while (t != NULL) {
guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;
t->TState = ObjectWaiter::TS_ENTER ;
u = t->_next ;
t->_prev = u ;
t->_next = s ;
s = t;
t = u ;
}
- 遍历
_cxq
链表,将每个节点的状态从TS_CXQ
更改为TS_ENTER
,表示线程准备进入锁的竞争。同时,反转链表的方向,将原本的后进先出(LIFO)顺序转换为先进先出(FIFO)顺序。
cpp
_EntryList = s ;
- 将反转后的链表赋值给
_EntryList
,完成转移和反转操作。
cpp
} else {
_EntryList = w ;
- 如果
QMode
不是1,即为0或2,直接将_cxq
赋值给_EntryList
,不进行反转。
cpp
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
- 遍历
_cxq
链表,将每个节点的状态从TS_CXQ
更改为TS_ENTER
,并构建双向链表的链接。
这段代码的关键在于处理不同的队列管理模式下,如何有效地将等待线程从条件变量队列转移到入口列表,并根据需要调整线程的唤醒顺序。在QMode
为1时,通过反转链表顺序,可以改变线程的唤醒顺序,这可能对某些场景下的锁竞争和线程调度策略有特定的优化效果。
2.2.5.9 _EntryList列表有数据则唤醒数据
主要处理线程退出监视器时的逻辑,尤其是在1-0模式下的操作。下面是对这段代码的详细解释:
cpp
if (_succ != NULL) continue;
- 检查
_succ
(后继线程)是否不为空。如果不为空,意味着已经有一个线程被选为下一个获取锁的候选者,因此当前线程可以继续执行循环的下一次迭代,而不需要唤醒其他线程。这有助于减少上下文切换的频率。
cpp
w = _EntryList ;
- 将
w
指向入口列表(_EntryList
)的头部,准备检查是否有等待线程需要被唤醒。
cpp
if (w != NULL) {
- 如果入口列表不为空,说明有线程正在等待获取锁。
cpp
guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
- 断言等待线程的状态为
TS_ENTER
,表示线程准备进入锁的竞争。这是一个不变量,确保系统的一致性和正确性。
cpp
ExitEpilog (Self, w) ;
return ;
- 调用
ExitEpilog
方法处理线程的唤醒逻辑,并退出当前方法。ExitEpilog
方法负责执行必要的操作来唤醒等待的线程,包括设置相关的状态和可能的内存屏障(MEMBAR)操作。
这段代码的主要目的是在线程退出监视器时,检查并处理等待队列,确保等待获取锁的线程能够被正确唤醒。通过检查_succ
变量,代码尝试避免不必要的线程唤醒,从而减少上下文切换的频率,提高系统的效率。如果入口列表中有等待的线程,那么通过调用ExitEpilog
方法来唤醒这些线程,确保锁的公平性和效率。