Java并发体系-第三阶段-JUC并发包-[2]-【万字长文系列】
1、文章可能会优先更新在Github,个人博客,公众号【Github有】。其它平台会晚一段时间。个人博客备用地址
2、如果Github很卡,可以在Gitee浏览,或者Gitee在线阅读,个人博客。Gitee在线阅读和个人博客加载速度比较快。
3、转载须知:转载请注明GitHub出处,让我们一起维护一个良好的技术创作环境!
4、如果你要提交 issue 或者 pr 的话建议到 Github 提交。
5、笔者会陆续更新,如果对你有所帮助,不妨Github点个Star~。你的Star是我创作的动力。
关于Java并发系列的书籍笔者看过4本,Java并发的教程视频看了4个机构的视频。个人认为笔者的并发系列文章可能是市面上关于Java并发最详细,深度最深的文章。如果有更好的文章,读者可以在下方评论,为开源社区做点贡献,也为其他人提供帮助。
1|0Phaser工具
1|1简介
java7中引入了一种新的可重复使用的同步屏障,称为移相器Phaser。Phaser拥有与CyclicBarrier
和CountDownLatch
类似的功能.
但是这个类提供了更加灵活的应用。CountDownLatch和CyclicBarrier都是只适用于固定数量的参与者。移相器适用于可变数目的屏障,在这个意义上,可以在任何时间注册新的参与者。并且在抵达屏障是可以注销已经注册的参与者。因此,注册到同步移相器的参与者的数目可能会随着时间的推移而变化。
如CyclicBarrier一样,移相器可以重复使用,这意味着当前参与者到达移相器后,可以再一次注册自己并等待另一次到达.
移相器的另一个重要特征是:移相器可能是分层的,这允许你以树形结构来安排移相器以减少竞争
简单例子:
结果:
1|2重复使用的例子
结果:
可以看到栅栏被重复利用了。
1|3动态减少
结果:
3号运动员受伤了,那么他就不能完成jump,3号运动员的phaser.arriveAndAwaitAdvance()
也就无法执行,就会导致程序无法终止。因为Phaser数量是3个,只要三个线程都到了才会结束。所以说3号运动员受伤后,可以减少Phaser的数量:phaser.arriveAndDeregister();//动态减少
1|4常用API
注册
register
- 是注册一个线程数,比较常用
bulkRegister
- 可以批量注册
到达
arrive
- 这个到达后,不会阻塞,相当于
countdown
机制【因为countdown只会阻塞调用者,其它线程干完任务就可以干其他事】 - 大家要理解一点,party 数和线程是没有关系的,不能说一个线程代表一个 party,因为我们完全可以在一个线程中重复调用 arrive() 方法。这么表达纯粹是方便理解用。
arriveAndAwaitAdvance
- 到达后会阻塞,相当于
CyclicBarrier
机制
arriveAndDeregister
- 当线程出现异常,不能正常到达时,可以调用该方法,
动态减少注册数
举例
onAdvance()
这个方法是 protected 的,所以它不是 phaser 提供的 API,从方法名字上也可以看出,它会在一个 phase 结束的时候被调用。
它的返回值代表是否应该终结(terminate)一个 phaser,之所以拿出来说,是因为我们经常会见到有人通过覆写该方法来自定义 phaser 的终结逻辑,如:
1、我们可以通过
phaser.isTerminated()
来检测一个 phaser 实例是否已经终结了2、当一个 phaser 实例被终结以后,register()、arrive() 等这些方法都没有什么意义了,大家可以玩一玩,观察它们的返回值,原本应该返回 phase 值的,但是这个时候会返回一个负数。
监控子线程任务
- 相当于起到监控的作用
- 如果子线程还没有执行完成,主线程就会阻塞
- 相较而言,可以不用增加注册量
举例
强制关闭
- 强制关闭phaser,但是
如果线程陷入阻塞,不会唤醒
1|5监控API
获取阶段数
- 返回当前相位数。 最大相位数为Integer.MAX_VALUE
- 每增加一轮就会加一
举例
结果:
获取注册的数
- 获得注册的线程数,相当于Countdown初始的的计数器
- 可以动态更改
获得到达和未到达的数目
getArrivedParties
- 获得已经到达的线程数,和没有到达的线程数
getUnarrivedParties
- 获得没有到达的线程数,和没有到达的线程数
1|6Phaser的分层结构
Tiering 这个词本身就不好翻译,大家将就一下,要表达的意思就是,将多个 Phaser 实例构造成一棵树。
1、第一个问题来了,为什么要把多个 Phaser 实例构造成一棵树,解决什么问题?有什么优点?
Phaser 内部用一个 state
来管理状态变化,随着 parties 的增加,并发问题带来的性能影响会越来越严重。
通常我们在说 0-15 位这种,说的都是从低位开始的
state 的各种操作依赖于 CAS,典型的无锁操作,但是,在大量竞争的情况下,可能会造成很多的自旋。
而构造一棵树就是为了降低每个节点(每个 Phaser 实例)的 parties 的数量,从而有效降低单个 state 值的竞争。
2、第二个问题,它的结构是怎样的?
这里我们不讲源码,用通俗一点的语言表述一下。我们先写段代码构造一棵树:
根据上面的代码,我们可以画出下面这个很简单的图:

