万字长文!用代码实例带你彻底:精通CompletableFuture的使用

前言

创建线程的方式只有两种:继承Thread或者实现Runnable接口。 但是这两种方法都存在一个缺陷,没有返回值

Java 1.5 以后,可以通过向线程池提交一个Callable来获取一个包含返回值的Future对象

Future接口的局限性

当Future的线程进行了一个非常耗时的操作,那我们的主线程也就阻塞了。

当我们在简单业务上,可以使用Future的另一个重载方法get(long,TimeUnit)来设置超时时间,避免我们的主线程被无穷尽地阻塞。

单纯使用Future接口或者FutureTask类并不能很好地完成以下我们所需的业务

将两个异步计算合并为一个,这两个异步计算之间相互独立,同时第二个又依赖于第一个的结果
等待Future集合中的所有任务都完成。
仅等待Future集合种最快结束的任务完成,并返回它的结果。
通过编程方式完成一个Future任务的执行
当Future的完成时间完成时会收到通知,并能使用Future的计算结果进行下一步的的操作,不只是简单地阻塞等待操作的结果

什么是CompletableFuture

在Java 8中, 新增类: CompletableFuture,结合了Future的优点,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果

CompletableFuture被设计在Java中进行异步编程。主线程不用为了任务的完成而阻塞/等待,你可以用主线程去并行执行其他的任务。 使用这种并行方式,极大地提升了程序的表现。

CompletableFuture实现了Future接口,因此有异步执行返回结果的能力。
CompletableFuture实现了CompletionStage接口,该接口是Java8新增得一个接口,用于异步执行中的阶段处理,其大量用在Lambda表达式计算过程中,目前只有CompletableFuture一个实现类。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {

方法命名规则

带有Async后缀方法都是异步另外线程执行,没有就是复用之前任务的线程
带有Apply标识方法都是可以获取返回值+有返回值的
带有Accept标识方法都是可以获取返回值
带有run标识的方法不可以获取返回值和无返回值,只是运行

get方法和join方法

join:阻塞获取结果或抛出非受检异常。
get: 阻塞获取结果或抛出受检测异常,需要显示进行try...catch处理

不同线程池使用
默认线程池执行
/**
 * 默认线程池
 * 运行结果:
 * main.................start.....
 * main.................end......
 * 当前线程:ForkJoinPool.commonPool-worker-9
 * 运行结果:5
 */
@Test
public void defaultThread() {
    System.out.println("main.................start.....");
    CompletableFuture.runAsync(() -> {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        int i = 10 / 2;
        System.out.println("运行结果:" + i);
    });
    System.out.println("main.................end......");
}

默认使用 ForkJoinPool.commonPool(),commonPool是一个会被很多任务共享的线程池,commonPool 设计时的目标场景是运行 非阻塞的 CPU 密集型任务,为最大化利用 CPU,其线程数默认为 CPU 数量- 1

哪些地方使用了commonPool
CompletableFuture
Parallel Streams
为什么要引入commonPool
为了避免任何并行操作都引入一个线程池,最坏情况会导致在单个JVM上创建了太多的池线程,降低效率。
commonPool线程池是怎么创建和使用的
ForkJoinTask一定会运行在一个ForkJoinPool中,如果没有显式地交它提交到ForkJoinPool,会使用一个common池(全进程共享)来执行任务。

自定义线程池执行

自定义一个线程池

private ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>());

使用定义的线程池

/**
 * 自定义线程池
 * 运行结果:
 * main.................start.....
 * main.................end......
 * 当前线程:pool-1-thread-1
 * 运行结果:5
 */
@Test
public void myThread() {
    System.out.println("main.................start.....");
    CompletableFuture.runAsync(() -> {
        System.out.println("当前线程:" + Thread.currentThread().getName());
        int i = 10 / 2;
        System.out.println("运行结果:" + i);
    },executor);
    System.out.println("main.................end......");
}
开启一个异步
runAsync-无返回值

使用runAsync开启一个异步任务线程,该方法无结果返回,适合一些不需要结果的异步任务

/***
 * 无返回值
 *  runAsync
 *  结果:
 * main.................start.....
 * main.................end......
 * 当前线程:33
 * 运行结果:5
 */
@Test
public void runAsync() {
    System.out.println("main.................start.....");
    CompletableFuture.runAsync(() -> {
        System.out.println("当前线程:" + Thread.currentThread().getId());
        int i = 10 / 2;
        System.out.println("运行结果:" + i);
    }, executor);
    System.out.println("main.................end......");
}
supplyAsync-有返回值

使用completableFuture.get()方法获取结果,这时程序会阻塞到这里直到结果返回。

/**
 * 有返回值
 * supplyAsync
 * 结果:
 * main.................start.....
 * 当前线程:33
 * 运行结果:5
 * main.................end.....5
 */
@Test
public void supplyAsync() throws ExecutionException, InterruptedException {
    System.out.println("main.................start.....");
    CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("当前线程:" + Thread.currentThread().getId());
        int i = 10 / 2;
        System.out.println("运行结果:" + i);
        return i;
    }, executor);
    System.out.println("main.................end....." + completableFuture.get());
}

如果要超时就得往下执行,请使用completableFuture.get(long timeout, TimeUnit unit)方法。

线程串行化方法
带有Async后缀方法都是异步另外线程执行,没有就是复用之前任务的线程

thenApply-上面任务执行完执行+获取返回值+有返回值

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 223,924评论 6 521
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,776评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 170,954评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,576评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,589评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,105评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,480评论 3 427
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,457评论 0 278
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,998评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,026评论 3 343
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,174评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,812评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,502评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,979评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,101评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,674评论 3 380
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,204评论 2 363

推荐阅读更多精彩内容