概述
是什么?
查看官网文档可以得知,LockSupport是用于创建锁或其他同步器基本的线程阻塞原语。换句话说,它是AQS等其他锁或同步器实现线程阻塞和唤醒的底层基础。
方法概述
LockSupport中提供了几个基本方法如下:
park(): 除非有许可,当前线程阻塞,禁止被调度;
当发生以下三种情况时,将立即释放:
- 其他线程以此线程为参数调用unpark(Thread)方法;
- 其他线程将此线程打断;
- 发生了不可预料的事情
park(Object):阻塞当前线程,释放条件与park()方法一样。只是增加Object参数,用于保存阻塞线程的对象,推荐使用;
parkNanos(Long):阻塞当前线程,释放条件与park()方法一样,只是添加了过期时间,单位为纳秒,超过时间则自动释放;
parkNanos(Object,Long): 与parkNanos()方法一样,新增object参数,用于记录阻塞当前线程的对象;
parkUntil(Long):阻塞当前线程,但是设置到期时间,过时间点自动释放,线程继续执行,单位为毫秒;
parkUntil(Object,Long):与parkUntil(Long)方法功能一样,新增Object参数,记录阻塞当前线程的对象;
unpark(Thread): 手动释放,线程继续执行;
使用实例
实例一
public class T05_LockSupport {
Object o=new Object();
public void m1(){
System.out.println("Hello,this is thread_1");
LockSupport.park(o);
System.out.println("Bye....");
}
public static void main(String[] args) {
T05_LockSupport s=new T05_LockSupport();
Thread t1=new Thread(()->{
s.m1();
},"thread-1");
t1.start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(t1);
}
}
当前线程阻塞,可由其他线程调用unpark释放,当前线程继续执行;
实例二
public class T05_LockSupport {
Object o=new Object();
public void m1(){
System.out.println("Hello,this is thread_1");
try {
for(int i=5;i>=0;i--){
System.out.println(i);
TimeUnit.SECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("park");
LockSupport.park(o);
System.out.println("Bye....");
}
public static void main(String[] args) {
T05_LockSupport s=new T05_LockSupport();
Thread t1=new Thread(()->{
s.m1();
},"thread-1");
t1.start();
System.out.println("unpark");
LockSupport.unpark(t1);
}
}
输出结果
示例二中,thread_1在调用park前等待了一段时间啊,且在此之前main线程已经调用unpark方法,但是结果显示正常,即thread_1能够正常向下执行。立即推:unpark和park的执行顺序并不重要,即不分先后顺序,只要调用的unpark方法,park就会失效。
实例三
public void m4(){
Thread current=Thread.currentThread();
LockSupport.unpark(current);
LockSupport.unpark(current);
System.out.println("1");
LockSupport.park(o);
System.out.println(2);
LockSupport.park(o);
System.out.println("end");
}
public static void main(String[] args) {
T05_LockSupport s=new T05_LockSupport();
s.m4();
}
输出结果:
在示例三中,先连续调用了两次unpark,再调用两次park,结果显示,在第二次park时,线程阻塞。也就是说,unpark方法并不会叠加,即使使用两次unpark,也只能对一个park发生作用。再一个表明,一个线程连续调用两次park,线程会一直阻塞下去,除非有另一个线程调用unpark获取其他原因。
示例四
public void m1(){
System.out.println("Hello,this is thread_1");
System.out.println("park");
LockSupport.park(o);
System.out.println("Bye....");
}
public static void main(String[] args) {
T05_LockSupport s=new T05_LockSupport();
Thread t1=new Thread(()->{
s.m1();
},"thread-1");
t1.start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
}
输出结果:
实例四中,并没有调用unpark方法,只是在main中调用interrupt,将线程中断,线程结束等待,继续向下执行,即park能对中断信号做出响应,且不抛出异常。
通过以上实例,可以总结出一下几点:
- park和unpark调用顺序不分先后;
- 多次连续调用unpark算作一次;
- park不可重入,多次调用park会造成死锁;
实现原理
查看源码,LockSupport常用方法源码如下:
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
观察源码可知,LockSupport归根究底其实是调用的unsafe中的park()和unpark()方法。 方法如下:
public native void unpark(Object var1);
//var1:是否是绝对时间(绝对时间,相对时间)
//var2:阻塞时间
public native void park(boolean var1, long var2);
每个线程会有一个parker对象,而Parker结构如下:
class Parker : public os::PlatformParker {
private:
volatile int _counter ;
Parker * FreeNext ;
JavaThread * AssociatedWith ; // Current association
public:
Parker() : PlatformParker() {
_counter = 0 ;
FreeNext = NULL ;
AssociatedWith = NULL ;
}
protected:
~Parker() { ShouldNotReachHere(); }
public:
// For simplicity of interface with Java, all forms of park (indefinite,
// relative, and absolute) are multiplexed into one call.
void park(bool isAbsolute, jlong time);
void unpark();
// Lifecycle operators
static Parker * Allocate (JavaThread * t) ;
static void Release (Parker * e) ;
private:
static Parker * volatile FreeList ;
static volatile int ListLock ;
};
_counter表示我们说的“许可”:_counter=0,获取失败;_counter=1,获取成功;
查看Hotspot中源码,Linux平台park() 方法源码如下:
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;
Thread* thread = Thread::current();
assert(thread->is_Java_thread(), "Must be JavaThread");
JavaThread *jt = (JavaThread *)thread;
// Optional optimization -- avoid state transitions if there's an interrupt pending.
// Check interrupt before trying to wait
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);
}
// Enter safepoint region
// Beware of deadlocks such as 6317397.
// The per-thread Parker:: mutex is a classic leaf-lock.
// In particular a thread must never block on the Threads_lock while
// holding the Parker:: mutex. If safepoints are pending both the
// the ThreadBlockInVM() CTOR and DTOR may grab Threads_lock.
//阻塞当前线程
ThreadBlockInVM tbivm(jt);
// Don't wait if cannot get lock since interference arises from
// unblocking. Also. check interrupt before trying wait
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;
}
#ifdef ASSERT
// Don't catch signals while blocked; let the running threads have the signals.
// (This allows a debugger to break into the running thread.)
sigset_t oldsigs;
sigset_t* allowdebug_blocked = os::Linux::allowdebug_blocked_signals();
pthread_sigmask(SIG_BLOCK, allowdebug_blocked, &oldsigs);
#endif
OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
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
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());
}
}
_cur_index = -1;
assert_status(status == 0 || status == EINTR ||
status == ETIME || status == ETIMEDOUT,
status, "cond_timedwait");
#ifdef ASSERT
pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
#endif
_counter = 0 ;
status = pthread_mutex_unlock(_mutex) ;
assert_status(status == 0, status, "invariant") ;
// Paranoia to ensure our locked and lock-free paths interact
// correctly with each other and Java-level accesses.
OrderAccess::fence();
// If externally suspended while waiting, re-suspend
if (jt->handle_special_suspend_equivalent_condition()) {
jt->java_suspend_self();
}
}
执行逻辑如下:
尝试能否拿到“许可”,即是_counter>0?若成功,则将_counter设置为0后直接返回;
if (Atomic::xchg(0, &_counter) > 0) return;
若不能获取许可,则构造ThreadBlockInVM,将线程改为阻塞状态;
ThreadBlockInVM tbivm(jt);
判断线程是否中断,若未中断,则给互斥量加锁,若加锁失败,则直接返回;
if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
return;
}
判断过期时间 ,将线程等待一段时间;
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());
}
}
将许可置为0,并释放互斥量;
_counter = 0 ;
status = pthread_mutex_unlock(_mutex) ;
其中ThreadBlockInVM 结构如下,
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);
//将线程由在vm中运行状态置为阻塞状态
trans_and_fence(_thread_in_vm, _thread_blocked);
}
~ThreadBlockInVM() {
trans_and_fence(_thread_blocked, _thread_in_vm);
// We don't need to clear_walkable because it will happen automagically when we return to java
}
};
_thread_in_vm和_thread_blocked 是定义线程状态的枚举值:
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 completness)
_thread_in_native = 4, // running in native code
_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, // running in Java or in stub code
_thread_in_Java_trans = 9, // corresponding transition state (not used, included for completness)
_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
};
unpark() 源码如下:
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") ;
}
}
执行逻辑很简单,只是把_counter置为1,并通知等待的线程结束等待。
总结
LockSupport的park和unpark底层依赖的是_counter来记录状态,只有0和1两种状态,当_counter==1时,可以继续执行,所以LockSupport中的park和unpark没有先后顺序,且不能被多次调用park,多次调用park都是_counter都是0,如果没有线程执行unpark,则会一直等待。而具体的线程阻塞,唤醒,等待依赖的的是不同操作系统的具体实现。