这棵树上有 7 个 phaser 实例,每个 phaser 实例在构造的时候,都指定了 parties 为 5,但是,对于每个拥有子节点的节点来说,每个子节点都是它的一个 party,我们可以通过 phaser.getRegisteredParties() 得到每个节点的 parties 数量:
- m1、m2、m3、m4 的 parties 为 5
- n1 的 parties 为 5 + 3,n2 的 parties 为 5 + 1
- root 的 parties 为 5 + 2
结论应该非常容易理解,我们来阐述一下过程。
在子节点注册第一个 party 的时候,这个时候会在父节点注册一个 party,注意这里说的是子节点添加第一个 party 的时候,而不是说实例构造的时候。
在上面代码的基础上,大家可以试一下下面的这个代码:
第一行代码中构造了 m5 实例,但是此时它的 parties == 0,所以对于父节点 n2 来说,它的 parties 依然是 6,所以第二行代码输出 6。第三行代码注册了 m5 的第一个 party,显然,第四行代码会输出 7。
当子节点的 parties 降为 0 的时候,会从父节点中"剥离",我们在上面的基础上,再加两行代码:
由于 m5 之前只有一个 parties,所以一次 arriveAndDeregister() 就会使得它的 parties 变为 0,此时第二行代码输出父节点 n2 的 parties 为 6。
还有一点有趣的是,在非树的结构中,此时 m5 应该处于 terminated 状态,因为它的 parties 降为 0 了,不过在树的结构中,这个状态由 root 控制,所以我们依然可以执行 m5.register()...
3、每个 phaser 实例的 phase 周期有快有慢,怎么协调的?
在组织成树的这种结构中,每个 phaser 实例的 phase 已经不受自己控制了,由 root 来统一协调,也就是说,root 当前的 phase 是多少,每个 phaser 的 phase 就是多少。
那又有个问题,如果子节点的一个周期很快就结束了,要进入下一个周期怎么办?需要等!这个时候其实要等所有的节点都结束当前 phase,因为只有这样,root 节点才有可能结束当前 phase。
我觉得 Phaser 中的树结构我们要这么理解,我们要把整棵树当做一个 phaser 实例,每个节点只是辅助用于降低并发而存在,整棵树还是需要满足 Phaser 语义的。
2|0阻塞队列
2|1请谈谈对阻塞队列的理解
2|2种类
2|3核心方法
2|4阻塞队列的使用场景
传统版生产者消费者模式 Demo
阻塞队列版生产者消费者模式Demo
3|0线程池
3|1主要优点
- 第一:降低资源消耗.通过重复利用自己创建的线程降低线程创建和销毁造成的消耗.
- 第二: 提高响应速度.当任务到达时,任务可以不需要等到线程的创建,就能立即执行.
- 第三: 提高线程的可管理性.线程是稀缺资源,如果无限的创阿金,不仅会消耗资源,还会较低系统的稳定性,使用线程池可以进行统一分配,调优和监控.
Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor、Executors、ExecutorService、ThreadPoolExecutor 这几个类。
3|2线程池七大参数入门简介
流程举例
一个银行网点 <线程池>,共 10* 个窗口 <maximumPoolSize 最大线程数>,开放 5* 个窗口 <corePoolSize 核心线程数>
。今天办理业务的特别多,其余5个窗口加班一天 <keepAliveTime + unit 多余线程存活时间+单位>,办理业务的人在窗口前排队* <workQueue 请求任务的阻塞队列>。银行里的A职员、B职员... 给办理业务 <threadFactory 产生线程、线程名、线程序数...>最多排10个,来了11个,并且每个窗口都有人在办理业务,多的人怎么拒绝呢?<handler 拒绝策略>
七大参数
-
corePoolSize
线程池中的常驻核心线程数
创建线程池后,当有请求任务进来,就安排池中的线程去执行请求任务
当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列中 -
maximumPoolSize
线程池能够容纳同时执行的最大线程数,此值必须大于等于1 -
keepAliveTime
多余的空闲线程的存活时间
当前线程池数量超过 corePoolSize 时,当空闲时间达到 keepAliveTime 值时,
多余空闲线程会被销毁直到只剩下 corePoolSize 个线程为止 -
unit
keepAliveTime 的单位 -
workQueue
任务队列,被提交但尚未被执行的任务 -
threadFactory
表示生成线程池中工作线程的线程工厂<线程名字、线程序数...>
用于创建线程一般用默认的即可 -
handler
拒接策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时
如何拒绝新的任务
3|3线程池的底层工作流程

