微信公众号:MyClass社区
若有问题或建议,请公众号留言java
LockSupport介绍
在阅读开源项目的的时候,常常看到有人使用LockSupport,咱们今天来看一下它的使用和基本原理。首先,简单的介绍一下LockSupport,它是并发包中的一个线程阻塞工具类,LockSupport提供park()和unpark()两个方法实现阻塞线程和解除线程阻塞。下面看看它与wait和notify使用上有何区别?微信
LockSupport的使用
咱们知道Object类也能够实现阻塞和唤醒,wait和notify只能在同步代码块或者同步方法中调用,这里是为了保证多线程object操做wait和notify的原子性。多线程
public void waitAndNotify() throws Exception {
final Object obj = new Object();
Thread testThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("do task");
System.out.println("wait---");
synchronized (obj){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("continue do task");
}
});
testThread.start();
//这里sleep是为了保证已经阻塞
Thread.sleep(1000);
synchronized (obj){
System.out.println("notify--------");
obj.notify();
}
}
并发
下面是LockSupport的使用,LockSupport.park()阻塞线程,而后调用unpark进行唤醒,不须要像wait/notify那样先要保证进入wait,才能唤醒线程,不然会无限等待。LockSupport若是先调用unpark后,线程再调用park是不会被一直阻塞的,使用起来更加简单灵活。jvm
public void lockSupportTest() {
final Object obj = new Object();
Thread testThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("do task");
System.out.println("wait---");
LockSupport.park();
System.out.println("continue do task");
}
});
testThread.start();
//这里sleep是为了保证park成功
Thread.sleep(1000);
System.out.println("notify--------");
LockSupport.unpark(testThread);
}
ide
以上的执行效果:函数
do task
wait---
notify--------
continue do task
工具
1.LockSupport不须要在同步代码块里 。因此线程间也不须要维护一个共享的同步对象了,实现了线程间的解耦。
2.notify和notifyAll,通常的若是多个线程都对同一个对象进行阻塞,notify()是不肯定唤醒哪个线程的等待。
3.unpark函数能够先于park调用,因此不须要担忧线程间的执行的前后顺序。
4.wait/notify和notifyAll和LockSupport阻塞的线程是相互隔离的,object阻塞的是对象维度,不一样的线程能够对同一个对象进行操做,而LockSupport是线程维度的阻塞,因此不会交叉影响,你们能够本身试一试。spa
LockSupport实现原理
wait方法实现
首先说一下wait,它是在同步机制中的一个操做,须要放弃同步对象锁的monitor,monitor信息保存在对象头信息中,以前讲过synchronized锁信息也是保存在对象头信息(Mark Word)中,当线程调用wait的时候,将线程放入该对象锁ObjectMonitor的等待唤醒集合wait-set中来实现将线程挂起。.net
notify方法实现
lock.notify()方法底层也是经过ObjectMonitor的void notify()实现,获取对象的信息的WaitSet列表中的第一个节点。这里须要注意的是,在jdk的notify方法注释是随机唤醒一个线程,实际上是第一个ObjectWaiter节点
LockSuppor实现
/**
* 若是给定线程的许可尚不可用,则使其可用。
* 若是线程调用了park,调用unpark将解除其阻塞状态。
* 若是没有park,保证下一次调用 park也不会受阻塞。
* 启动了线程,调用才会有效果,否哦无效。
* @param thread: 要执行 unpark 操做的线程;该参数为 null 表示此操做没有任何效果。
*/
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
/**
* 为了线程调度,在许可可用以前阻塞当前线程。
* 若是许可可用,则使用该许可,而且该调用当即返回;
* 不然,为线程调度禁用当前线程,并在发生如下三种状况之一之前,使其处于休眠状态:
* 1. 其余某个线程将当前线程做为目标调用 unpark
* 2. 其余某个线程中断当前线程
* 3. 该调用不合逻辑地(即毫无理由地)返回
*/
public static void park() {
UNSAFE.park(false, 0L);
}
LockSuppor的park/unpark方法实际上是操做Unsafe类里的函数。
//参数线程
public native void unpark(Thread jthread);
//isAbsolute参数是指明时间是绝对的,仍是相对的。
public native void park(boolean isAbsolute, long time);
经过openjdk的源码看看其native实现,HotSpot里park/unpark的实现,每一个java线程都有一个Parker实例,Parker类是这样定义的,在Parker类里的_counter字段,就是用来记录线程是否阻塞状态的,网上不少人称之为所谓的“许可”状态,具体逻辑就不扩展了,有机会能够深刻了解一下底层jvm的实现原理。
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] ;
...
总结
LockSuppor真正解耦了线程之间的同步,线程之间再也不须要一个Object或者其它变量来存储状态;
当调用park时,先尝试直接可否拿到“许可”,即_counter>0时,若是成功,则把_counter设置为0,并返回;
当unpark时,直接设置_counter为1,而且返回。若是_counter以前的值是0,则还要唤醒在park中等待的线程;
简单的理解就是维护_counter的变量,当park时,这个变量置为了0,表明当前线程阻塞了,等待许可,当unpark时,这个变量置为1,则线程获取许可表明唤醒继续执行;
屡次调用unpark方法和调用一次unpark方法效果同样,由于都是直接将_counter赋值为1,而不是加1。
本文分享自微信公众号 - MyClass社区(MyClass_ZZ)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。