前言
通常来说,开启线程能够提高程序的并发能力,而Thread 类里并没有任何方法可以获取到线程的执行结果。接下来,我们将一步步分析如何拿到线程的执行结果。通过本篇文章,你将了解到:
1、原始方式 获取线程执行结果2、FutureTask 获取线程执行结果3、线程池 获取线程执行结果
1、原始方式 获取线程执行结果
publicclassThreadRet{privateintsum=0;publicstaticvoidmain(Stringargs[]){ThreadRetthreadRet=newThreadRet();threadRet.startTest();}privatevoidstartTest(){Threadt1=newThread(newRunnable(){@Overridepublicvoidrun(){inta=5;intb=5;intc=a+b;//将结果赋予成员变量sum=c;System.out.println("c:"+c);}});t1.start();try{//等待线程执行完毕t1.join();//执行过这条语句后,说明线程已将sum赋值}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("sum:"+sum);}}
打印结果如下:
说明主线程已经拿到线程1的执行结果了。原理也很简单:
线程1在计算结果,那么其它线程必须要等待它执行结束了才能得到有效的结果。
此时可以选择两种方式检测计算结果:轮询与等待-通知,当然是用等待-通知更有效率。
Thread.join 即是是用了等待-通知方式,Thread.join 一直等到目标线程执行完毕后才返回,否则阻塞等待。
Thread.join 原理请移步:Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 详解
2、FutureTask 获取线程执行结果
FutureTask 使用
虽然上述方式能够获取线程执行结果,然而却有如下不足之处:
1、每次都需要定义不同类型的成员变量来接收返回结果。2、每次都需要Thread.join 阻塞等待。
想想有没有什么方法将上述功能封装起来呢?该到Callable出场了。
privatevoidstartCall(){//定义Callable,具体的线程处理在call()里进行Callable<String>callable=newCallable(){@OverridepublicObjectcall()throwsException{Stringresult="helloworld";//返回resultreturnresult;}};//定义FutureTask,持有Callable引用FutureTask<String>futureTask=newFutureTask(callable);//开启线程newThread(futureTask).start();try{//获取结果Stringresult=futureTask.get();System.out.println("result:"+result);}catch(ExecutionExceptione){e.printStackTrace();}catch(InterruptedExceptione){e.printStackTrace();}}
最终打印如下:
可以看出,能够正确获取到线程的执行结果了。
操作步骤分四步:
1、定义Callable,线程具体的工作在此处理,可以返回任意值。2、定义FutureTask,持有Callable 引用,并且指定泛型的具体类型,该类型决定了线程最终的返回类型。实际上就是将Callable.call()返回值强转为具体类型。3、最后构造Thread,并传入FutureTask,而FutureTask实现了Runnable。4、通过FutureTask 获取线程执行结果。
FutureTask 原理
先看关键类的定义:
#Callable.javapublicinterfaceCallable<V>{//返回泛型Vcall()throwsException;}
Callable 只有一个方法,该方法返回泛型类型。
再看FutureTask:
#FutureTask.javapublicvoidrun(){try{//传进来的CallableCallable<V>c=callable;if(c!=null&&state==NEW){Vresult;booleanran;try{//执行Callablecall方法result=c.call();ran=true;}catch(Throwableex){...}//记录结果if(ran)set(result);}}finally{...}}protectedvoidset(Vv){if(U.compareAndSwapInt(this,STATE,NEW,COMPLETING)){//记录到成员变量outcome里outcome=v;//CAS修改状态U.putOrderedInt(this,STATE,NORMAL);//finalstate//通知等待线程执行结果的其它线程finishCompletion();}}privatevoidfinishCompletion(){//waiters为链表头,该链表记录着所有等待该线程执行结果的其它线程for(WaitNodeq;(q=waiters)!=null;){//CAS不成功,则继续循环if(U.compareAndSwapObject(this,WAITERS,q,null)){//CAS修改成功,将链表头置空//遍历链表for(;;){//取出等待的线程Threadt=q.thread;if(t!=null){q.thread=null;//唤醒LockSupport.unpark(t);}//继续找下一个线程WaitNodenext=q.next;if(next==null)break;q.next=null;//unlinktohelpgcq=next;}break;}}...}
上述逻辑很清晰:
1、FutureTask 实现了Runnable,重写了run()方法,当线程执行时会执行run()方法,而run()最终调用了Callable的call()方法,返回值记录在成员变量outcome里。2、当run()执行完毕后,说明结果已经出来了,将通知其它线程(唤醒)。
既然有唤醒过程,那么必然有等待过程,否则唤醒的逻辑无意义。FutureTask 实现了Future接口,重写了get()等方法。
#FutureTask.javapublicVget()throwsInterruptedException,ExecutionException{ints=state;if(s<=COMPLETING)//阻塞等待s=awaitDone(false,0L);//处理返回值returnreport(s);}privateintawaitDone(booleantimed,longnanos)throwsInterruptedException{WaitNodeq=null;booleanqueued=false;for(;;){//一些临界状态判断//封装为节点,加入到等待链表里//限时等待elseif(timed){...if(state<COMPLETING)//线程挂起指定的时间LockSupport.parkNanos(this,parkNanos);}else//一直等待,直到有结果返回LockSupport.park(this);}}privateVreport(ints)throwsExecutionException{Objectx=outcome;if(s==NORMAL)//将强转为泛型指定的类型return(V)x;...}
由上可以看出:
1、FutureTask.get() 阻塞等待线程执行结果返回。2、若是还没结果,先将自己加入到等待链表里,并且可以指定等待一定的时间,若是时间到了还是没有结果,就直接返回。3、最后等到执行结果后,强转为想要的类型,在例子里强转为String。
整个流程用图表示如下:
对比原始方式和FutureTask方式异同点:不同点原始方式通过Object.wait/Object.notify 来实现等待通知,而FutureTask 通过Volatile + CAS+LockSupport 来实现等待通知。
相同点线程执行结果都存储在成员变量里。
3、线程池 获取线程执行结果
小Demo:
privatevoidstartPool(){//线程池ExecutorServiceservice=Executors.newSingleThreadExecutor();//定义CallableCallable<String>callable=newCallable(){@OverridepublicObjectcall()throwsException{Stringresult="helloworld";//返回resultreturnresult;}};//返回Future,实际上是FutureTask实例Future<String>future=service.submit(callable);try{System.out.println(future.get());}catch(ExecutionExceptione){e.printStackTrace();}catch(InterruptedExceptione){e.printStackTrace();}}
线程池提供了三种方式获取线程执行结果,虽然使用方式不太一样,但内部都是依靠Callable+FutureTask来实现的。第一种 Future submit(Callable task);传入的参数为Callable,Callable.run()决定返回值。
第二种Future<?> submit(Runnable task);传入参数为Runnable,Runnable.run() 没有返回值,因此此时Future.get()返回null。
第三种 Future submit(Runnable task, T result);传入参数除了Runnable,还有result,虽然Runnable.run() 没有返回值,但是最终Future.get() 将会返回result。
总结
以上分析了三种方式获取线程结果(实际两种,最后两种可归结为一类),虽然做法不一样,但速途同归。想要获取线程执行结果,无非两个核心:
1、能够知道线程何时结束。2、能够将结果抛出(比如存储在成员变量里)。
下篇将重点分析线程池的使用与原理。
演示代码 若是有帮助,给github 点个赞呗~
您若喜欢,请点赞、关注,您的鼓励是我前进的动力
作者:小鱼人爱编程