1、创建线程池后,等待请求任务
2、当调用 execute() 方法添加请求任务时,线程池做如下判断
- 如果正在运行的线程数量小于 corePoolSize,马上创建线程执行请求任务
- 如果正在运行的线程数量大于或等于 corePoolSize,将请求任务放入阻塞队列
- 如果阻塞队列满了,且正在运行的线程数小于 mamimumPoolSize,创建非核心线程执行请求任务
- 如果队列满了且线程池线程达到最大线程数,线程池启动饱和拒绝策略来执行
3、当一个线程完成任务时,从阻塞队列中取出下一个任务来执行
4、当一个线程无事可做超过一定时间
- 如果当前运行的线程数大于 corePoolSize,该线程被销毁
- 所以,线程池完成所有请求任务后,最终会收缩到 corePoolSize 的大小
3|4线程池的4种拒绝策略
JDK 内置的拒绝策略
-
AbortPolicy(默认)
- 直接抛出 RejectedExecutionException 异常阻止系统正常运行
-
CallerRunsPolicy
- "调用者运行" 一种调节机制
- 该策略既不会抛弃任务,也不会抛出异常
- 而是将某些任务回退到调用者,从而降低新任务的流量
-
DiscardOldestPolicy
- 抛弃队列中等待最久的任务
- 然后把当前任务中加入队列中尝试再次提交当前任务
-
DiscardPolicy
- 直接丢弃任务,不予任何处理也不抛出异常
- 如果允许任务丢失,这是最好的一种方案
以上拒绝策略都是实现了 RejectedExecutionHandler 接口
3|5线程池在实际生产中使用哪一个
后文会介绍Java内置的几个线程池
阿里巴巴 Java 开发手册
线程池不允许使用 Executors 创建,而是通过 ThreadPoolExecutor 的方式
FixedThreadPool 和 SingleThreadPool
允许的阻塞队列容量为 Integer.MAX_VALUE,可能会堆积大量的请求,导致 OOM
CachedThreadPool 和 ScheduledThreadPool
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,导致 OOM
3|6线程池合理配置参数
1、CPU 密集型
意思是该任务需要大量的运算,而没有阻塞,CPU 一直全速运行
CPU 密集任务只有在真正的多核 CPU 上才可能得到加速(通过多线程)
CPU 密集型任务配置尽可能少的线程数量
一般公式 : CPU 核数 + 1个线程的线程池最大线程数
2、IO 密集型
由于 IO 密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程
一般公式 : CPU 核数* 2
3、IO 密集型 2
IO 密集型、即该任务需要大量的 IO,即大量的堵塞
在单线程上运行 IO 密集型的任务会导致浪费大量的 CPU 算力浪费在等待上
所以,IO 密集型任务中使用多线程可以大大的加速程序运行,即时在单核 CPU 上
这种加速主要就是利用了被浪费掉的阻塞时间
参考公式 : CPU 核数 / (1 - 阻塞系数)
例: 8 核CPU 8/(1-0.9) = 80 个线程数
3|7线程池的状态
线程池状态含义如下
• RUNNING 接受新任务并且处理阻塞队列里的任务
• SHUTDOWN :拒绝新任务但是处理阻塞队列里的任务
• STOP :拒绝新任务并且放弃阻塞队列里的任务,同时会中断正在处理的任务。
• TIDYING:所有任务都执行完(包含阻塞队列里面的任务)后,当前线程池活动线程,数为0,将要调用 terminated 方法
• TERMINATED:终止状态,terminated 方法调用完成以后的状态
线程池状态转换列举如下
• RUNNING -> SHUTDOWN 显式调用shutdown () 方法 或者隐式调用了 finalize()方法里面的 shutdown() 方法
• RUNNING或SHUTDOWN) -> STOP 显式调用 shutdownNow() 方法
• SHUTDOWN ->TIDYING 当线程池和任务队列都为空时
• STOP -> TIDYING 当线程池为空时
• TIDYING -> TERMNATED terminated() hook 方法执行完成
3|8线程池的关闭
关闭有两个方法:shutdown
和shutdownNow
shutdown
- 从源码可以看出,本质上执行的是
interrupt
方法 - 如果线程是空闲的,执行的是Condition的await的方法,会被直接打断,被回收
- 如果正在工作,该线程会被打上一个标记,等任务执行后被回收
shutdownNow
- 先打断空闲的打断
- 然后清空任务队列
- 然后不断的尝试打断正在执行的线程
- 最后会返回一个List集合,包含还没有执行的任务
awaitTermination 操作
当线程调用awaitTermination
方法后,当前线程会被阻塞,直到线程池状态变为TERMINATED 才返回 或者等待时间超时才返回。
4|0Executors
内置线程池用的不多,不用太在意
4|1简介
Java通过Executors提供五种线程池,分别为:
-
newCachedThreadPool
:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 -
newFixedThreadPool
:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 -
newScheduledThreadPool
:创建一个定长线程池,支持定时及周期性任务执行。和一个线程的区别
newSingleThreadExecutor Thread 任务执行完成后,不会自动销毁,可以复用 任务执行完成后,会自动销毁 可以将任务存储在阻塞队列中,逐个执行 无法存储任务,只能执行一个任务 -
newSingleThreadExecutor
:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 -
newWorkStealingPool
:创建一个ForkJoin线程池,线程数是CPU核数,可以充分利用CPU资源。从1.8开始有的
简单例子:
有三个内置线程池比较简单,下面介绍下稍复杂的两个内置线程池。
4|2newWorkStealingPool
分析源码我们可以得知
- 采用的ForkJoin框架,可以将任务进行分割,同时线程之间会互相帮助
- 最大的线程数是CPU核数,充分利用CPU资源
4|3newScheduledThreadPool
- 创建的是一个定时的任务,每隔一段时间就会运行一次
首先可以对比的就是Timer这个类
结果
可以发现:如果任务时间超过了定时时长,就无法按照预定的时间执行
其他工具的解决方式:
crontab
定时处理器为了确保时间的正确性,会重新启一个线程
有三个方法
-
schedule(commod,delay,unit) ,这个方法是说系统启动后,需要等待多久执行,delay是等待时间。只执行一次,没有周期性。
-
scheduleAtFixedRate(commod,initialDelay,period,unit),这个是以period为固定周期时间,按照一定频率来重复执行任务,initialDelay是说系统启动后,需要等待多久才开始执行。例如:如果设置了period为5秒,线程启动之后执行了大于5秒,线程结束之后,立即启动线程的下一次,如果线程启动之后只执行了3秒就结束了那执行下一次,需要等待2秒再执行。这个是优先保证任务执行的频率,
-
scheduleWithFixedDelay(commod,initialDelay,delay,unit),这个是以delay为固定延迟时间,按照一定的等待时间来执行任务,initialDelay意义与上面的相同。例如:设置了delay为5秒,线程启动之后不管执行了多久,结束之后都需要先生5秒,才能执行下一次。这个是优先保证任务执行的间隔。
5|0ExecutorService
ExecutorService一般就是用来作为我们自定义线程池的引用。
API
1、getActiveCount()
:获取当前线程池中活跃的线程个数;若是没有execute(Runnable)
任务的话,是不会创建线程的;提交一个任务,也只会创建一个线程去执行,而不会一次性直接创建corePoolSize
个线程。
2、allowCoreThreadTimeOut(true)
:当任务执行完成的时候,释放线程池;若使用的线程池的keepAliveTime为0,需要手动修改,因为不允许keepAliveTime为0的线程池,调用此方法;
3、invokeAny(Call<T>)
:此方法是一个同步方法,会阻塞调用线程;若其中有一个任务返回了,则其它的任务取消,不会继续执行; 此方法也存在超时设置重构方法;防止线程一直等待;无法结束。
6|0Future
6|1Future API
1、get()
:此方法是阻塞的,但是抛出了InterruptedException,所以是可以被打断的;使用interrupt()
进行打断的时候,打断的是调用get()的线程,让当前线程不再阻塞的等待获取数据;并不是真正执行任务的那个线程。
2、get(TimeOut)
:若是获取数据超时了,但是任务还是依旧执行,只是不再等待任务的返回值。
3、isDone()
:执行任务期间不管是否执行成功了,还是执行失败了(抛出异常)。只要结束,isDone()就会返回true。
4、boolean cancel(boolean mayInterruptIfRunning)
:取消任务。
返回false的情况:1.任务已经执行完成了,是无法被取消的。2.之前已经被cancel过
输出:
true
true
true
根据例子我们可以看到,cancel虽然取消了任务,但是任务任然在执行,这是为什么呢?
其实我们如果查看FutureTask的源码就会发现cancel只不过是调用了Thread的interrupt方法,而interrupt只能是停掉线程中有sleep,wait,join逻辑的线程,抛出一个InterruptException。这样看来FutureTask的cancel方法并不能停掉一切正在执行的异步任务。但是这里我们有一个妥协的做法就是在判断条件中加!Thread.currentThread().isInterrupted()这个判断即可.
改进代码1
输出:
true
true
true
1111111
可以看到任务是真正被终止了。
还有一个场景
在上面改进代码的第一步,第一行代码是个IO操作,假设耗时非常长,那就根本没有机会判断while条件。此时如果cancel,一样不会真正的终止任务的执行。
改进代码2
控制台输出:
true
true
trueProcess finished with exit code 0
可以看到直接结束了,思想就是将线程设置为守护线程,一旦主线程执行完,守护线程无论在干什么都会马上结束。所以后面的System.out.println("1111111");
都没有打印
6|2已经被cancel的任务,是否还能拿到结果?
输出:
true
true
true
1111111
java.util.concurrent.CancellationException
at java.util.concurrent.FutureTask.report(FutureTask.java:121)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at Future.FutureExample1.testCance2(FutureExample1.java:63)
at Future.FutureExample1.main(FutureExample1.java:19)
输出了111111,说明程序已经走到了return那一行,但是可以看到拿不到了爆出了异常。
6|3Future的缺陷以及解决方案
1、缺陷一:使用Future可以保证任务的异步执行;但是,只要去获取任务的结果,就会导致程序的阻塞;从而,从异步再次变为了同步。
2、缺陷二:假设批量执行一些异步任务,大部分任务都是几秒完成的,有少许任务是几个小时才完成。那你get()的时候,万一拿到了几个小时执行的任务,就会一直阻塞,导致几秒完成的任务拿不到结果。
3、像netty会有回调的callback
缺陷代码
JDK7解决方案
JDK8解决方案
CompletionService:具体见下面
7|0CompletionService
7|1简介
-
CompletionService的实现目标是任务先完成可优先获取到,即结果按照完成先后顺序排序。
-
ExecutorCompletionService类是常用的CompletionService实现类,该类只有三个成员变量:
- 可以看到ExecutorCompletionService主要是增强executor线程池的。
- Task包装后被塞入completionQueue,当Task结束,其Future就可以从completionQueue中获取到。
执行流程:

