文章目录
背景
看condition源码的时候,park这里没有明白,到底是怎么唤醒的,是立即唤醒,还是park自旋自动唤醒?
这个还没查清楚,结果看有的博文,把我彻底搞晕了(就是我下面的坑
),所以想弄明白到底怎么回事
概览
本质是0/1之间的切换,这里的0/1就是permit,理解为许可,一个线程最多只有一个permit,park消耗1->0,unpark增加0->1
不会死锁
permit不会叠加,不要连续调用两次unpark,相当于调用了一次unpark,除非park之后permit被消耗为0,再去调用一次unpark才可以继续变为1才可以。
一般配置自旋使用,当然没有自旋也可以,因为在park哪里已经阻塞住了,通过unpark唤醒是也可以的。
while (!canProceed()) { … LockSupport.park(this); }}
看下踩坑的总结
踩坑
之前读过一篇博文其中有一句这样的话,搞了我一天,最后不得不弄懂park的原理,强迫症啊
而如果线程A处于等待许可状态,再次调用park,则会永远等待下去,调用unpark也无法唤醒。https://blog.csdn.net/anLA_/article/details/78635300
包括这篇文章
如果一个线程连续2次调用LockSupport.park(),那么该线程一定会一直阻塞下去。https://blog.csdn.net/aitangyong/article/details/38373137
https://www.cnblogs.com/set-cookie/p/9582547.html 在这个文章中有一个Demo,和这里说的意思大概差不多,我进行了稍微的改造:
package com.leesin.heightConcurrent.park;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @description:
* @author: Leesin.Dong
* @date: Created in 2020/3/17 19:08
* @version:
* @modified By:
*/
public class ParkDemo {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println("开始");
LockSupport.park();
System.out.println("第一次park");
// LockSupport.unpark(Thread.currentThread());
LockSupport.park();
System.out.println("第二次park");
}, "Thread1");
Thread thread2 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(thread1);
LockSupport.unpark(thread1);
}, "Thread2");
Thread thread3 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(thread1);
}, "Thread3");
thread1.start();
thread2.start();
// thread3.start();
}
}
如果关闭// thread3.start();,确实会出现
但是我打开// thread3.start();,就能解决问题了
并不是像上面说的那样
则会永远等待下去,调用unpark也无法唤醒。
总结
连续两次调用unpark,还是1,这个时候被第一个park消耗掉,第二个park处于阻塞状态,这样确实没有办法了。
但是可以再通过另一个线程再次unpark变成1,甚至在两次unpark之间让他sleep1s,也可以达到效果。
TimeUnit.SECONDS.sleep(1);
LockSupport.unpark(thread1);
TimeUnit.SECONDS.sleep(1);
LockSupport.unpark(thread1);
在第一个park和第二个park之间unpark一次即可
如果park() unpark(){thread2} unpark(){thread2} park() ,这样会在第二个park()处阻塞
park() unpark(){thread2} unpark(){thread3} park(),或者park() unpark(){thread2} unpark(){thread2 sleep n s} park()这样不会再第二个park处阻塞
再想想:
之前连续两次是因为中间有一个时间阈值,第一次unpark变为1之后还没来得及唤醒第一次park,即1还没来得及被消耗,又一次unpark,这次还是1,就相当于 unpark->unpark->park消耗掉。
如果第一次unpark之后,唤醒了park,permit被第一次park消耗掉变成0,这个时候,再次通过unpark就可以成为1,然后继续被第二次park消耗掉了,也就不会被阻塞住了。
所以关键是要找到这个时间阈值,所以这里的关键点不是thread3,而是sleep n s,这个n跟操作系统的调度时间有关。
关键点:只要unpark被消耗掉,就可以再次park了。
源码
park
void Parker::park(bool isAbsolute, jlong time) {
// 先原子的将_counter的值设为0,并返回_counter的原值,如果原值>0说明有通行证,直接返回
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;
// park方法的传参是isAbsolute = false, time = 0,所以会继续往下走
if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
return;
}
// 这里time为0,如果调用的是parkNanos或者parkUtil,这里time就会>0,
if (time > 0) {
// 如果time > 0,unpackTime计算absTime的时间
unpackTime(&absTime, isAbsolute, time);
}
ThreadBlockInVM tbivm(jt);
// 再次判断线程是否被中断,如果没有被中断,尝试获得互斥锁,如果获取失败,直接返回
if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
return;
}
int status ;
// 如果_counter > 0, 不需要等待,这里再次检查_counter的值
if (_counter > 0) { // no wait needed
_counter = 0;
status = pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
// 插入一个写内存屏障,保证可见性,具体实现见下方
OrderAccess::fence();
return;
}
// 省略assert代码
OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
// 设置JavaThread的_suspend_equivalent为true,表示线程被暂停
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()
assert(_cur_index == -1, "invariant");
if (time == 0) {
_cur_index = REL_INDEX; // arbitrary choice when not timed
// 让线程等待_cond[_cur_index]信号,到这里线程进入等待状态
status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
} else {
_cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
// 线程进入有超时时间的等待,内部实现调用了pthread_cond_timedwait系统调用
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());
}
}
_cur_index = -1;
// 省略assert代码
// _counter重新设置为0
_counter = 0 ;
// 释放互斥锁
status = pthread_mutex_unlock(_mutex) ;
assert_status(status == 0, status, "invariant") ;
// 插入写屏障
OrderAccess::fence();
// 省略额外检查代码
}
// OrderAccess::fence 在linux平台的实现
inline void OrderAccess::fence() {
// 如果是多核cpu
if (os::is_MP()) {
// always use locked addl since mfence is sometimes expensive
// 判断是否AMD64 CPU,汇编代码实现写屏障
#ifdef AMD64
__asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
__asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
}
}
ThreadBlockInVM tbivm(jt) (就是阻塞)
这属于C++新建变量的语法,也就是调用构造函数新建了一个变量,变量名为tbivm,参数为jt。类的实现为
class ThreadBlockInVM : public ThreadStateTransition {
public:
ThreadBlockInVM(JavaThread *thread)
: ThreadStateTransition(thread) {
// Once we are blocked vm expects stack to be walkable
thread->frame_anchor()->make_walkable(thread);
//把线程由运行状态转成阻塞状态
trans_and_fence(_thread_in_vm, _thread_blocked);
}
...
};
_thread_in_vm 表示线程当前在VM中执行,_thread_blocked表示线程当前阻塞了,他们globalDefinitions.hpp
中定义的枚举
//这个枚举是用来追踪线程在代码的那一块执行,用来给 safepoint code使用,有4种重要的类型,_thread_new/_thread_in_native/_thread_in_vm/_thread_in_Java。形如xxx_trans的状态都是中间状态,表示线程正在由一种状态变成另一种状态,这种方式使得 safepoint code在处理线程状态时,不需要对线程进行挂起,使得safe point code运行更快,而给定一个状态,通过+1就可以得到他的转换状态
enum JavaThreadState {
_thread_uninitialized = 0, // should never happen (missing initialization)
_thread_new = 2, // just starting up, i.e., in process of being initialized
_thread_new_trans = 3, // corresponding transition state (not used, included for completeness)
_thread_in_native = 4, // running in native code . This is a safepoint region, since all oops will be in jobject handles
_thread_in_native_trans = 5, // corresponding transition state
_thread_in_vm = 6, // running in VM
_thread_in_vm_trans = 7, // corresponding transition state
_thread_in_Java = 8, // Executing either interpreted or compiled Java code running in Java or in stub code
_thread_in_Java_trans = 9, // corresponding transition state (not used, included for completeness)
_thread_blocked = 10, // blocked in vm
_thread_blocked_trans = 11, // corresponding transition state
_thread_max_state = 12 // maximum thread state+1 - used for statistics allocation
};
父类ThreadStateTransition中定义trans_and_fence如下
void trans_and_fence(JavaThreadState from, JavaThreadState to) { transition_and_fence(_thread, from, to);} //_thread即构造函数传进来de thread
// transition_and_fence must be used on any thread state transition
// where there might not be a Java call stub on the stack, in
// particular on Windows where the Structured Exception Handler is
// set up in the call stub. os::write_memory_serialize_page() can
// fault and we can't recover from it on Windows without a SEH in
// place.
//transition_and_fence方法必须在任何线程状态转换的时候使用
static inline void transition_and_fence(JavaThread *thread, JavaThreadState from, JavaThreadState to) {
assert(thread->thread_state() == from, "coming from wrong thread state");
assert((from & 1) == 0 && (to & 1) == 0, "odd numbers are transitions states");
//标识线程转换中
thread->set_thread_state((JavaThreadState)(from + 1));
// 设置内存屏障,确保新的状态能够被VM 线程看到
if (os::is_MP()) {
if (UseMembar) {
// Force a fence between the write above and read below
OrderAccess::fence();
} else {
// Must use this rather than serialization page in particular on Windows
InterfaceSupport::serialize_memory(thread);
}
}
if (SafepointSynchronize::do_call_back()) {
SafepointSynchronize::block(thread);
}
//线程状态转换成最终的状态,对待这里的场景就是阻塞
thread->set_thread_state(to);
CHECK_UNHANDLED_OOPS_ONLY(thread->clear_unhandled_oops();)
}
https://juejin.im/post/5c041bae51882516eb562f15#heading-7
unpark
void Parker::unpark() {
int s, status ;
// 先进入_mutex的临界区,声明如下
// pthread_mutex_t _mutex [1] ;
// pthread_cond_t _cond [2] ;
status = pthread_mutex_lock(_mutex);
assert (status == 0, "invariant") ;
s = _counter;
// 将_counter置为1
_counter = 1;
// s记录的是unpark之前的_counter数,如果s < 1,说明有可能该线程在等待状态,需要唤醒。
if (s < 1) {
// thread might be parked
// _cur_index代表被使用cond的index
if (_cur_index != -1) {
// thread is definitely parked
// 根据虚拟机参数WorkAroundNPTLTimedWaitHang来做不同的处理,默认该参数是1
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");
// 后发信号唤醒线程,唤醒操作在互斥代码块外部,感觉这里可能会有风险,暂时还GET不到。。。
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") ;
}
}
https://juejin.im/post/5d72f75d5188255aed032abb#heading-5
简要
park 过程
当调用park时,先尝试能否直接拿到“许可”,即_counter>0时,如果成功,则把_counter设置为0,并返回:
void Parker::park(bool isAbsolute, jlong time) {
// Ideally we'd do something useful while spinning, such
// as calling unpackTime().
// Optional fast-path check:
// Return immediately if a permit is available.
// We depend on Atomic::xchg() having full barrier semantics
// since we are doing a lock-free update to _counter.
if (Atomic::xchg(0, &_counter) > 0) return;
如果有中断信号,就直接返回,且没有清除中断状态。
if (Thread::is_interrupted(thread, false)) {
// 线程执行了中断,返回
return;
}
如果不成功,则构造一个ThreadBlockInVM,然后检查_counter是不是>0,如果是,则把_counter设置为0,unlock mutex并返回:
ThreadBlockInVM tbivm(jt);
if (_counter > 0) { // no wait needed
_counter = 0;
status = pthread_mutex_unlock(_mutex);
否则,再判断等待的时间,然后再调用pthread_cond_wait函数等待,如果等待返回,则把_counter设置为0,unlock mutex并返回:
ps:这里可能顺序不对,应该在阻塞的上面
完整源码
https://juejin.im/post/5c041bae51882516eb562f15#heading-2
https://juejin.im/post/5d72f75d5188255aed032abb#heading-2
if (time == 0) {
status = pthread_cond_wait (_cond, _mutex) ;
}
_counter = 0 ;
status = pthread_mutex_unlock(_mutex) ;
assert_status(status == 0, status, "invariant") ;
OrderAccess::fence();
**
unpark 过程
当unpark时,则简单多了,直接设置_counter为1,再unlock mutex返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程:
void Parker::unpark() {
int s, status ;
status = pthread_mutex_lock(_mutex);
assert (status == 0, "invariant") ;
s = _counter;
_counter = 1;
if (s < 1) {
if (WorkAroundNPTLTimedWaitHang) {
status = pthread_cond_signal (_cond) ;
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) ;
assert (status == 0, "invariant") ;
}
} else {
pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
}
}
链接:https://www.jianshu.com/p/e3afe8ab8364
源码总结:
park
1:直接返回
interrupt:直接返回,不清除中断状态
时间到了:直接返回
否则: 阻塞
unpark
unpark之前的值
0:唤醒阻塞的线程
1:直接返回
unpark立即唤醒park中的阻塞
源码博文
https://juejin.im/post/5c041bae51882516eb562f15#heading-2(推荐)
https://juejin.im/post/5d72f75d5188255aed032abb#heading-2(推荐)
https://www.jianshu.com/p/e3afe8ab8364(简单)
只要记住
park
1:不阻塞
interrupt:不阻塞,不清除中断状态
否则: 阻塞
unpark
unpark之前的值
0:唤醒
1:无
unpark立即唤醒park中的阻塞
再简洁(最终):
park遇到0 阻塞 (park不能减1阻塞)
unpark之前的是0 唤醒(unpark可以加1唤醒)
park会被interrupt唤醒,且不清除中断状态
unpark不能连续+1,不能叠加,一次性的
unpark立即唤醒park中的阻塞