本文共 2900 字,大约阅读时间需要 9 分钟。
简述:在前面几篇分析java并发工具的博客当中,基于AQS实现的,底层都会用到LockSupport所提供的park和unpark(Thread)方法来进行线程的挂起和唤醒,所以这篇文章我准备来看下LockSupport到底是如何实现线程挂起和唤醒的,以及它有哪些比较好的功能,可以在以后开发当中使用。
我们知道在java当中我们可以使用Object类当中的wait方法和notify方法来实现线程的阻塞和唤醒,但是有一点,Object类中提供的wait和notify必须是在synchronized关键字加锁之后,才能使用。但是我们在使用AQS时候,并不使用synchronized关键字。LockSupport当中提供的park和unpark方法对线程的操作就可以满足。
除此之外我们来看一个例子
public class NotifyTest { public static void main(String[] args)throws Exception { final Object obj = new Object(); Thread A = new Thread(new Runnable() { @Override public void run() { int sum = 0; for(int i=0;i<10;i++){ sum+=i; } try { synchronized (obj){ obj.wait(); } }catch (Exception e){ e.printStackTrace(); } System.out.println(sum); } }); A.start(); //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法 //Thread.sleep(1000); synchronized (obj){ obj.notify(); } }}
上述代码在我本地执行过几次,发现有时候可以打印出来计算结果,有时候程序一直不能结束,处于死循环当中,这是为什么呢,原因在线程启动之后调用了wait方法,main线程调用了notify方法,有种可能就是main线程调用notify方法先与线程调用wait方法,这个时候程序就一直无法执行。当我们把上述obj.wait方法和obj.notify方法替换成Lock.park和Lock.unpar(A)程序无论如何都可以正常执行成功。
综上所述,Object类当中线程挂起和唤醒和LockSupport中线程挂起和唤醒的不同点在于,Object类当中必须要在同步代码块里,而LockSupport则不需要,还有一点,Object当中如果先执行了notify在执行wait,那么线程将永远处于挂起不会被唤醒状态,LockSupport则不会。
接下来是关于源码分析
LockSupport中使用了大量Unsafe类来实现,我们先来看关于常用的几个方法
public class LockSupport {//挂起当前线程public static void park(){}// 挂起当前线程一定时间,超过某个时间后唤醒public static void parkNanos(long nanos){}// 挂起当前线程,直到deadline 之后唤醒 从1970年开始到deadline的毫秒数public static void parkUntil(long deadline){}// 挂起当前线程,阻塞对象public static void park(Object blocker){}// 挂起当前线程,在某个阻塞对象上,最长不超过nanos纳秒public static void parkNanos(Object blocker, long nanos) {}// 挂起当前线程在某个阻塞对象上,直到deadline时间public static void parkUntil(Object blocker, long deadline){}//唤醒挂起的线程public static void unpark(Thread thread){}}
其实以上挂起的方法主要是挂起指定时间,还有一个就是在某个对象上挂起指定时间,我们来看下具体的实现
public static void park() { UNSAFE.park(false, 0L); }
对于park方法,直接挂起,等到unpark唤醒
对于park在某个对象上的实现
public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); }
1、获取当前线程
2、设置线程的blocker对象(使用Unsafe的方式修改thread对象当中parkBlocker的值)
3、挂起线程
4、将当前对象的blocker对象清空
为什么要设置两次blocker?
在执行完Unsafe.park方法后,线程被挂起,不会往下面执行,这时候要等待Unsafe.unpark方法唤醒,然后再继续执行,如果unsafe.unpark方法执行后,该线程又执行了park方法,并且没有设置parkBlocer的值,我们拿到的就是之前park时,设置过得值,显然是不对的。
blocker的作用是什么?
其实在最早的时候,LockSupport提供的park方法,只有park(long) park(deadline) 和park,是没有设置blocker的方法的,主要是为了调试,在dump查看阻塞线程信息的时候,是没有的,之前我们使用synchronized阻塞在一个对象上的时候,是可以看到线程的阻塞对象,方便定位问题,LockSupport之前不提供blocker时,并不知道阻塞在哪个对象上了,因此jdk1.6提供了parkObject的方法。
以上就是关于LockSupport的介绍,如有问题,欢迎指正~
转载地址:http://ylvti.baihongyu.com/