1、简单 html
在没有LockSupport以前,线程的挂起和唤醒我们都是经过Object的wait和notify/notifyAll方法实现。java
写一段例子代码,线程A执行一段业务逻辑后调用wait阻塞住本身。主线程调用notify方法唤醒线程A,线程A而后打印本身执行的结果。面试
public class TestObjWait { 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 { obj.wait(); }catch (Exception e){ e.printStackTrace(); } System.out.println(sum); } }); A.start(); //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法 Thread.sleep(1000); obj.notify(); } }
执行这段代码,不难发现这个错误:ide
Exception in thread "main" java.lang.IllegalMonitorStateException at java.lang.Object.notify(Native Method)
缘由很简单,wait和notify/notifyAll方法只能在同步代码块里用(这个有的面试官也会考察)。因此将代码修改成以下就可正常运行了:函数
public class TestObjWait { 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(); } } }
那若是换成LockSupport呢?简单得很,看代码:工具
public class TestObjWait { public static void main(String[] args)throws Exception { Thread A = new Thread(new Runnable() { @Override public void run() { int sum = 0; for(int i=0;i<10;i++){ sum+=i; } LockSupport.park(); System.out.println(sum); } }); A.start(); //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法 Thread.sleep(1000); LockSupport.unpark(A); } }
直接调用就能够了,没有说非得在同步代码块里才能用。简单吧。
2、灵活线程
若是只是LockSupport在使用起来比Object的wait/notify简单,那还真不必专门讲解下LockSupport。最主要的是灵活性。htm
上边的例子代码中,主线程调用了Thread.sleep(1000)方法来等待线程A计算完成进入wait状态。若是去掉Thread.sleep()调用,代码以下:对象
public class TestObjWait { 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(); } } }
多运行几回上边的代码,有的时候可以正常打印结果并退出程序,但有的时候线程没法打印结果阻塞住了。缘由就在于:主线程调用完notify后,线程A才进入wait方法,致使线程A一直阻塞住。因为线程A不是后台线程,因此整个程序没法退出。blog
那若是换作LockSupport呢?LockSupport就支持主线程先调用unpark后,线程A再调用park而不被阻塞吗?是的,没错。代码以下:
public class TestObjWait { 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; } LockSupport.park(); System.out.println(sum); } }); A.start(); //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法 //Thread.sleep(1000); LockSupport.unpark(A); } }
无论你执行多少次,这段代码都能正常打印结果并退出。这就是LockSupport最大的灵活所在。
总结一下,LockSupport比Object的wait/notify有两大优点:
①LockSupport不须要在同步代码块里 。因此线程间也不须要维护一个共享的同步对象了,实现了线程间的解耦。
②unpark函数能够先于park调用,因此不须要担忧线程间的执行的前后顺序。
3、应用普遍
LockSupport在Java的工具类用应用很普遍,我们这里找几个例子感觉感觉。以Java里最经常使用的类ThreadPoolExecutor为例。先看以下代码:
public class TestObjWait { public static void main(String[] args)throws Exception { ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(1000); ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,5,1000, TimeUnit.SECONDS,queue); Future<String> future = poolExecutor.submit(new Callable<String>() { @Override public String call() throws Exception { TimeUnit.SECONDS.sleep(5); return "hello"; } }); String result = future.get(); System.out.println(result); } }
代码中咱们向线程池中扔了一个任务,而后调用Future的get方法,同步阻塞等待线程池的执行结果。
这里就要问了:get方法是如何组塞住当前线程?线程池执行完任务后又是如何唤醒线程的呢?
我们跟着源码一步步分析,先看线程池的submit方法的实现:
在submit方法里,线程池将咱们提交的基于Callable实现的任务,封装为基于RunnableFuture实现的任务,而后将任务提交到线程池执行,并向当前线程返回RunnableFutrue。
进入newTaskFor方法,就一句话:return new FutureTask<T>(callable);
因此,我们主线程调用future的get方法就是FutureTask的get方法,线程池执行的任务对象也是FutureTask的实例。
接下来看看FutureTask的get方法的实现:
比较简单,就是判断下当前任务是否执行完毕,若是执行完毕直接返回任务结果,不然进入awaitDone方法阻塞等待。
awaitDone方法里,首先会用到上节讲到的cas操做,将线程封装为WaitNode,保持下来,以供后续唤醒线程时用。再就是调用了LockSupport的park/parkNanos组塞住当前线程。
上边已经说完了阻塞等待任务结果的逻辑,接下来再看看线程池执行完任务,唤醒等待线程的逻辑实现。
前边说了,我们提交的基于Callable实现的任务,已经被封装为FutureTask任务提交给了线程池执行,任务的执行就是FutureTask的run方法执行。以下是FutureTask的run方法:
c.call()就是执行咱们提交的任务,任务执行完后调用了set方法,进入set方法发现set方法调用了finishCompletion方法,想必唤醒线程的工做就在这里边了,看看代码实现吧:
没错就在这里边,先是经过cas操做将全部等待的线程拿出来,而后便使用LockSupport的unpark唤醒每一个线程。
在使用线程池的过程当中,不知道你有没有这么一个疑问:线程池里没有任务时,线程池里的线程在干吗呢?
看过个人这篇文章《线程池的工做原理与源码解读》的读者必定知道,线程会调用队列的take方法阻塞等待新任务。那队列的take方法是否是也跟Future的get方法实现同样呢?我们来看看源码实现。
以ArrayBlockingQueue为例,take方法实现以下:
与想象的有点出入,他是使用了Lock的Condition的await方法实现线程阻塞。但当咱们继续追下去进入await方法,发现仍是使用了LockSupport: