转载请注明原创出处,谢谢!html
HappyFeet的博客java
前段时间把 Object.await() 和 Object.notify()、LockSupport.park() 和 LockSupport.unpark() 差很少理解了,不过在跟踪 C++ 源码的时候看到了 ObjectMonitor.cpp 这个类(在 await() 释放锁和被 notify() 后从新获取锁的时候会调用这部分代码);在看这里面的实现的时候,发现,咦?怎么它内部的逻辑和 synchronized 的特色如此之像!后来发现原来这里面就有 synchronized 底层实现。node
之前经过文档学习了 synchronized 的用法及特色,可是并无了解它的原理,现在终于知道了它为何有这些特色了!c++
不只如此,在这个过程当中还把以前学到的一些知识点(对象的内存布局中的 Mark Word 那一部分)串了起来,有种恍然大悟的感受!web
下面就来对 synchronized 关键字作一个整理,加深记忆。编程
/** * @author happyfeet * @since Jan 19, 2020 */
public class SynchronizedUsageExample {
private static int staticCount;
private final Object lock = new Object();
private int count;
public static synchronized int synchronizedOnStaticMethod() {
return staticCount++;
}
public synchronized void synchronizedOnInstanceMethod() {
count++;
}
public int synchronizedOnCodeBlock() {
synchronized (lock) {
return count++;
}
}
}
复制代码
很简单的一个例子,不过涵盖了 synchronized 的全部用法:安全
做用于静态方法时锁是 class 对象bash
做用于实例方法时锁是当前实例对象,即 thisoracle
做用于代码块时锁是括号里的对象,即上例中的 lock 对象app
当多个线程访问同步代码块时,须要先获取锁,有且仅有一个线程能够获取到锁,没有获取到锁的线程会被阻塞,直到获取到锁;不一样锁之间互不影响。
咱们来看个例子:
/** * @author happyfeet * @since Feb 03, 2020 */
public class SynchronizedLockExample {
private final Object lock = new Object();
public static void main(String[] args) {
SynchronizedLockExample lockExample = new SynchronizedLockExample();
Thread thread1 = new Thread(() -> lockExample.synchronizedOnCodeBlock(), "thread-1");
Thread thread2 = new Thread(() -> lockExample.synchronizedOnCodeBlock(), "thread-2");
Thread thread3 = new Thread(() -> lockExample.synchronizedOnInstanceMethod(), "thread-3");
Thread thread4 = new Thread(() -> lockExample.synchronizedOnInstanceMethod(), "thread-4");
sleepOneSecond();
thread1.start();
sleepOneSecond();
thread2.start();
sleepOneSecond();
thread3.start();
sleepOneSecond();
thread4.start();
while (true) {
}
}
private synchronized void synchronizedOnInstanceMethod() {
println("I'm in synchronizedOnInstanceMethod, thread name is {}.", Thread.currentThread().getName());
while (true) {
// do something
}
}
private void synchronizedOnCodeBlock() {
synchronized (lock) {
println("I'm in synchronizedOnCodeBlock, thread name is {}.", Thread.currentThread().getName());
while (true) {
// do something
}
}
}
private static void sleepOneSecond() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
执行 main 函数,程序输出以下:
2020-02-03T10:19:12.993 thread thread-1 : I'm in synchronizedOnCodeBlock, thread name is thread-1.
2020-02-03T10:19:14.984 thread thread-3 : I'm in synchronizedOnInstanceMethod, thread name is thread-3.
复制代码
Dump Threads,查看各个线程的运行状态,如图所示:
代码简介
很是简单的一个例子,两个方法:synchronizedOnInstanceMethod 和 synchronizedOnCodeBlock(两个方法内部均为死循环,模拟正在处理业务逻辑,线程会一直持有锁);
其中,synchronizedOnInstanceMethod 方法以 this 对象做为锁,synchronizedOnCodeBlock 以 lock 对象做为锁,而后启动了 4 个线程调用这两个方法:thread-1 和 thread-2 调用 synchronizedOnCodeBlock 方法,thread-3 和 thread-4 调用 synchronizedOnInstanceMethod 方法。
结果分析
从程序输出能够看到,thread-1 和 thread-3 都进入到了同步代码块内,处于 RUNNABLE 状态;因为 lock 和 this 锁分别被 thread-1 和 thread-3 所持有,thread-2 和 thread-4 没法获取到相应的锁,因而都处于 BLOCKED 状态。仔细看上面的截图能够发现,thread-4 正在等待获取 this 对象锁(一样的,thread-2 正在等待获取 lock 对象锁,不过截图中没有体现出来):
- waiting to lock <0x000000076acf00f0> (a com.yhh.example.concurrency.SynchronizedLockExample)
复制代码
一些拓展
同一个类中,全部被 synchronized 修饰的静态方法使用的锁都是 class 对象;全部被 synchronized 修饰的实例方法使用的锁都是 this 对象;因此,对于这种:
private synchronized void method1() {
// do something
}
private synchronized void method2() {
// do something
}
private synchronized void method3() {
// do something
}
复制代码
其实都是公用 this 对象这一把锁,若是 this 锁被线程 A 经过某个方法好比 method1() 将锁持有,那么,其余线程调用 method1()、method2()、method3() 时都会被阻塞;静态方法也是如此。
这里的核心点是:同一把锁同一时刻只能被一个线程所持有。
synchronized 是可重入锁,可重入锁指的是在一个线程中能够屡次获取同一把锁。
可重入锁最大的做用就是避免死锁。能够参考这里面的回答:java的可重入锁用在哪些场合?
以 SynchronizedUsageExample 为例,反编译获得字节码以下(省略了一些不重要的字节码):
Classfile /Users/happyfeet/projects/java/sampleJava/src/main/java/com/yhh/example/concurrency/SynchronizedUsageExample.class
...
{
public static synchronized int synchronizedOnStaticMethod();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED // 同步标记
Code:
stack=3, locals=0, args_size=0
0: getstatic #4 // Field staticCount:I
3: dup
4: iconst_1
5: iadd
6: putstatic #4 // Field staticCount:I
9: ireturn
LineNumberTable:
line 14: 0
public synchronized void synchronizedOnInstanceMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED // 同步标记
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #5 // Field count:I
5: iconst_1
6: iadd
7: putfield #5 // Field count:I
10: return
LineNumberTable:
line 18: 0
line 19: 10
public int synchronizedOnCodeBlock();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=4, locals=3, args_size=1
0: aload_0
1: getfield #3 // Field lock:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter // enter the monitor
7: aload_0
8: dup
9: getfield #5 // Field count:I
12: dup_x1
13: iconst_1
14: iadd
15: putfield #5 // Field count:I
18: aload_1
19: monitorexit // exit the monitor(正常退出)
20: ireturn
21: astore_2
22: aload_1
23: monitorexit // exit the monitor(出现异常时走的逻辑)
24: aload_2
25: athrow
Exception table:
from to target type
7 20 21 any
21 24 21 any
LineNumberTable:
line 22: 0
...
}
SourceFile: "SynchronizedUsageExample.java"
复制代码
将有 synchronized 修饰的方法与普通方法对比,能够获得:
有 synchronized 修饰的方法的 flags 中多了一个同步标记:ACC_SYNCHRONIZED
有 synchronized 修饰的代码块中出现了成对的 monitorenter 和 monitorexit(注意:上面字节码中第二个 monitorexit 是处理异常,说明在执行第一个 monitorexit 以前程序出现了异常)
这几个的做用,须要从 JVM 规范中去找,主要是这几个地方:
JVM 的同步机制是经过 monitor (监视器锁)的进入和退出实现的。这里是 JVM 所定义的规范,不只仅是 Java 语言,其余能够运行于 JVM 上的语言一样适用,例如:Scala。
对于 Java 语言来讲,比较经常使用的同步实现就是使用 synchronized 关键字,做用于方法和代码块上。
当 synchronized 做用于方法上时,反编译成字节码以后会发现方法的 flags 中会有一个同步标记:ACC_SYNCHRONIZED。
对于有 ACC_SYNCHRONIZED 标记的方法,线程在执行方法时的步骤是这样的:
当 synchronized 做用于代码块时,能够发现字节码中出现了成对的 monitorenter 和 monitorexit 指令。
咱们来看看 JVM 规范中是怎么描述这两个指令的:
做用
获取对象的 monitor
参数
objectref,必须是对象引用,若是为 null 则抛出 NPE
描述
每一个对象与一个 monitor 关联,monitor 只有在拥有 owner 的状况下才被锁定。当一个线程执行到 monitorenter 指令时会尝试获取对象锁所关联的 monitor,获取规则以下:
- 若是 monitor 的 entry count 为0,则该线程能够进入 monitor,并将 monitor 的 entry count 的值设为 1,该线程成为 monitor 的 owner;
- 若是当前线程已经拥有该 monitor,只是从新进入(reenter),则将 monitor 的 entry count 的值加 1;
- 若是 monitor 的 owner 是其余线程,则当前线程进入阻塞状态,直到 monitor 的 entry count 为 0 以后再次尝试获取 monitor。
做用
释放对象的 monitor
参数
objectref,必须是对象引用,若是为 null 则抛出 NPE
描述
执行 monitorexit 的线程必须是 objectref 关联的 monitor 的 owner,若是不是则抛出 IllegalMonitorStateException;
执行 monitorexit 的效果就是将 objectref 关联的 monitor 的 entry count 的值减 1,当 entry count 的值变为 0 时,当前线程将释放对象的 monitor,再也不是该 monitor 的 owner。
到了这,对于 synchronized 的使用基本上是没有什么问题了;不过对于 synchronized 的执行过程尚未一个完整的认识,下面咱们来看一看 synchronized 在 HotSpot 中是如何执行的?
咱们先来看看什么是偏向锁、轻量级锁和重量级锁?后续讲 synchronized 在 HotSpot 中的执行过程当中会讲到这些锁是如何获取的。
偏向锁是 JDK 1.6 加入的,其目的是消除数据在无竞争状况下的同步原语,进一步提升程序的性能。
若是开启了偏向锁,锁会偏向于第一个得到它的线程,若是后续的执行过程该锁没有被其余线程获取,则持有偏向锁的线程将永远不须要再进行同步;
而一旦出现多个线程竞争同一把锁,就必须撤销偏向锁,撤销完以后再执行轻量级锁的同步操做,此时就会带来额外的锁撤销的消耗。
偏向锁能够提升带有同步但无竞争的程序性能,但若是程序中大多数锁老是被多个不一样的线程访问,那偏向模式就是多余的;
JDK 1.6 默认开启,能够经过参数 -XX:-UseBiasedLocking 禁用偏向锁。
偏向锁是直接修改 Mark Word 中的 lock(标志位)和 biased_lock(是否偏向锁),偏向线程 ID 也直接存储于锁对象的 Mark Word 中。
轻量级锁也是 JDK 1.6 加入的,其目的是在线程交替执行的状况下,减小传统的重量级锁使用操做系统互斥量产生的性能消耗。
轻量级锁会在当前线程的栈帧中创建一个名为 Lock Record 的空间,用于存储锁对象目前的 Mark Word 的拷贝,而后经过 CAS 尝试将对象的 Mark Word 更新为指向 Lock Record 的指针。若是更新成功,说明这个线程拥有了该对象锁,同时对象 Mark Word 的锁标志位转变为 “00”,代表此对象处于轻量级锁定状态。
当两个以上线程竞争同一个锁,那么轻量级锁再也不有效,须要膨胀为重量级锁。此时除了互斥量的开销外,还有额外的轻量级锁的加锁和解锁操做,所以,在有竞争的状况下,轻量级锁会比传统的重量级锁更慢。
既然如此,那为何还须要有轻量级锁呢?由于 “对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。
重量级锁是经过对象内部的监视器(monitor)实现,锁对象的 Mark Word 指向一个类型为 ObjectMonitor 的对象指针;monitor 的本质是依赖于底层操做系统的 Mutex Lock 实现,操做系统实现线程同步时,须要进行系统调用,须要从用户态(User Mode)到内核态(Kernel Mode)的切换,代价相对较高。
三种锁的适用场景以下:
- 偏向锁:适用于只有一个线程获取锁
- 轻量级锁:适用于多个线程交替获取锁,不存在竞争
- 重量级锁:适用于多个线程竞争同一个锁
这里主要目的是对 synchronized 的执行过程有一个大概的认识,因此省略了不少代码及注释(注释其实很重要,不过为了篇幅起见,都去掉了),只保留了一些关键的 C++ 源码,如需查看完整 C++ 源码能够点击代码片断后面的连接查看。
synchronized 的 HotSpot 实现依赖于对象头的 Mark Word,关于 Mark Word 能够参考:深刻理解Java虚拟机之----对象的内存布局 中的 Mark Word 模块,这是其中一张关于 64 位系统的 Mark Word 示例图片:
对于 synchronized 方法会多一个 ACC_SYNCHRONIZED 同步标记,线程在执行方法时的步骤为:
enters the monitor
=>invoke method
=>exits the monitor
这里对于
enters the monitor
具体作了啥不是很清楚,不过我猜测应该是和 monitorenter 指令的行为是一致的;一样的,exits the monitor
与 monitorexit 指令一致。能够参考:Java synchronized是否有对应的字节码指令实现? 和 R 大的读书笔记 第146页 Threads and Synchronization - The Java bytecode implementation
如下为本身的一些理解(可能会有不对的地方),仅做参考。
templateTable_x86_64.cpp#monitorenter
:openjdk/hotspot/src/cpu/x86/vm/templateTable_x86_64.cpp
synchronized 代码块的字节码会出现成对的 monitorenter 和 monitorexit 指令,那么这两个指令是如何发挥做用的呢?
参考 R 大读书笔记 第 232 页 7.2.1 Interpreter 模块 及 JVM 之模板解释器
JVM 在执行 monitorenter 指令的入口为:
void TemplateTable::monitorenter() {
...
// store object
__ movptr(Address(c_rarg1, BasicObjectLock::obj_offset_in_bytes()), rax);
__ lock_object(c_rarg1);
...
// The bcp has already been incremented. Just need to dispatch to
// next instruction.
__ dispatch_next(vtos);
}
复制代码
这一段主要是汇编代码,很晦涩,太难懂。
实话实说,我是没看懂这一段代码在干吗,一脸懵逼,直接进入下一个方法:lock_object。
interp_masm_x86_64.cpp#lock_object
:openjdk/hotspot/src/cpu/x86/vm/interp_masm_x86_64.cpp
void InterpreterMacroAssembler::lock_object(Register lock_reg) {
assert(lock_reg == c_rarg1, "The argument is only for looks. It must be c_rarg1");
if (UseHeavyMonitors) {
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
lock_reg);
} else {
Label done;
...
if (UseBiasedLocking) {
biased_locking_enter(lock_reg, obj_reg, swap_reg, rscratch1, false, done, &slow_case);
}
...
// Call the runtime routine for slow case
call_VM(noreg,
CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter),
lock_reg);
bind(done);
}
}
复制代码
这里也一样基本上没看懂,有几个关键字眼能够推敲一番:UseHeavyMonitors、UseBiasedLocking,进入下一个方法:InterpreterRuntime::monitorenter
interpreterRuntime.cpp#monitorenter
:openjdk/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
...
Handle h_obj(thread, elem->obj());
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
...
IRT_END
复制代码
BasicObjectLock 类型的 elem 对象包含一个 BasicLock 类型的 _lock 对象和一个指向 Object 对象的指针 _obj;
class BasicObjectLock VALUE_OBJ_CLASS_SPEC {
friend class VMStructs;
private:
BasicLock _lock; // the lock, must be double word aligned
oop _obj; // object holds the lock;
public:
// Manipulation
oop obj() const { return _obj; }
void set_obj(oop obj) { _obj = obj; }
BasicLock* lock() { return &_lock; }
...
};
复制代码
class BasicLock VALUE_OBJ_CLASS_SPEC {
friend class VMStructs;
private:
volatile markOop _displaced_header;
public:
markOop displaced_header() const { return _displaced_header; }
void set_displaced_header(markOop header) { _displaced_header = header; }
...
};
复制代码
UseBiasedLocking:标识 JVM 是否开启偏向锁功能,若是开启则执行 fast_enter 逻辑,不然执行 slow_enter;
synchronizer.cpp#fast_enter
:openjdk/hotspot/src/share/vm/runtime/synchronizer.cpp
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
if (UseBiasedLocking) {
if (!SafepointSynchronize::is_at_safepoint()) {
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
return;
}
} else {
assert(!attempt_rebias, "can not rebias toward VM thread");
BiasedLocking::revoke_at_safepoint(obj);
}
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
slow_enter (obj, lock, THREAD) ;
}
复制代码
偏向锁的获取
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
这个方法作的事情就是获取偏向锁。点进去一看,又是一大块代码。。。并且还不是很容易理解。。。
参考 《深刻理解 JVM 虚拟机》这本书和战小狼的这篇博客 JVM源码分析之synchronized实现 加以理解,大体逻辑为:
(a)获取锁对象的对象头中的 Mark Word,判断是否是可偏向状态;
假设当前虚拟机启用了偏向锁(启用参数:-XX:+UseBiasedLocking,这是 JDK 1.6 的默认值),那么,当锁对象第一次被线程获取的时候,虚拟机就会把对象头中的标志位设为 “01”,便可偏向状态。
(b)判断 Mark Word 中的偏向线程 ID:若是为空,则进入步骤(c);若是指向当前线程,说明当前线程是偏向锁的持有者,能够不须要同步,直接执行同步块代码;若是指向其它线程,进入步骤(d);
(c)经过 CAS 设置 Mark Word 中的偏向线程 ID 为当前线程 ID,若是设置成功,当前线程成为了偏向锁的持有者,能够直接执行同步块代码;不然进入步骤(d);
(d)若是是从第(b)步直接进入步骤(d),说明当前线程尝试获取一个已经处于偏向模式的锁;而从第(c)步进入的步骤(d),说明当前还有其余线程在竞争锁,且当前线程竞争失败了;这两种状况都会使得锁的偏向模式宣告结束。当到达全局安全点(safepoint),得到偏向锁的线程被挂起,并撤销偏向锁,执行 slow_enter 将锁升级;升级完成后被阻塞在安全点的线程尝试获取升级以后的锁。
这里撤销偏向锁并将锁升级,会根据锁对象目前是否处于被锁定的状态分为两种状况:未锁定(标志位为 “01”)和轻量级锁定(标志位为 “00”)。
synchronizer.cpp#slow_enter
:openjdk/hotspot/src/share/vm/runtime/synchronizer.cpp
fast_enter 获取偏向锁失败,就会进入到 slow_enter 方法中,而这里包含了获取轻量级锁的实现。
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
if (mark->is_neutral()) {
// Anticipate successful CAS -- the ST of the displaced mark must
// be visible <= the ST performed by the CAS.
lock->set_displaced_header(mark);
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT (slow_enter: release stacklock) ;
return ;
}
// Fall through to inflate() ...
} else
if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
lock->set_displaced_header(NULL);
return;
}
...
lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
复制代码
轻量级锁的获取
(a)mark->is_neutral()
判断 Mark Word 是否处于未锁定状态,若是是,进入第(c)步,不然进入第(b)步;
(b)若是当前线程是轻量级锁的持有者,能够不须要同步,直接执行同步块代码;不然,进入步骤(d);
(c)经过 CAS 设置锁对象的 Mark Word 为 lock,若是设置成功,当前线程成为了轻量级锁的持有者,能够直接执行同步块代码;不然进入步骤(d);
(d)若是是从第(b)步直接进入步骤(d),说明当前线程与一个已经处于轻量级锁的线程争用同一个锁;而从第(c)步进入的步骤(d),说明当前还有其余线程在竞争锁,且当前线程竞争失败了;若是有两个以上的线程争用同一个锁,那轻量级锁也再也不有效,须要执行 inflate 将锁膨胀为重量级锁,同时将锁标志的状态值变为 “10”。
synchronizer.cpp#inflate
:openjdk/hotspot/src/share/vm/runtime/synchronizer.cpp
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
...
for (;;) {
const markOop mark = object->mark() ;
// The mark can be in one of the following states:
// * Inflated - just return
// * Stack-locked - coerce it to inflated
// * INFLATING - busy wait for conversion to complete
// * Neutral - aggressively inflate the object.
// * BIASED - Illegal. We should never see this
// CASE: inflated
if (mark->has_monitor()) {
ObjectMonitor * inf = mark->monitor() ;
...
return inf ;
}
// CASE: inflation in progress - inflating over a stack-lock.
// Some other thread is converting from stack-locked to inflated.
...
if (mark == markOopDesc::INFLATING()) {
TEVENT (Inflate: spin while INFLATING) ;
ReadStableMark(object) ;
continue ;
}
// CASE: stack-locked
...
if (mark->has_locker()) {
ObjectMonitor * m = omAlloc (Self) ;
m->Recycle();
m->_Responsible = NULL ;
m->OwnerIsThread = 0 ;
m->_recursions = 0 ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // Consider: maintain by type/class
markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
if (cmp != mark) {
omRelease (Self, m, true) ;
continue ; // Interference -- just retry
}
...
markOop dmw = mark->displaced_mark_helper() ;
assert (dmw->is_neutral(), "invariant") ;
m->set_header(dmw) ;
m->set_owner(mark->locker());
m->set_object(object);
object->release_set_mark(markOopDesc::encode(m));
// Hopefully the performance counters are allocated on distinct cache lines
// to avoid false sharing on MP systems ...
if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
TEVENT(Inflate: overwrite stacklock) ;
if (TraceMonitorInflation) {
if (object->is_instance()) {
ResourceMark rm;
tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(void *) object, (intptr_t) object->mark(),
object->klass()->external_name());
}
}
return m ;
}
// CASE: neutral
ObjectMonitor * m = omAlloc (Self) ;
// prepare m for installation - set monitor to initial state
m->Recycle();
m->set_header(mark);
m->set_owner(NULL);
m->set_object(object);
m->OwnerIsThread = 1 ;
m->_recursions = 0 ;
m->_Responsible = NULL ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // consider: keep metastats by type/class
if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
m->set_object (NULL) ;
m->set_owner (NULL) ;
m->OwnerIsThread = 0 ;
m->Recycle() ;
omRelease (Self, m, true) ;
m = NULL ;
continue ;
// interference - the markword changed - just retry.
// The state-transitions are one-way, so there's no chance of
// live-lock -- "Inflated" is an absorbing state.
}
// Hopefully the performance counters are allocated on distinct
// cache lines to avoid false sharing on MP systems ...
if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
TEVENT(Inflate: overwrite neutral) ;
if (TraceMonitorInflation) {
if (object->is_instance()) {
ResourceMark rm;
tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(void *) object, (intptr_t) object->mark(),
object->klass()->external_name());
}
}
return m ;
}
}
复制代码
锁的膨胀过程
这一段代码乍一看很长,真的很长,不过上面已是删减了不少代码以后的版本了。细看以后发现其实这一段代码很容易理解:
(a)首先,这个代码最终是要获得一个 ObjectMonitor 对象,后续会调用其 enter 方法。
(b)而且它是一个自旋方法: for (;;) {}
(c)接着它列举了 mark 的五种状态以及在这五种状态下的操做(实际上第五种是不可达状态):
// The mark can be in one of the following states:
// * Inflated - just return
// * Stack-locked - coerce it to inflated
// * INFLATING - busy wait for conversion to complete
// * Neutral - aggressively inflate the object.
// * BIASED - Illegal. We should never see this
复制代码
(d)当锁膨胀完成,返回对应的 ObjectMonitor 对象以后,并不表示该线程竞争到了锁,真正的锁竞争发生在 ObjectMonitor::enter 方法中。
objectMonitor.cpp#enter
:openjdk/hotspot/src/share/vm/runtime/objectMonitor.cpp
void ATTR ObjectMonitor::enter(TRAPS) {
Thread * const Self = THREAD ;
void * cur ;
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
if (cur == NULL) {
// Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
assert (_recursions == 0 , "invariant") ;
assert (_owner == Self, "invariant") ;
// CONSIDER: set or assert OwnerIsThread == 1
return ;
}
if (cur == Self) {
// TODO-FIXME: check for integer overflow! BUGID 6557169.
_recursions ++ ;
return ;
}
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
_recursions = 1 ;
// Commute owner from a thread-specific on-stack BasicLockObject address to
// a full-fledged "Thread *".
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
...
// TODO-FIXME: change the following for(;;) loop to straight-line code.
for (;;) {
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition()
// or java_suspend_self()
EnterI (THREAD) ;
if (!ExitSuspendEquivalent(jt)) break ;
//
// We have acquired the contended monitor, but while we were
// waiting another thread suspended us. We don't want to enter
// the monitor while suspended because that would surprise the
// thread that suspended us.
//
_recursions = 0 ;
_succ = NULL ;
exit (false, Self) ;
jt->java_suspend_self();
}
...
}
...
}
复制代码
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
首先咱们要明白,这里返回的 cur 是 &_owner 的实际原值;
(a)若是 cur == NULL,说明该线程执行 cmpxchg_ptr 成功,成功获取到锁,直接返回;
(b)若是 cur == Self,即以前持有锁的线程就是本身,说明这是一个重入操做,只须要将 _recursions 加 1 以后返回便可;
(c)若是 Self->is_lock_owned ((address)cur),这里,我以为能够这样理解:首先明白一点,这也是一个重入操做,可是它是一个特殊的重入操做;由于以前持有锁的 &_owner 其实也是当前线程,可是这个 cur 和 Self 没办法经过 == 判断出来,须要用另外一种方式才能够判断,即 Self->is_lock_owned ((address)cur);这种方式判断出来若是为真,那么 _recursions 的值也必定为 0,而后将 Self 赋给 _owner,同时, _recursions 设置为 1,而后返回;
(d)若是上面三种状况都不知足,那就说明真正遇到了竞争;紧接着一个自旋操做竞争锁:先经过 EnterI 方法竞争锁,而后判断在获取锁的这段时间内,若是没有其余线程挂起本身,那么获取锁成功,返回;不然将锁释放,同时执行 jt->java_suspend_self() 等待挂起本身的线程将本身唤醒。
咱们接下来看 EnterI 这个方法,EnterI 方法才是真正实现竞争锁的地方。
objectMonitor.cpp#EnterI
:openjdk/hotspot/src/share/vm/runtime/objectMonitor.cpp
void ATTR ObjectMonitor::EnterI (TRAPS) {
Thread * Self = THREAD ;
// Try the lock - TATAS
if (TryLock (Self) > 0) {
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
...
if (TrySpin (Self) > 0) {
assert (_owner == Self , "invariant") ;
assert (_succ != Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
// The Spin failed -- Enqueue and park the thread ...
ObjectWaiter node(Self) ;
Self->_ParkEvent->reset() ;
node._prev = (ObjectWaiter *) 0xBAD ;
node.TState = ObjectWaiter::TS_CXQ ;
// Push "Self" onto the front of the _cxq.
// Once on cxq/EntryList, Self stays on-queue until it acquires the lock.
// Note that spinning tends to reduce the rate at which threads
// enqueue and dequeue on EntryList|cxq.
ObjectWaiter * nxt ;
for (;;) {
node._next = nxt = _cxq ;
if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
// Interference - the CAS failed because _cxq changed. Just retry.
// As an optional optimization we retry the lock.
if (TryLock (Self) > 0) {
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
}
...
for (;;) {
if (TryLock (Self) > 0) break ;
// park self
if (_Responsible == Self || (SyncFlags & 1)) {
TEVENT (Inflated enter - park TIMED) ;
Self->_ParkEvent->park ((jlong) RecheckInterval) ;
// Increase the RecheckInterval, but clamp the value.
RecheckInterval *= 8 ;
if (RecheckInterval > 1000) RecheckInterval = 1000 ;
} else {
TEVENT (Inflated enter - park UNTIMED) ;
Self->_ParkEvent->park() ;
}
if (TryLock(Self) > 0) break ;
...
}
UnlinkAfterAcquire (Self, &node) ;
if (_succ == Self) _succ = NULL ;
assert (_succ != Self, "invariant") ;
if (_Responsible == Self) {
_Responsible = NULL ;
OrderAccess::fence(); // Dekker pivot-point
}
if (SyncFlags & 8) {
OrderAccess::fence() ;
}
return ;
}
复制代码
若是看过 AQS 的源码的人来看这一段代码,会发现这段代码和 AQS 的 ConditionObject 的行为很类似。下面咱们来看 EnterI 是怎么去竞争锁的:
(a)TryLock (Self) 尝试一下获取锁,若是返回值 > 0,说明获取锁成功,返回;
(b)TrySpin (Self) 在入队以前自旋一小段时间尝试获取锁,若是返回值 > 0,说明获取锁成功,返回;
(c)若是前面两个操做都没成功,就建立一个 ObjectWaiter 类型的 node,加入到 cxq/EntryList 的队首;入队是一个自旋操做,若是入队失败,再次尝试获取锁,若是获取成功,直接返回;不然继续执行入队操做;
(d)执行到这,说明 Self 已经在队列里面了,这里又是一个自旋操做:TryLock + park();尝试获取锁,若是获取成功,进入(e);不然挂起本身,等待被唤醒(当持有锁的线程释放锁时,会唤醒最先进入等待队列的节点)。当该线程唤醒以后,会从挂起的点继续执行。
(e)获取到锁,将本身从队列中移除,返回。
至此,关于 monitorenter 的执行过程基本上就结束了;而 monitorexit 的执行过程和 monitorenter 基本相似,能够参考 monitorenter 的执行过程自行理解,这里再也不进行讲述。这里有一点须要注意,执行 monitorexit 时,会在代码的最后最后调用 unpark 用于唤醒阻塞于 EnterI 方法中的线程。
遗留的问题
看过了 synchronized 底层源码以后再来回看《深刻理解 Java 虚拟机》中关于 synchronized 的部分,发现原来本身的这些理解书本上原本就有,并且详细的很,难免想到以前看到的口天师兄的这个回答:你的编程能力从何时开始日新月异?,真的很是精辟。
写这篇文章花了挺长时间的,不过收获也确实蛮多的,说说本身的感觉吧:
参考资料:
(4)monitorenter
(5)monitorexit
(6)《深刻理解 Java 虚拟机》第二版 周志明 著.