1.Java 异步编程的源码完美利器:CompletableFuture 指北
2.Java多线程(十五)Future和CompletableFuture的13种方法
3.Java线程池详解:Future的使用和实现
4.Java线程池之线程返回值
5.Java 异步编程的几种方式
6.Java多线程常用类总结:Future、CountDownLatch、源码CyclicBarrier
Java 异步编程的源码完美利器:CompletableFuture 指北
在Java异步编程领域,CompletableFuture是源码不可或缺的利器。本文旨在深入探讨CompletableFuture,源码以解答其为何能成为异步编程的源码魔兽世界网页源码完美解决方案。
在讨论Future获取异步执行结果之前,源码我们先了解Future接口及其常用方法。源码Future接口提供了丰富的源码功能,包括取消任务、源码判断任务状态以及获取结果的源码方法。
通过Future接口,源码我们能使用FutureTask类将Callable任务转化为可执行的源码异步任务,并在必要时获取任务结果。源码然而,源码Future与FutureTask在实际应用中存在不足,如缺乏异步回调机制、无法主动完成或取消任务、阻塞式获取结果以及异常处理的灵活性问题。
为解决这些不足,Java引入了CompletableFuture,提供更丰富的功能,如异步回调、任务组合、时序依赖关系描述以及异常处理。CompletableFuture通过多种方法创建任务,如使用Runnable、Supplier接口,以及默认使用的ForkJoinPool线程池。
在处理任务依赖关系时,CompletableFuture提供了描述串行、AND汇聚、OR汇聚以及异常处理的接口。通过thenApply、thenAccept、thenRun和thenCompose等方法,我们能清晰描述任务的串行执行关系。
对于AND汇聚关系,我们可以使用thenCombine、thenAcceptBoth或runAfterBoth等接口;而对于OR汇聚关系,applyToEither、acceptEither或runAfterEither等接口则能实现这一目的。这些方法允许我们灵活地组合和处理异步任务。
异常处理在异步编程中尤为重要,CompletableFuture通过简单易用的电玩城源码定制方法,如exceptionally、whenComplete和handle等,帮助我们捕获并处理异常。这些方法允许我们以链式编程的方式,优雅地处理异步操作中的异常情况。
获取异步结果时,我们有多种选择,如get、join、whenComplete、handle、allOf和anyOf等方法。这些方法提供了灵活的接口,以适应不同的异步获取需求。例如,allOf方法允许我们在所有任务完成时触发操作,而anyOf方法则等待任意一个任务完成。
通过以上内容,我们全面理解了CompletableFuture在Java异步编程中的作用,它不仅解决了Future与FutureTask的不足,还提供了丰富的功能,以支持更复杂的异步编程场景。CompletableFuture是Java异步编程的完美利器,值得开发者深入研究和掌握。
Java多线程(十五)Future和CompletableFuture的种方法
Future和CompletableFuture的种方法,为Java多线程编程提供了更为强大的异步计算功能。在处理并发任务时,Future和Callable的结合可以使得主线程在等待结果的同时,执行其他操作,大大提升了程序的效率和灵活性。
异步计算允许在不等待结果的情况下继续执行代码流程,通过使用另一个线程来完成部分计算,使得调用可以继续运行或返回,而无需等待计算结果。Future接口正是为了满足这种需求而设计的,它允许程序异步获取执行结果,无需阻塞主线程。
实现异步计算的关键在于FutureTask,它是一个具体的Future实现,同时具备Runnable和Future接口。当FutureTask内部的run方法执行完Callable的call方法后,结果会被保存在私有成员outcome中。通过调用get方法获取这个object的值,即可完成FutureTask的单号网源码下载任务。
在实际应用中,CompletableFuture进一步扩展了Future的功能,以解决多个Future结果之间的依赖和相关性问题。通过四种静态方法创建异步操作,用户可以更灵活地管理多个并发任务。
使用CompletableFuture时,当计算结果完成或抛出异常时,可以执行特定的回调方法,如whenComplete或whenCompleteAsync,实现更为精细的控制逻辑。此外,thenApply方法用于在任务之间串联,thenAccept则专注于消费任务结果,无需返回值。handle方法用于处理任务完成时的异常情况,与thenApply不同的是,它在任务完成后再执行处理,而thenApply仅处理正常任务。
thenRun方法在任务完成后执行后续操作,无需关注任务结果。thenCombine方法允许合并两个任务的结果,而thenAcceptBoth方法在两个任务都完成后进行操作,但不返回任何值。applyToEither和acceptEither方法则分别选择执行速度快的任务结果进行下一步操作,但不返回任何值。runAfterEither和runAfterBoth方法则在任何一个或两个任务完成后执行指定操作,用于实现更为复杂的控制流。
最后,thenCompose方法用于将任务的执行结果转换为另一个任务,实现任务链的构建。通过这些方法的组合应用,开发者可以构建出复杂而高效的并发程序结构,极大地提升了Java多线程编程的灵活性和效率。
Java线程池详解:Future的使用和实现
在处理异步任务时,Java线程池中的任务会返回一个Future对象,用于管理任务执行结果和状态。本文将详细介绍Future的使用和实现,包括获取执行结果、取消任务、获取任务状态以及FutureTask的详细实现。
1. 使用Future
1.1. 获取任务执行结果
Future提供了一个不带参数的get方法和一个带超时参数的get方法用于获取任务的执行结果。若任务执行完成,get方法会立即返回或抛出一个Exception;如果任务未完成,get方法会阻塞直到任务结束。etc挖矿源码带超时的get方法则会在指定时间内任务未完成时抛出TimeoutException。
java
ExecutorService es = Executors.newFixedThreadPool(1);
Future f = es.submit(new Callable() { @Override public Date call() throws Exception { Thread.sleep(); return new Date(); } });
try {
f.get(, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
System.out.println("timeout");
}
// 输出:timeout
如果任务抛出异常,get方法会将异常封装为ExecutionException并抛出,可以通过getCause方法获得原始异常。若任务被取消,get方法将抛出CancellationException。
1.2. 取消任务
通过Future可以取消任务,例如在get超时时抛弃任务时,应立即停止这些任务以避免为不再需要的结果浪费计算资源。
java
ExecutorService es = Executors.newFixedThreadPool(1);
Future f = es.submit(new Runnable() { @Override public void run() { throw new RuntimeException("task throw error test"); } });
// 等待任务执行完成
try {
f.get();
} catch (ExecutionException e) {
System.out.println(e.getCause().getMessage());
}
// 输出:task throw error test
1.3. 获取任务状态
Future提供了isDone和isCancelled两个方法来获取任务的状态。如果任务已完成,isDone返回true;如果任务被取消,isCancelled返回true。
java
/* 任务是否已完成。
完成可能是由于正常终止、异常或取消,在所有这些情况下,该方法将返回true。*/
boolean isDone();
/* Future是否已经被取消
在调用cancel后,无论cancel是否返回true改返回都将始终返回true。*/
boolean isCancelled();
2. FutureTask
FutureTask是Future的一个具体实现,实现了RunnableFuture接口。Executor框架下的线程池通常使用FutureTask作为底层实现。在AbstractExecutorService中,所有提交的任务都会被先封装为FutureTask,然后在execute方法中执行。FutureTask通过newTaskFor方法统一生成,并在任务执行前将其封装。
2.1. FutureTask的状态
FutureTask中的state字段记录了任务的当前状态,FutureTask中的操作在发起前都要先校验状态,操作后又要更新状态。FutureTask中的状态包括NEW、COMPLETING、NORMAL、EXCEPTIONAL、CANCELLED和INTERRUPTING,分别表示任务执行结束、执行抛出异常、被取消和被中断。
状态更新流程如下:通常调用get方法获取任务结果的线程与执行任务线程并非同一线程,因此为保证state字段的线程安全性,state字段的更新使用了Unsafe类的compareAndSwapObject方法,使用CAS算法进行更新。
2.2. get方法的源码 版权声明格式实现
当调用FutureTask的get方法时,会阻塞直到任务执行完成或等待超时。其实现为在get方法中通过一个for(;;)循环一直循环到任务结束或超时,循环中执行:
- 当任务正常或异常执行完成后,会唤醒阻塞队列中的所有线程。
- 线程被唤醒后会返回任务的状态。
- 若调用get的线程被中断,则会立即抛出InterruptedException。
- 若任务被取消,则会抛出CancellationException。
2.3. FutureTask的执行
生成FutureTask时,需要指定异步任务,如指定的任务类型为Runnable类型的,则会被转换为Callable类型并将任务保存在callable字段中。通常,FutureTask的任务会在一个异步线程中执行,即在异步线程中执行其run方法。而FutureTask的run方法会调用其持有的异步任务的call方法,获取call的执行结果来更新FutureTask的结果和状态。
初始时任务的状态为NEW和执行任务的线程(runner字段保存了执行任务的线程)为null,因此若run开始时非NEW状态或runner非空,则任务已被执行或正在执行中。为避免重复发起执行,这里会直接返回。call正常执行结束后或抛出异常结束时都会使用返回的结果或异常去更新状态。
若执行正常完成,则将outcome字段设置为执行返回的结果,FutureTask状态最终更新为正常结束NORMAL。若执行时抛出异常结束,则将outcome设置为抛出的异常,FutureTask状态最终更新为非正常结束EXCEPTIONAL。
任务结果更新完成后会通知到阻塞等待结果的线程。任务执行结束后遍历等待队列中的所有节点,将节点的被阻塞等待的线程逐个唤醒,并移除节点。这些操作由finishCompletion实现。
2.4. cancel方法的实现
cancel方法可以取消任务。取消时,若选择了中断,则先更新状态为INTERRUPTING,然后对执行任务的线程发出中断,之后再将任务状态更新为INTERRUPTED。在中断和状态更新完成后,表示任务已经被取消,因此最后也需要调用finishCompletion唤醒所有等待该任务执行结束的线程。
以上内容详细介绍了Future和FutureTask的使用与实现,帮助开发者更好地理解和运用异步任务处理机制。通过这些机制,开发者可以更高效地管理任务执行,处理结果获取和取消需求。
Java线程池之线程返回值
前言
通常来说,开启线程能够提高程序的并发能力,而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原理请移步:JavaThread.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来实现的。第一种Futuresubmit(Callabletask);传入的参数为Callable,Callable.run()决定返回值。
第二种Future<?>submit(Runnabletask);传入参数为Runnable,Runnable.run()没有返回值,因此此时Future.get()返回null。
第三种Futuresubmit(Runnabletask,Tresult);传入参数除了Runnable,还有result,虽然Runnable.run()没有返回值,但是最终Future.get()将会返回result。
总结以上分析了三种方式获取线程结果(实际两种,最后两种可归结为一类),虽然做法不一样,但速途同归。想要获取线程执行结果,无非两个核心:
1、能够知道线程何时结束。2、能够将结果抛出(比如存储在成员变量里)。
下篇将重点分析线程池的使用与原理。
演示代码若是有帮助,给github点个赞呗~
您若喜欢,请点赞、关注,您的鼓励是我前进的动力
作者:小鱼人爱编程
Java 异步编程的几种方式
异步编程在Java中是让程序并发运行的一种方式,它能提高程序的吞吐量,更好地处理高并发场景,并减少用户等待时间。本文将介绍Java中实现异步编程的几种方式。
最简单的方法是使用Thread类。在Java 8以上版本,可以使用Lambda表达式简化代码。创建一个Thread对象来执行异步任务。下面提供同步和异步版本的示例进行对比。
同步执行时,耗时 ms;异步执行耗时 ms,异步方式明显提高了效率。
在示例中,一个线程在main方法内启动,执行异步任务。主线程与线程并发运行,任务并行执行,直到主线程执行完其他任务后等待线程完成。
然而,这种方式仅适用于示例,生产环境使用时需注意可能的事故。Thread方式存在线程管理问题和并发控制问题。
在JDK 1.5中引入了FutureTask类,它实现了Future接口,表示异步计算结果。FutureTask可以处于三种状态:未执行、执行中和已完成。下面修改示例,将任务方法修改为返回String类型,使用FutureTask执行异步编程。
使用FutureTask方式,异步编程的耗时与Thread方式相近。通过FutureTask获取结果时,会阻塞调用线程,无法实现真正的异步。
JDK 8引入了CompletableFuture类,实现Future和CompletionStage接口,提供多种方法进行异步编程。下面展示使用CompletableFuture实现异步编程。
在主线程中开启异步任务,并返回CompletableFuture对象。使用thenCompose方法,将一个CompletableFuture的结果作为另一个方法的参数,实现任务链。
CompletableFuture内部使用Fork/Join框架异步处理任务,简化了异步代码编写。此外,CompletableFuture功能强大,提供了方便的方法。
总结,本文介绍了Java中实现异步编程的三种基本方式:Thread、FutureTask和CompletableFuture。这些基础工具在实际应用中可以进一步扩展,如Guava库的ListenableFuture和Futures类,以及Spring框架的异步执行能力,通过@Async注解实现异步处理。有兴趣的读者可以自行学习这些高级工具。
Java多线程常用类总结:Future、CountDownLatch、CyclicBarrier
在Java多线程编程中,`FutureTask`、`CountDownLatch`和`CyclicBarrier`是常用的类。下面我们将对这三个类进行总结,以期更直观地理解它们的用法和应用场景。
`FutureTask`是一个工具类,它实现了`Future`和`Runnable`接口,具有两种构造方式。当需要在多线程环境下执行某个任务并获取结果时,`FutureTask`可以作为一个载体,不仅能够将任务提交给线程池执行,还能将执行结果通过`Future`接口获取。这对于需要并行执行的串行任务特别有用,能够有效地提高程序的执行效率。
`CountDownLatch`用于实现线程间的同步和等待。在某些场景下,我们需要等待多个线程执行完毕,如查询不同类型的账单并进行对比入库操作。通过`CountDownLatch`的计数器功能,可以确保在主线程等待所有查询操作完成后再继续执行后续操作。使用`latch.countDown()`对计数器减一,通过`latch.await()`实现对计数器等于0的等待。
`CyclicBarrier`则是进一步优化`CountDownLatch`功能的类。它允许一组线程等待直到所有线程到达特定的“栅栏”点。在处理查询订单和对比操作时,可以将两个操作并行化,通过`barrier.await()`让计数器减一,等待所有线程到达后执行对账操作。`CyclicBarrier`的独特之处在于,当所有线程到达栅栏点后,它会调用回调函数执行特定操作,如对账操作。
总结来说,`FutureTask`用于封装任务和结果,`CountDownLatch`用于等待多个线程完成,而`CyclicBarrier`则用于控制多个线程在特定点同时执行操作。这些类在实现多线程并行操作时提供了强大的工具,有助于优化代码结构和提高程序性能。
参考:
《Java并发编程实战》王宝令