转载请注明原创出处,谢谢!html
HappyFeet的博客java
最近在忙着找工做、找房子,事儿也挺多的,加上又换了个城市,也就没什么心思写博客了。现在工做已定,房子也租好了,是时候调整好本身的心态,开始写博客了。linux
说实话,这段时间面了很多公司,和不少面试官交流了许多,感触颇多,不过目前还没想好怎么写,我会尽快将这段时间辞职及面试的体会整理成一篇博客发表出来,还请你们耐心等待!c++
暂且把面试的事搁下,我们今天来聊 LockSupport.park() 和 LockSupport.unpark() 的底层原理。git
为何会聊到这两个方法呢?缘由是在阅读 AQS 的源码的时候发现这两个方法调用的次数很是多!因此在继续深刻阅读 AQS 源码以前,先来熟悉一下 LockSupport.park() 和 LockSupport.unpark() 的底层实现,为后续 AQS 的学习打下基础。github
park 翻译成中文是 "停放" 的意思,在代码中的该方法含义是 "挂起" 当前线程。面试
实际上,LockSupport 类中所提供的方法有许多,经常使用的有下面这几个:api
其中 unpark 用于唤醒线程,其余三个均用于挂起线程。bash
挂起线程又分为无限期和有限期挂起,对应到线程的状态是 WAITING(无限期等待)和 TIMED_WAITING(限期等待)。oracle
一个被无限期挂起的线程恢复的方式有三种:
其余线程调用了 unpark 方法,参数为被挂起的线程
其余线程中断了被挂起的线程
The call spuriously (that is, for no reason) returns.
这里讲的是虚假唤醒,能够参考如下几篇资料:
因为虚假唤醒的存在,在调用 park 时通常采用自旋的方式,伪代码以下:
while (!canProceed()) { ... LockSupport.park(this); } 复制代码
而有限期挂起的除了上面三种以外,还有第四种方式:
This class associates, with each thread that uses it, a permit.
复制代码
Java 文档里说到了,每一个线程都关联一个许可(permit)。
当许可可用时,调用 park 会当即返回,不然可能(虚假唤醒则不会被挂起)被挂起;若是许可不可用时,调用 unpark 会使得许可变成可用,而若是许可自己是可用时,调用 unpark 不会有任何影响。
可能直接看文字不是那么的一目了然,咱们来看几个例子:
public static void exampleOne() {
Thread thread = new Thread(() -> {
while (flag) {
}
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park();
System.out.println("after second park");
});
thread.start();
flag = false;
sleep(20);
System.out.println("before unpark.");
LockSupport.unpark(thread);
}
复制代码
输出结果
before first park
before unpark
after first park
复制代码
分析
首先,许可初始是不可用的;因此在调用 park 后 thread 被挂起,后续主线程调用了 unpark 方法唤醒了被挂起的 thread,输出 after first park
,紧接着 thread 调用 park 继续被挂起。
private static void exampleTwo() {
Thread thread = new Thread(() -> {
while (flag) {
}
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park();
System.out.println("after second park");
});
thread.start();
LockSupport.unpark(thread);
LockSupport.unpark(thread);
flag = false;
}
复制代码
输出结果
before first park
after first park
复制代码
分析
主线程先对 thread 执行两次 unpark 操做,而后 thread 再连续调用两次 park 方法,结果发现,第二个 park 会挂起 thread;这里主要体现了 unpark 效果不会被累积,当许可可用时,调用 unpark 方法不会产生任何效果。
private static void exampleThree() {
Thread thread = new Thread(() -> {
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park();
System.out.println("after second park");
System.out.println("isInterrupted is " + Thread.interrupted());
System.out.println("isInterrupted is " + Thread.interrupted());
LockSupport.park();
System.out.println("after third park");
});
thread.start();
sleep(200);
thread.interrupt();
}
复制代码
输出结果
before first park
after first park
after second park
isInterrupted is true
isInterrupted is false
复制代码
分析
thread 前后共调用了三次 park,前两次调用没啥区别,在第三次调用以前调用了两次 Thread.interrupted();从输出结果来看,发现前两次 park 并无生效,只有第三次 park 将线程挂起了,Why?
咱们先来看 Thread.interrupted() 的做用:**判断当前线程的中断状态,同时将中断状态清除。**实际上这里只须要调用一次 Thread.interrupted() 便可,调用了两次是为了查看线程中断状态的变化。
当线程的中断状态为 true 时,park 失去了效果,不会挂起线程;而当调用了 Thread.interrupted() 将中断状态清除以后,park 又恢复了效果。
因此这里能够得出的结论:线程中断会使 park 失效。
重头戏来啦!上面讲到了 park 和 unpark 的几个方法,其实它们最终对应于 UNSAFE.park(boolean isAbsolute, long time) 和 UNSAFE.unpark(Object thread) 这两个 native 方法。
下面咱们就去看看这两个 native 方法是如何实现的。
unsafe.cpp#Unsafe_Park
:openjdk/hotspot/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))
UnsafeWrapper("Unsafe_Park");
EventThreadPark event;
#ifndef USDT2
HS_DTRACE_PROBE3(hotspot, thread__park__begin, thread->parker(), (int) isAbsolute, time);
#else /* USDT2 */
HOTSPOT_THREAD_PARK_BEGIN(
(uintptr_t) thread->parker(), (int) isAbsolute, time);
#endif /* USDT2 */
JavaThreadParkedState jtps(thread, time != 0);
thread->parker()->park(isAbsolute != 0, time);
#ifndef USDT2
HS_DTRACE_PROBE1(hotspot, thread__park__end, thread->parker());
#else /* USDT2 */
HOTSPOT_THREAD_PARK_END(
(uintptr_t) thread->parker());
#endif /* USDT2 */
...
UNSAFE_END
复制代码
这里有几个分支判断,不过能够看出不管是哪一个分支,最终都会走 park(bool isAbsolute, jlong time)
这个方法。
os_linux.cpp#Parker::park
:openjdk/hotspot/src/os/linux/vm/os_linux.cpp
void Parker::park(bool isAbsolute, jlong time) {
...
if (Atomic::xchg(0, &_counter) > 0) return;
Thread* thread = Thread::current();
assert(thread->is_Java_thread(), "Must be JavaThread");
JavaThread *jt = (JavaThread *)thread;
...
if (Thread::is_interrupted(thread, false)) {
return;
}
// Next, demultiplex/decode time arguments
timespec absTime;
if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
return;
}
if (time > 0) {
unpackTime(&absTime, isAbsolute, time);
}
...
if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
return;
}
int status ;
if (_counter > 0) { // no wait needed
_counter = 0;
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
// Paranoia to ensure our locked and lock-free paths interact
// correctly with each other and Java-level accesses.
OrderAccess::fence();
return;
}
assert(_cur_index == -1, "invariant");
if (time == 0) {
_cur_index = REL_INDEX; // arbitrary choice when not timed
status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
} else {
_cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
if (status != 0 && WorkAroundNPTLTimedWaitHang) {
pthread_cond_destroy (&_cond[_cur_index]) ;
pthread_cond_init (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr());
}
}
...
_counter = 0 ;
status = pthread_mutex_unlock(_mutex) ;
OrderAccess::fence();
// If externally suspended while waiting, re-suspend
if (jt->handle_special_suspend_equivalent_condition()) {
jt->java_suspend_self();
}
}
复制代码
首先经过 Atomic::xchg(0, &_counter) 方法将 _counter 置 0,若是原来的 _counter > 0,说明原来的许但是可用的,直接返回;
若是当前线程的处于中断状态,直接返回;
Thread::is_interrupted(thread, false) 只会判断线程的中断状态,不会重置其中断状态;Thread.interrupted() 调用的是 Thread::is_interrupted(thread, true),判断线程的中断状态同时将其重置。
关于 pthread_cond_wait 能够看一下这篇文章: pthread_cond_wait 详解
感受和 Object.wait、Object.notify、Object.notifyAll 的机制很相似。
unsafe.cpp#Unsafe_Unpark
:openjdk/hotspot/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(void, Unsafe_Unpark(JNIEnv *env, jobject unsafe, jobject jthread))
UnsafeWrapper("Unsafe_Unpark");
Parker* p = NULL;
if (jthread != NULL) {
oop java_thread = JNIHandles::resolve_non_null(jthread);
if (java_thread != NULL) {
jlong lp = java_lang_Thread::park_event(java_thread);
if (lp != 0) {
// This cast is OK even though the jlong might have been read
// non-atomically on 32bit systems, since there, one word will
// always be zero anyway and the value set is always the same
p = (Parker*)addr_from_java(lp);
} else {
// Grab lock if apparently null or using older version of library
MutexLocker mu(Threads_lock);
java_thread = JNIHandles::resolve_non_null(jthread);
if (java_thread != NULL) {
JavaThread* thr = java_lang_Thread::thread(java_thread);
if (thr != NULL) {
p = thr->parker();
if (p != NULL) { // Bind to Java thread for next time.
java_lang_Thread::set_park_event(java_thread, addr_to_java(p));
}
}
}
}
}
}
if (p != NULL) {
#ifndef USDT2
HS_DTRACE_PROBE1(hotspot, thread__unpark, p);
#else /* USDT2 */
HOTSPOT_THREAD_UNPARK(
(uintptr_t) p);
#endif /* USDT2 */
p->unpark();
}
UNSAFE_END
复制代码
前面一大段代码是在给 Parker* p 赋值,最终调用的是 p 的 unpark 方法:p->unpark()。
os_linux.cpp#Parker::unpark
:openjdk/hotspot/src/os/linux/vm/os_linux.cpp
void Parker::unpark() {
int s, status ;
status = pthread_mutex_lock(_mutex);
assert (status == 0, "invariant") ;
s = _counter;
_counter = 1;
if (s < 1) {
// thread might be parked
if (_cur_index != -1) {
// thread is definitely parked
if (WorkAroundNPTLTimedWaitHang) {
status = pthread_cond_signal (&_cond[_cur_index]);
assert (status == 0, "invariant");
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant");
} else {
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant");
status = pthread_cond_signal (&_cond[_cur_index]);
assert (status == 0, "invariant");
}
} else {
pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
}
} else {
pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
}
}
复制代码
unpark 方法其实很简单,首先经过 pthread_mutex_lock 获取锁,而后将 _counter 置为 1,再判断当前是否有线程被挂起,若是有,则经过 pthread_cond_signal 唤醒被挂起的线程,而后释放锁。
学习知识真是一环扣一环,学一个不会的知识点,很容易碰到新的不会的知识点,而后不断的接触新知识点,不断地学习新的内容;当你不断的学习,不断的把新知识点吃透,慢慢的又会发现其实不少知识点的思想又是相通的,学起来反而没那么费劲了。
就比如看到 pthread_cond_wait 的机制时,就会联想到 Object.wait,由于二者的实现有不少相像的地方,理解起来也就比较简单。
最后留一个问题,你们能够思考一下:LockSupport.park 和 Object.wait 二者有何区别?
参考资料: