欢迎来到DIVCSS5查找CSS资料与学习DIV CSS布局技术!
  本篇是《自己动手写把"锁"》系列技术铺垫的最后一个知识点。本篇主要讲解LockSupport工具类,它用来实现线程的挂起和唤醒。
 
  LockSupport是Java6引入的一个工具类,它简单灵活,应用广泛。
 
  一、简单
 
  俗话说,没有比较就没有伤害。这里咱们还是通过对比来介绍LockSupport的简单。
 
  在没有LockSupport之前,线程的挂起和唤醒咱们都是通过Object的wait和notify/notifyAll方法实现。
 
  写一段例子代码,线程A执行一段业务逻辑后调用wait阻塞住自己。主线程调用notify方法唤醒线程A,线程A然后打印自己执行的结果。
 
  执行这段代码,不难发现这个错误:
 
  Exceptioninthread"main"java.lang.IllegalMonitorStateException
 
  atjava.lang.Object.notify(NativeMethod)
 
  原因很简单,wait和notify/notifyAll方法只能在同步代码块里用(这个有的面试官也会考察)。所以将代码修改为如下就可正常运行了:
 
  复制代码
 
  publicclassTestObjWait{
 
  publicstaticvoidmain(String[]args)throwsException{
 
  finalObjectobj=newObject();
 
  ThreadA=newThread(newRunnable(){
 
  @Override
 
  publicvoidrun(){
 
  intsum=0;
 
  for(inti=0;i<10;i++){
 
  sum+=i;
 
  }
 
  try{
 
  synchronized(obj){
 
  obj.wait();
 
  }
 
  }catch(Exceptione){
 
  e.printStackTrace();
 
  }
 
  System.out.println(sum);
 
  }
 
  });
 
  A.start();
 
  //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
 
  Thread.sleep(1000);
 
  synchronized(obj){
 
  obj.notify();
 
  }
 
  }
 
  }
 
  复制代码
 
  那如果咱们换成LockSupport呢?简单得很,看代码:
 
  直接调用就可以了,没有说非得在同步代码块里才能用。简单吧。
 
  二、灵活
 
  如果只是LockSupport在使用起来比Object的wait/notify简单,那还真没必要专门讲解下LockSupport。最主要的是灵活性。
 
  上边的例子代码中,主线程调用了Thread.sleep(1000)方法来等待线程A计算完成进入wait状态。如果去掉Thread.sleep()调用,代码如下:
 
  复制代码
 
  publicclassTestObjWait{
 
  publicstaticvoidmain(String[]args)throwsException{
 
  finalObjectobj=newObject();
 
  ThreadA=newThread(newRunnable(){
 
  @Override
 
  publicvoidrun(){
 
  intsum=0;
 
  for(inti=0;i<10;i++){
 
  sum+=i;
 
  }
 
  try{
 
  synchronized(obj){
 
  obj.wait();
 
  }
 
  }catch(Exceptione){
 
  e.printStackTrace();
 
  }
 
  System.out.println(sum);
 
  }
 
  });
 
  A.start();
 
  //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
 
  //Thread.sleep(1000);
 
  synchronized(obj){
 
  obj.notify();
 
  }
 
  }
 
  }
 
  复制代码
 
  多运行几次上边的代码,有的时候能够正常打印结果并退出程序,但有的时候线程无法打印结果阻塞住了。原因就在于:主线程调用完notify后,线程A才进入wait方法,导致线程A一直阻塞住。由于线程A不是后台线程,所以整个程序无法退出。
 
  那如果换做LockSupport呢?LockSupport就支持主线程先调用unpark后,线程A再调用park而不被阻塞吗?是的,没错。代码如下:
 
  复制代码
 
  publicclassTestObjWait{
 
  publicstaticvoidmain(String[]args)throwsException{
 
  finalObjectobj=newObject();
 
  ThreadA=newThread(newRunnable(){
 
  @Override
 
  publicvoidrun(){
 
  intsum=0;
 
  for(inti=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调用,所以不需要担心线程间的执行的先后顺序。
 
  三、应用广泛
 
  LockSupport在Java的工具类用应用很广泛,咱们这里找几个例子感受感受。以Java里最常用的类ThreadPoolExecutor为例。先看如下代码:
 
  复制代码
 
  publicclassTestObjWait{
 
  publicstaticvoidmain(String[]args)throwsException{
 
  ArrayBlockingQueue<Runnable>queue=newArrayBlockingQueue<Runnable>(1000);
 
  ThreadPoolExecutorpoolExecutor=newThreadPoolExecutor(5,5,1000,TimeUnit.SECONDS,queue);
 
  Future<String>future=poolExecutor.submit(newCallable<String>(){
 
  @Override
 
  publicStringcall()throwsException{
 
  TimeUnit.SECONDS.sleep(5);
 
  return"hello";
 
  }
 
  });
 
  Stringresult=future.get();
 
  System.out.println(result);
 
  }
 
  }
 
  复制代码
 
  代码中我们向线程池中扔了一个任务,然后调用Future的get方法,同步阻塞等待线程池的执行结果。
 
  这里就要问了:get方法是如何组塞住当前线程?线程池执行完任务后又是如何唤醒线程的呢?
 
  咱们跟着源码一步步分析,先看线程池的submit方法的实现:
 
  在submit方法里,线程池将我们提交的基于Callable实现的任务,封装为基于RunnableFuture实现的任务,然后将任务提交到线程池执行,并向当前线程返回RunnableFutrue。
 
  进入newTaskFor方法,就一句话:returnnewFutureTask<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:
 
  限于篇幅,jdk里的更多应用就不再追下去了。
 
  四、LockSupport的实现
 
  学习要知其然,还要知其所以然。接下来不妨看看LockSupport的实现。
 
  进入LockSupport的park方法,可以发现它是调用了Unsafe的park方法,这是一个本地native方法,只能通过openjdk的源码看看其本地实现了。

如需转载,请注明文章出处和来源网址:http://www.divcss5.com/html/h56866.shtml