startThreadC();
sleep(100);
log(“start notify”);
lock.notify();
log(“release lock”);
}
},“thread-B”).start();
}
public void startThreadC(){
new Thread(() -> {
synchronized (lock){
log(“get lock”);
log(“release lock”);
}
}, “thread-C”).start();
}
public static void main(String[] args){
new NotifyDemo().startThreadA();
}
}
以上就是本次实战用到的demo,代码功能简述如下:
-
启动线程A,取得锁之后先启动线程B再执行wait()方法,释放锁并等待;
-
线程B启动之后会等待锁,A线程执行wait()之后,线程B取得锁,然后启动线程C,再执行notify唤醒线程A,最后退出synchronize代码块,释放锁;
-
线程C启动之后就一直在等待锁,这时候线程B还没有退出synchronize代码块,锁还在线程B手里;
-
线程A在线程B执行notify()之后就一直在等待锁,这时候线程B还没有退出synchronize代码块,锁还在线程B手里;
-
线程B退出synchronize代码块,释放锁之后,线程A和线程C竞争锁;
把上面的代码在Openjdk8下面执行,反复执行多次,都得到以下结果:
thread-A : get lock
thread-A : start wait
thread-B : get lock
thread-C : c thread is start
thread-B : start notify
thread-B : release lock
thread-A : after wait, acquire lock again
thread-A : release lock
thread-C : get lock
thread-C : release lock
针对以上结果,问题来了:第一个问题:将以上代码反复执行多次,结果都是B释放锁之后A会先得到锁,这又是为什么呢?C为何不能先拿到锁呢?
第二个问题:线程C自开始就执行了monitorenter指令,它能得到锁是容易理解的,但是线程A呢?在wait()之后并没有没有monitorenter指令,那么它又是如何取得锁的呢?
wait()、notify()这些方法都是native方法,所以只有从JVM源码寻找答案了,本次阅读的是openjdk8的源码;
带上问题去看JVM源码
按照demo代码执行顺序,我整理了如下问题,带着这些问题去看JVM源码可以聚焦主线,不要被一些支线的次要的代码卡住(例如一些异常处理,监控和上报等):
-
线程A在wait()的时候做了什么?
-
线程C启动后,由于此时线程B持有锁,那么线程C此时在干啥?
-
线程B在notify()的时候做了什么?
-
线程B释放锁的时候做了什么?
源码中最重要的注释信息
在源码中有段注释堪称是整篇文章最重要的说明,请大家始终记住这段信息,处处都用得上:
ObjectWaiter对象存在于WaitSet、EntryList、cxq等集合中,或者正在这些集合中移动
原文如下:
请务必记住这三个集合:WaitSet、EntryList、cxq
好了,接下来看源码分析问题吧:
线程A在wait()的时候做了什么
打开hotspot/src/share/vm/runtime/objectMonitor.cpp,看ObjectMonitor::wait方法:
如上图所示,有两处代码值得我们注意:
-
绿框中将当前线程包装成ObjectWaiter对象,并且状态为TS_WAIT,这里对应的是jstack看到的线程状态WAITING;
-
红框中调用了AddWaiter方法,跟进去看下:
这个ObjectWaiter对象被放入了WaitSet中,WaitSet是个环形双向链表(circular doubly linked list)
回到ObjectMonitor::wait方法接着往下看,会发现关键代码如下图,当前线程通过park()方法开始挂起(suspend):
至此,我们把wait()方法要做的事情就理清了:
-
包装成ObjectWaiter对象,状态为TS_WAIT;
-
ObjectWaiter对象被放入_WaitSet中;
-
当前线程挂起;
线程B持有锁的时候线程C在干啥
此时的线程C无法进入synchronized{}代码块,用jstack看应该是BLOCKED状态,如下图:
我们看看monitorenter指令对应的源码吧,位置:openjdk/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
“must be NULL or an object”);
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);
}
assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
“must be NULL or an object”);
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END
上面的代码有个if (UseBiasedLocking)判断,是判断是否使用偏向锁的,本例中的锁显然已经不属于当前线程C了,所以我们还是直接看slowenter(hobj, elem->lock(), CHECK)方法吧;
打开openjdk/hotspot/src/share/vm/runtime/synchronizer.cpp:
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), “should not see bias pattern here”);
//是否处于无锁状态
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())) {
//如果处于有锁状态,就检查是不是当前线程持有锁,如果是当前线程持有的,就return,然后就能执行同步代码块中的代码了
assert(lock != mark->locker(), “must not re-lock the same lock”);
assert(lock != (BasicLock*)obj->mark(), “don’t relock with same BasicLock”);
lock->set_displaced_header(NULL);
return;
}
#if 0
// The following optimization isn’t particularly useful.
if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {
lock->set_displaced_header (NULL) ;
return ;
}
#endif
// The object header will never be displaced to this lock,
// so it does not matter what the value is, except that it
// must be non-zero to avoid looking like a re-entrant lock,
// and must not look locked either.
lock->set_displaced_header(markOopDesc::unused_mark());
//锁膨胀
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
线程C在上面代码中的执行顺序如下:
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-H4vzn99F-1710826840541)]
[外链图片转存中…(img-WvoCcL4J-1710826840542)]
[外链图片转存中…(img-UyVoYcIs-1710826840542)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-Kw1bRPXN-1710826840542)]