当前位置: 首页 > 科技观察

腾讯方面:Thread、Runnable、Callable、Future、FutureTask,说说它们的关系?_0

时间:2023-03-19 23:14:42 科技观察

大家好,我是楼仔!Thread、Runnable、Callable、Future、FutureTask,你能详细说说它们的内部关系吗?这也是面试中经常被问到的问题。本文主要告诉大家各种对象的内部关系,可以灵活运用。以下为文章目录:一、Thread与Runnable1.1Thread先来看看Thread最简单的使用姿势:publicclassMyThreadextendsThread{publicMyThread(Stringname){super(name);}@Overridepublicvoidrun(){字符串名称=Thread.currentThread().getName();System.out.println(name+"已经运行");}publicstaticvoidmain(String[]args){newMyThread("ThreadOne").start();}}线程包含4种状态:Create->Ready->Run->End。执行start()时,线程进入就绪态,对应线程抢占cpu调度资源后,进入运行态。这个时候会调用run方法,执行完就结束了。1.2Runnable先看一下Runnable最简单的用法:System.out.println(name+"已经运行");}publicstaticvoidmain(String[]args){newThread(newMyTask(),"Thread2").start();}}在这里,MyTask是一个Runnable,它将run()方法实现为Thread()输入参数。基本上同学们都知道怎么用,但是你知道原理吗?1.3Thread和Runnable的关系我们看一下Runnable的接口定义:publicinterfaceRunnable{/***当使用一个实现接口Runnable的对象*创建线程时,启动线程导致对象的*run方法在单独执行的*线程中被调用。*

*run方法的一般契约是它可以*采取任何行动。**@seejava.lang.Thread#run()*/publicabstractvoidrun();}英文翻译大致如下:当一个对象继承并实现run()方法时,当线程starts()时,它会在线程中单独执行对象的run()方法。这个翻译基本上说出了Runnable和Thread的关系:MyTask继承了Runnable,实现了run()方法;Thread初始化并使用MyTask作为自己的成员变量;Thread执行run()方法,线程处于“就绪”状态;等待CPU调度,执行Thread的run()方法,但run()内部实现实际上是MyTask.run()方法执行完毕,线程处于“运行”状态。这里的第2步和第4步需要对照源代码进行检查。Thread初始化时,MyTask作为入参target,最终赋值给Thread.target:Thread.run()执行时,实际是执行的target.run(),即MyTask.run(),它是一个典型的策略模式:2.Callable、Future、FutureTask先看它们的整体关系图:刚开始看这个图,感觉Java真的很麻烦。已经有两种创建线程的方式,Thread和Runnable,为什么还要这样呢?3件事呢?其实对于Thread和Runable来说,它们的run()是没有返回值的,不能抛出异常,所以当需要返回多线程数据的时候,就需要使用Callable和Feature。2.1CallableCallable是一个接口,它有一个Vcall()方法,这个V就是我们的返回值类型:publicinterfaceCallable{/***计算一个结果,如果不能计算则抛出异常。**@returncomputedresult*@throwsExceptionifunabletocomputearesult*/Vcall()throwsException;}我们一般使用匿名类形式的Callable,call()包含具体的业务逻辑:Callablecallable=newCallable(){@OverridepublicStringcall()throwsException{//执行业务逻辑...return"thisisCallableisrunning";}};这里有个问题,这个callable.call()和Thread.run()是什么关系?2.2FutureTask通过关系图,FutureTask继承RunnableFuture,RunnableFuture继承Runnable和Future:publicinterfaceRunnableFutureextendsRunnable,Future{/***SetsthisFuturetotheresultofitscomputation*unlessithasbeencancelled.*/voidrun();}所以,FutureTask也是一个Runnable!!!这里有点意思,既然FutureTask是一个Runnable,那肯定是要实现FutureTask.run()方法方法,那么FutureTask也可以作为Thread的初始化参数,使用姿势如下:newThread(FutureTaskobject).start();所以当Thread.run()被执行时,FutureTask.run()实际上被执行了。这是我们破解的第一层下面,我们破解FutureTask.run()和Callable.call()之间的关系。2.3Callable和FutureTask的关系在FutureTask初始化的时候,Callable必须作为FutureTask的初始化入参:当执行FutureTask.run()时,实际上执行的是Callable.call():所以,这里又是一个典型的策略模式!!!现在我们应该可以清楚地理解Thread、Runnable、FutureTask和Callable之间的关系了:Thread.run()执行Runnable.run();FutureTask继承Runnable并实现FutureTask.run();FutureTask.run()Callable.run()被执行;依次传递,最后Thread.run()实际上是Callable.run()被执行。所以整个设计方法其实就是两种策略模式,Thread和Runnable是一种策略模式,FutureTask和Callable是另一种策略模式,最后通过Runnable和FutureTask之间的继承关系将这两种策略模式组合在一起。嗯。..我们是不是忘记了Future~~2.4Future为什么有Future?再问一个问题,大家可能都知道了。我们使用FutureTask借助Thread执行线程后,如何获取结果数据呢?这里需要用到Future。我们看一下Future接口:publicinterfaceFuture{//取消任务,如果任务正在运行,当mayInterruptIfRunning为true时,表示任务将被中断,返回true;//为false时,会等待这个任务执行完毕,返回true;如果任务没有执行,取消任务后返回true,如果任务执行了,返回falsebooleancancel(booleanmayInterruptIfRunning);//判断任务是否取消,正常执行不取消booleanisCancelled();//判断任务是否已经执行,任务被取消或发生异常,则视为完成,returntruebooleanisDone();//获取任务返回结果,如果任务没有完成,则等待完成并返回结果,如果在获取过程中出现异常,则抛出异常,//例如异常如中断发生时会抛出InterruptedExceptionVget()throwsInterruptedException,ExecutionException;//如果在指定时间内没有返回结果,则抛出TimeoutExceptionVget(longtimeout,TimeUnitunit)throwsInterruptedException,ExecutionException,TimeoutException;}对于FutureTask来说,Callable是它的任务,FutureTask内部维护了一个任务状态,所有状态都是围绕这个任务进行的,随着任务的进行,状态不断更新。FutureTask继承Future实现任务取消、数据获取、任务状态判断等功能。比如我们经常调用get()方法来获取数据。如果任务没有完成,当前线程会被放入阻塞队列中等待。当任务执行时,阻塞队列中的线程会被唤醒。3.具体例子privatestaticListprocessByMultiThread(IntegerbatchSize)throwsExecutionException,InterruptedException{Listoutput=newArrayList<>();//获取批量数据List>batchProcessData=getProcessData(batchSize);//启动线程List>>futureTaskList=newArrayList<>();for(ListprocessData:batchProcessData){Callable>callable=()->processOneThread(processData);FutureTask>futureTask=newFutureTask<>(callable);新线程(futureTask).start();//启动线程futureTaskList.add(futureTask);}//获取线程数据的返回for(FutureTaskfutureTask:futureTaskList){ListprocessData=(List)futureTask.get();output.addAll(processData);}returnoutput;}这个例子很简单:先把数据分成batchSizeN个批次;开始移动N个线程执行任务;通过futureTask.get()获取各个线程的数据,并汇总输出。这个例子其实不太适合线上场景,因为每次调用都会初始化线程。如果调用太多,内存可能会耗尽。会爆,需要用到线程池。