Java8并发编程(使用CompletableFuture)

java8异步处理

非阻塞IO/异步/并行

非阻塞指线程处理异步任务时,当异步任务获取到数据时使用回调函数处理数据,而不是CPU空闲等待数据返回后再处理。

使用场景

Restful服务往往部署在不同的物理机器上,通过Http协议进行调用,如果直接在主线程中进行IO操作,主线程将阻塞以等待请求完成。而Java原生多线程编程繁琐而且容易出错,线程池也无法完美解决主线程阻塞的问题,Future以同步代码风格编写异步调用,提供主线程无需阻塞的方法,对于微服务架构是一个很好的并发解决方案。

在实际的web项目中,传统的服务器后端,往往使用线程池做请求处理,每一个请求到来独占一个线程,当涉及io操作时,线程阻塞以等待IO完成,当IO耗时太长或者IO操作太频繁时,线程长时间无法释放,导致线程池可用线程不足,后续的请求被迫排队,服务器吞吐量受到严重影响。

另一个场景不太常见,一段代码中包含多个计算密集型任务(如超大矩阵计算)时,在同一个线程中线性执行使得整段代码运行时间过长,这时使用异步执行可以适当加速可并行执行的代码。

scala事件驱动

Scala的Future和Promise使用 ExecutionContext 管理异步计算任务,宏观上控制并行的粒度。你可以把它看成是一个线程池,它给任务分配新的线程或者在本线程下执行,虽然不推荐这种降级为线性执行的方式。

ExecutionContextForkJoinPool 实现线程管理,有特殊需求时需要特别配置 ForkJoinPool 的最大线程数。最大线程数设置一般参照CPU核数。虽然线程很轻量,但是当CPU线程数远小于活跃的线程数量时,线程切换频繁发生时的开销还是挺可观的。这时候并行粒度太小,并行太多反而会降低性能。

Future有两种状态

  • 未完成
  • 完成

完成状态有两种方式

  • 成功,带有返回值
  • 失败,带有异常

Java CompletableFuture Demo

github项目代码

使用前提:java8

Java8吸收了Scala的Future和其他基于事件驱动的异步调用框架,比如Netty的ChannelFuture等的优点,在原先的Future接口之上实现了CompletableFuture。

CompletableFuture 通过回调函数,实现非阻塞的IO的异步调用。

使用场景:Restful服务往往部署在不同的物理机器上,通过Http协议进行调用,如果直接在主线程中进行IO操作,主线程将阻塞以等待请求完成。而Java原生多线程编程繁琐而且容易出错,线程池也无法完美解决主线程阻塞的问题,Future以同步代码风格编写异步调用,提供主线程无需阻塞的方法,对于微服务架构是一个很好的并发解决方案。

全部Demo代码

Main.java

import java.io.IOException;

importjava.util.concurrent.CompletableFuture;

public class Main {
    public static void main(String[] args throws Exception {
        HttpTask task = new HttpTask();

        System.out.println("main threadstarts in thread id: "+Thread.currentThread().getId());

        CompletableFuturefutureNonBlocking =CompletableFuture.supplyAsync(()-> {
            try {
                return task.doHtt("https://guazi.com");
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedExceptione) {
                e.printStackTrace();
            }
            return "error";
        });
        CompletableFuture futureBlocking= CompletableFuture.supplyAsync(( -> {
            try {
                return task.doHtt("https://guazi.com");
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedExceptione) {
                e.printStackTrace();
            }
            return "error";
        });

        // 非阻塞
        System.out.printl("-----------------非阻塞位1----------------------\n");
        // future.thenAcceptAsync() 方法阻塞本线程,http请求成功后执行回调函数
        futureNonBlocking.thenAcceptAsyn(result -> System.out.printl("\nfrom non blocking future:\n+result+"\n"));
        System.out.printl("-----------------非阻塞位2----------------------\n");

        // 阻塞
        System.out.println("\nfromblocking future:\n+futureBlocking.get()+"\n");
        // future.get() 方法阻塞本线程,直http请求成功
        System.out.printl("-----------------阻塞位1----------------------\n");
        System.out.println("main threadends");
    }
}

HttpTask.java

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;

public class HttpTask {
    public String doHttp(String url)throws IOException,InterruptedException {
        long threadId =Thread.currentThread().getId();
        OkHttpClient client = newOkHttpClient();
        System.out.println("http iostarts in thread id: "+ threadId);

        Request request = newRequest.Builder()
                .url(url)
                .build();

        Response response = client.newCal(request).execute();
        String result = response.headers(.toString();

        // mock delay
        for (int i=1; i<=3; ++i){
            // 阻塞本线程1秒
            Thread.sleep(1000);
            System.out.println("delayed + i + " seconds in thread id:"+ threadId);
        }

        return "******** headers inthread id "+threadId+"***********\n"+resul+"***************** headers******************";
    }
}

输出结果

终端输出

本Demo中有三个线程执行,已通过线程ID标识,非阻塞线程(这里线程id为10)异步执行,结果通过回调函数处理。线程id为11的同样异步执行,但是Future接口的get()方法使主线程(id为1)阻塞以等待结果到达。

总结

线程10 → 异步 + 非阻塞IO
线程11 → 异步 + 阻塞IO

推荐使用 异步 + 非阻塞IO 的方式,这也是 Java1.8CompletableFutureJava1.5Future 的不同之处。

可以看到:

阻塞位置1 总是在 blocking futureget()方法之后执行。

使用姿势


不关心返回结果

这种情况毋庸置疑: 异步 + 非阻塞

必须获取到返回结果程序才能继续执行

这种情况下有两种选择

  1. 异步 + 阻塞 (前文已述) 说明一点,这种方式并不是完全失去了性能上的提升,因为在另一进程在未执行完成时,本进程不是必须在空等,而是可以做自己的数据处理,两个进程并发执行,虽然有时候快的那个要等待慢的那个,但是这比非异步也是有很大的性能提升的.如果实际场景中返回结果耗时太长,比如下载批量图片,请使用方法2
  2. 使用回调函数,类似javascript对异步的处理. 这种方法可以实现 异步+非阻塞的效果,但是复杂逻辑场景中会出现回调函数嵌套层数增加的混乱. trade-offs :-) :-)

参考链接

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

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,182评论 11 349
  • layout: posttitle: 《Java并发编程的艺术》笔记categories: Javaexcerpt...
    xiaogmail阅读 5,793评论 1 19
  • 爱情可以是低到尘埃里还要开出花来的卑微,也可以是自此天涯不相问的骄傲。爱情从来不需要理由,没有值不值得,爱了就是爱...
    宏红阅读 164评论 0 0
  • 他们,曾是朋友,曾是敌人,曾是路人。 曾经, 有的你嫉妒,有的你羡慕;有的嫉妒你,有的羡慕你; 还有的,刚说了,是...
    亲爱的乱七八糟阅读 241评论 0 0
  • 小时候找不写日记的借口 就是说自己懒或者无话可说 无事可写 现在N久没更新日志 找的借口也每每如此 其实每天发生的...
    柴禾妞_074f阅读 376评论 0 0