7|2阻塞和非阻塞获取
阻塞获取
take方法回使调用者阻塞,可以保证一定会有Future取出
非阻塞获取
poll方法会去查看是否有任务完成,有则取出;没有,就会返回一个null
7|3代码解决Future缺陷
结果:
稍微改一下就可以打印出来了
结果:
7|4按完成顺序获取结果验证
控制台输出:
8|0CompleableFuture(重要,很常用)
8|1为什么会出现CompletableFuture?
1、使用Future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isDone()是否为true,这两种方法都不是很好,因为主线程也会被迫等待。
2、从Java 8开始引入了CompletableFuture,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。
优点:
- 可以利用结果进行级联的执行
- 支持callback会自动回调给调用者
- 执行一批任务时,可以按照任务执行的顺序,获得结果
- 可以并行的获取结果,只拿最先获取的结果级联的执行
8|2简介及注意点
1、CompletableFuture相当于是Future和ExecutorService的结合体,CompleableFuture依然是对Executor的封装,看构造函数的源码,可以知道一般情况下会创建一个ForkJoinPool,同时ThreadFactory会设置为守护线程。这就意味着:一旦主线程结束,线程池就会关闭。
。可能导致回调函数还未执行, 便停止了。
如下:
控制台输出:
Done
2、可以改为此方法runAsync(Runnable, Executors), 让线程池去去管理线程. 不会跟随调用线程消失; 但是, 需要注意关闭线程池.
控制台输出:
starting
All finished!
end!
Finished!Process finished with exit code 0
8|3构造CompleableFuture
创建CompleableFuture
不建议使用构造方法,而是使用静态的工厂方法构建。
allOf(CompletableFuture<?>... cfs)
:这个方法会返回一个全新的CompletableFuture,传递进去的所有CompletableFuture执行完才算是执行完成。anyOf(CompletableFuture<?>... cfs)
:这个方法会返回一个全新的CompletableFuture,只要传递进去的有一个CompletableFuture执行完,就算是执行完成completedFuture(U value)
:可以假设一个执行出了一个结果,进行下面的级联操作。runAsync
:异步的执行Runnable,没有返回值。supplyAsync
:异步的执行Supplier实例,会有返回值。
runAsync
特点就是没有返回值,并且参数是Runnable
。比一般的提交一个Runnable相比,可以更加灵活点使用,级联、并联等操作
举例:
结果:
supplyAsync
需要给supplyAsync
提供一个Supplier
举例:
结果:
anyOf
举例:
结果:
这个例子中,前两个CompletableFuture
都睡了两秒,所以执行最快的肯定是第三个,从结果中也得到了验证。
需要注意一点,虽然是异步的从一个地方取值,但是其他任务依然会执行完成,而并非不再执行了。
allOf
结果:
8|4组合方法
组合两个任务,同时处理两个结果
举例:
结果:
分析
- 可以看出是两个任务组合,然后同时将两个结果一起处理
组合两个任务,任务完成后做的操作
当两个任务任意一个执行完成后,执行一个操作
举例
结果:
组合两个任务,处理后,返回一个结果
举例
结果:
第一个任务的输出是第二个任务的输入
相当于一次级联操作
举例:
结果:
8|5中转方法
有返回值
当执行完成时执行的操作
举例
结果
分析
-
当执行完成时执行的回调方法
-
该方法会接收执行的结果以及异常
-
回调完成会,会把原来任务执行的结果传递回去
-
whenCompleteAsync是异步的;whenComplete是同步的,会卡住主线程
-
需要传递一个
BiConsumer
接口,如下所示:
- T是执行的结果,U是执行时产生的异常
级联操作
举例
结果
分析
- 是一个级联操作,即拿着上个任务的结果,做下个任务,同时返回一个新的结果
处理结果的操作
举例
结果:
分析:
- 相比于
whenComplete
返回值可以自己处理,相当于一次级联 - 相比于
thenApply
,可以处理异常
无返回值
处理结果
举例
结果
分析
- 相当于一次级联,但是没有返回值
执行完全部任务
分析
- 相较
thenAccept
,不处理任务的执行结果
8|6终结方法
处理异常
结果:
立马获取结果
举例
结果
分析
- 如果结果完成返回结果,如果未完成,返回传入进去的值
判断结果是否完成,如果未完成则赋予结果
判断结果是否完成,如果未完成返回异常
后续获取结果会产生异常
9|0总结
thenAccept
()处理正常结果;exceptionally
()处理异常结果;thenApplyAsync
()用于串行化另一个CompletableFuture
;anyOf
()和allOf
()用于并行化多个CompletableFuture
。
10|0参考:
《Java并发编程之美》
https://www.cnblogs.com/yuandengta/p/12887361.html
__EOF__

本文链接:https://www.cnblogs.com/youthlql/articles/14027047.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix