LockSupport是一个编程工具类,主要是为了阻塞和唤醒线程用的。使用它咱们能够实现不少功能,今天主要就是对这个工具类的讲解,但愿对你有帮助:java
1、LockSupport简介web
刚刚开头提到过,LockSupport是一个线程工具类,全部的方法都是静态方法,可让线程在任意位置阻塞,也能够在任意位置唤醒。编程
它的内部其实两类主要的方法:park(停车阻塞线程)和unpark(启动唤醒线程)。微信
//(1)阻塞当前线程
public static void park(Object blocker);
//(2)暂停当前线程,有超时时间
public static void parkNanos(Object blocker, long nanos);
//(3)暂停当前线程,直到某个时间
public static void parkUntil(Object blocker, long deadline);
//(4)无期限暂停当前线程
public static void park();
//(5)暂停当前线程,不过有超时时间的限制
public static void parkNanos(long nanos);
//(6)暂停当前线程,直到某个时间
public static void parkUntil(long deadline);
//(7)恢复当前线程
public static void unpark(Thread thread);
public static Object getBlocker(Thread t);
注意上面的123方法,都有一个blocker,这个blocker是用来记录线程被阻塞时被谁阻塞的。用于线程监控和分析工具来定位缘由的。app
如今咱们知道了LockSupport是用来阻塞和唤醒线程的,并且以前相信咱们都知道wait/notify也是用来阻塞和唤醒线程的,那和它相比,LockSupport有什么优势呢?less
这里假设你已经了解了wait/notify的机制,若是不了解,能够在网上一搜,很简单。相信你既然学到了这个LockSupport,相信你已经提早已经学了wait/notify。编辑器
咱们先来举一个使用案例:ide
public class LockSupportTest {
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println(getName() + " 进入线程");
LockSupport.park();
System.out.println("t1线程运行结束");
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
System.out.println("t1已经启动,可是在内部进行了park");
LockSupport.unpark(t1);
System.out.println("LockSupport进行了unpark");
}
}
上面这段代码的意思是,咱们定义一个线程,可是在内部进行了park,所以须要unpark才能唤醒继续执行,不过上面,咱们在MyThread进行的park,在main线程进行的unpark。函数
这样来看,好像和wait/notify没有什么区别。那他的区别究竟是什么呢?这个就须要仔细的观察了。这里主要有两点:工具
(1)wait和notify都是Object中的方法,在调用这两个方法前必须先得到锁对象,可是park
不须要获取某个对象的锁就能够锁住线程。
(2)notify只能随机选择一个线程唤醒,没法唤醒指定的线程,unpark却能够唤醒一个指定的线程。
区别就是这俩,仍是主要从park和unpark的角度来解释的。既然这个LockSupport这么强,咱们就深刻一下他的源码看看。
2、源码分析(基于jdk1.8)
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
blocker是用来记录线程被阻塞时被谁阻塞的。用于线程监控和分析工具来定位缘由的。setBlocker(t, blocker)方法的做用是记录t线程是被broker阻塞的。所以咱们只关注最核心的方法,也就是UNSAFE.park(false, 0L)。
UNSAFE是一个很是强大的类,他的的操做是基于底层的,也就是能够直接操做内存,所以咱们从JVM的角度来分析一下:
每一个java线程都有一个Parker实例:
class Parker : public os::PlatformParker {
private:
volatile int _counter ;
...
public:
void park(bool isAbsolute, jlong time);
void unpark();
...
}
class PlatformParker : public CHeapObj<mtInternal> {
protected:
pthread_mutex_t _mutex [1] ;
pthread_cond_t _cond [1] ;
...
}
咱们换一种角度来理解一下park和unpark,能够想一下,unpark其实就至关于一个许可,告诉特定线程你能够停车,特定线程想要park停车的时候一看到有许可,就能够立马停车继续运行了。所以其执行顺序能够颠倒。
如今有了这个概念,咱们体会一下上面JVM层面park的方法,这里面counter字段,就是用来记录所谓的“许可”的。
本小部分总结来源于:https://www.jianshu.com/p/1f16b838ccd8
当调用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;
若是不成功,则构造一个ThreadBlockInVM,而后检查_counter是否是>0,若是是,则把_counter设置为0,unlock mutex并返回:
ThreadBlockInVM tbivm(jt);
// no wait needed
if (_counter > 0) {
_counter = 0;
status = pthread_mutex_unlock(_mutex);
不然,再判断等待的时间,而后再调用pthread_cond_wait函数等待,若是等待返回,则把_counter设置为0,unlock mutex并返回:
if (time == 0) {
status = pthread_cond_wait (_cond, _mutex) ;
}
_counter = 0 ;
status = pthread_mutex_unlock(_mutex) ;
assert_status(status == 0, status, "invariant") ;
OrderAccess::fence();
这就是整个park的过程,总结来讲就是消耗“许可”的过程。
仍是先来看一下JDK源码:
/**
* Makes available the permit for the given thread, if it
* was not already available. If the thread was blocked on
* {@code park} then it will unblock. Otherwise, its next call
* to {@code park} is guaranteed not to block. This operation
* is not guaranteed to have any effect at all if the given
* thread has not been started.
*
* @param thread the thread to unpark, or {@code null}, in which case
* this operation has no effect
*/
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
上面注释的意思是给线程生产许可证。
当unpark时,则简单多了,直接设置_counter
为1,再unlock mutext返回。若是_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") ;
}
}
ok,如今咱们已经对源码进行了分析,整个过程其实就是生产许可和消费许可的过程。并且这个生产过程能够反过来。也就是先生产再消费。下面咱们使用几个例子验证一波。
3、LockSupport使用
public class LockSupportTest {
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println(getName() + " 进入线程");
LockSupport.park();
System.out.println(" 运行结束");
System.out.println("是否中断:" + Thread.currentThread().isInterrupted());
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
System.out.println("t1线程已经启动了,可是在内部LockSupport进行了park");
t1.interrupt();
System.out.println("main线程结束");
}
}
咱们看一下结果:

public static class MyThread extends Thread {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + " 进入线程");
LockSupport.park();
System.out.println(" 运行结束");
}
}
咱们只需在park以前先休眠1秒钟,这样能够确保unpark先执行。
OK,今天的文章先写到这,若有问题,还请批评指正。
本文分享自微信公众号 - 愚公要移山(fdd_sxu_nwpu)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。