概述
Java 8系列:
Java 8系列之Lambda表达示
Java 8系列之StreamApi
Java 8系列之Collector
Java 8系列之Optional
Java 8系列之Future
一、基本概念
1.并发与并行
2.同步API与异步API
同步API:你调用了某个方法,调用方在被调用方运行的过程中会等待
,被调用方运行结束返回,调用方取得被调用方的返回值并继续运行。即使调用方和被调用方在不同的线程中运行,调用方还是需要等待被调用方结束运行,这就是阻塞式调用。
异步API:你调用了某个方法,被调用方直接返回
,或者至少在被调用方计算完成之前,将它剩余的计算任务交给另一个线程去做,该线程和调用方是异步的,这就是非阻塞式调用。
二、异步调用
1.java8之前实现异步调用的方式
首先写一个简单的例子来了解异步调用:使用Future以异步的方式执行一个耗时的操作,这种编程方式让我们的线程可以在线程池中以并发方式
调用另一个线程执行耗时操作的同时,去执行一些其他的任务。
//创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(6,6,2, TimeUnit.MINUTES,new LinkedBlockingDeque<Runnable>());
//任务(耗时就用sleep)
public Double task(Long start,String name) {
double taskResult = Math.random() * 1000;
try {
Thread.sleep((long)taskResult);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行"+name+",耗时:" + (System.nanoTime() - start) / 1_000_000 + " msecs");
return taskResult;
}
@Test
public void testFuture() throws Exception{
long start = System.nanoTime();
//1.向线程池提交异步任务1,使用future来接收线程结果
Future<Double> future = executor.submit(()->task(start,"任务1"));
//2.主流程执行任务2
Double task2Result = task(start,"任务2");
//3.future.get(),阻塞操作,直到获取异步操作的结果
Double task1Result = future.get();
System.out.println("全部计算完成,耗时:"+ (System.nanoTime() - start) / 1_000_000 + " msecs");
System.out.println("task1Result:"+ task1Result);
System.out.println("task2Result:"+ task2Result);
}
结果:
执行任务2,耗时:432 msecs
执行任务1,耗时:849 msecs
全部计算完成,耗时:849 msecs
task1Result:815.5350145603924
task2Result:398.9337349758296
2.使用java8的CompletableFuture来实现异步调用
@Test
public void testCompletableFuture() throws InterruptedException, ExecutionException {
long start = System.nanoTime();
//1.向线程池提交异步任务1,使用CompletableFuture来接收线程结果
CompletableFuture<Double> task2 = CompletableFuture.supplyAsync(() -> task(start, "任务1"),executor);
//2.主流程执行任务2
Double task1Result = task(start, "任务2");
//3.future.get(),阻塞操作,直到获取异步操作的结果
Double task2Result = task2.get();
System.out.println("全部计算完成,耗时:"+ (System.nanoTime() - start) / 1_000_000 + " msecs");
System.out.println("task1Result:"+ task1Result);
System.out.println("task2Result:"+task2Result );
}
结果:
执行任务2,耗时:597 msecs
执行任务1,耗时:1024 msecs
全部计算完成,耗时:1024 msecs
task1Result:564.4290801402495
task2Result:992.1778479980603
其中CompletableFuture类
提供了大量的工厂方法,使用这些方法能更容易地完成整个流程,还不用担心实现的细节。
如supplyAsync方法
接受一个生产者(Supplier)
作为参数,返回一个CompletableFuture
对象,该对象完成异步执行后会读取调用生产者方法的返回值。
如不指定线程池,生产者方法会交由ForkJoinPool池
中的某个执行线程(Executor)运行
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
也可使用supplyAsync方法的重载版本,第二个参数传递线程池
三、几种异步调用的时间对比(比较粗糙)
List<String> taskList = Arrays.asList("任务1",
"任务2",
"任务3",
"任务4",
"任务5",
"任务6");
//任务
public Long taskUpgrade(Long start,String name) {
long sum=0;
for (long i = 0; i <1000000000L ; i++) {
sum=sum+i;
}
//System.out.println("执行"+name+",耗时:" + (System.nanoTime() - start) / 1_000_000 + " msecs");
return sum;
}
/**
* 使用流顺序计算
* @param start
* @return
*/
public List<Long> getTaskResult(long start) {
return taskList.stream()
.map(taskName -> taskUpgrade(start,taskName))
.collect(toList());
}
/**
* 使用流并行计算
* @param start
* @return
*/
public List<Long> getTaskResultParallel(long start) {
return taskList.parallelStream()
.map(taskName -> taskUpgrade(start,taskName))
.collect(toList());
}
/**
* 异步运算:使用默认执行器
* @param start
* @return
*/
public List<Long> getTaskResultFuture(long start) {
List<CompletableFuture<Long>> futureList = taskList.stream()
.map(taskName -> CompletableFuture.supplyAsync(() -> taskUpgrade(start,taskName)))
.collect(toList());
//CompletableFuture 类中的 join 方法和 Future 接口中的 get 有相同的含义
return futureList.stream()
.map(CompletableFuture::join)
.collect(toList());
}
/**
* 异步运算:使用定制的执行器
* @param start
* @return
*/
public List<Long> getTaskResultFuture1(long start) {
List<CompletableFuture<Long>> futureList = taskList.stream()
.map(taskName -> CompletableFuture.supplyAsync(() -> taskUpgrade(start,taskName), executor))
.collect(toList());
//CompletableFuture 类中的 join 方法和 Future 接口中的 get 有相同的含义
return futureList.stream()
.map(CompletableFuture::join)
.collect(toList());
}
@Test
public void test3() {
long start = System.nanoTime();
getTaskResult(start);
System.out.println("使用流顺序计算:" + (System.nanoTime() - start) / 1_000_000 + " msecs");
start = System.nanoTime();
getTaskResultParallel(start);
System.out.println("使用流【并行】计算:" + (System.nanoTime() - start) / 1_000_000 + " msecs");
start = System.nanoTime();
getTaskResultFuture(start);
System.out.println("异步运算:使用【默认执行器】,【并发】:" + (System.nanoTime() - start) / 1_000_000 + " msecs");
start = System.nanoTime();
getTaskResultFuture1(start);
System.out.println("异步运算:使用【定制的执行器】,【并发】:" + (System.nanoTime() - start) / 1_000_000 + " msecs");
}
结果:
使用流顺序计算:1607 msecs
使用流【并行】计算:280 msecs
异步运算:使用【默认执行器】,【并发】:521 msecs
异步运算:使用【定制的执行器】,【并发】:284 msecs
并行
和并发
不相伯仲,究其原因都一样:它们内部采用的是同样的通用线程池,默认都使用固定数目的线程,具体线程数取决于Runtime.getRuntime().availableProcessors()
的返回值。
而CompletableFuture
具有一定的优势,因为它允许你对执行器( Executor )
进行配置,尤其是线程池的大小
两种并发
情况,结果不同
,是因为默认执行器
是使用的ForkJoinPool
做为线程池,默认线程数量等于运行计算机上的处理器数量
,小于我们自定义的线程池的线程数量6
四、几种异步调用的方式对比
模拟执行任务,并把任务结果放到list中
//创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(20,20,2, TimeUnit.MINUTES,new LinkedBlockingDeque<Runnable>());
List<String> taskList = Arrays.asList("任务1",
"任务2",
"任务3",
"任务4",
"任务5",
"任务6");
//任务
public Long task(Long start,String name) {
//Math.random() *
long sum=0;
for (long i = 0; i <1000000000L ; i++) {
sum=sum+i;
}
//System.out.println("执行"+name+",耗时:" + (System.nanoTime() - start) / 1_000_000 + " msecs");
return sum;
}
//任务,主要是为了执行countDownLatch.countDown();
public Long task(Long start,String name,CountDownLatch countDownLatch) {
//Math.random() *
long sum=0;
for (long i = 0; i <1000000000L ; i++) {
sum=sum+i;
}
countDownLatch.countDown();
//System.out.println("执行"+name+",耗时:" + (System.nanoTime() - start) / 1_000_000 + " msecs");
return sum;
}
/**
* 异常调用:使用Callable,返回值用Future接收,使用get()方法阻塞
* @throws Exception
*/
public void useCallable() throws Exception{
long start = System.nanoTime();
List<Long> list=new ArrayList<>();
List<Future<Long>> futureList = taskList
.stream()
.map(o -> executor.submit(() -> task(start, o)))
.collect(Collectors.toList());
for (Future<Long> f: futureList) {
list.add(f.get());
}
System.out.println("使用Future,耗时:"+ (System.nanoTime() - start) / 1_000_000 + " msecs");
//System.out.println("list:"+ list);
}
/**
* 异常调用,使用CountDownLatch,await()方法来阻塞
* @throws Exception
*/
public void useCountDownLatch() throws Exception{
long start = System.nanoTime();
List<Long> list=new ArrayList<>();
CountDownLatch countDownLatch = new CountDownLatch(6);
List<Future<Long>> futureList = taskList
.stream()
.map(o ->executor.submit(()->task(start,o,countDownLatch)))
.collect(Collectors.toList());
countDownLatch.await();
for (Future<Long> f: futureList) {
list.add(f.get());
}
System.out.println("使用CountDownLatch,耗时:"+ (System.nanoTime() - start) / 1_000_000 + " msecs");
//System.out.println("list:"+ list);
}
/**
* 异常调用,使用CompletableFuture,join()方法来阻塞
* @throws Exception
*/
public void useCompletableFuture() throws Exception{
long start = System.nanoTime();
List<Long> list=new ArrayList<>();
List<CompletableFuture<Long>> completableFutureList = taskList.stream()
.map(o -> CompletableFuture.supplyAsync(() -> task(start, o), executor))
.collect(Collectors.toList());
CompletableFuture<Void> allOf = CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[0]));
allOf.join();
for (CompletableFuture<Long> c: completableFutureList) {
list.add(c.get());
}
System.out.println("使用CompletableFuture,耗时:"+ (System.nanoTime() - start) / 1_000_000 + " msecs");
//System.out.println("list:"+ list);
}
@Test
public void test() throws Exception {
useCallable();
useCountDownLatch();
useCompletableFuture();
}
结果:
使用Future,耗时:433 msecs
使用CountDownLatch,耗时:417 msecs
使用CompletableFuture,耗时:287 msecs
结果不重要,只要展示下,多任务同时处理的几种机制
,代码实现上来说复杂度
差不多吧,方便以后项目中使用
最后
以上就是文艺板凳为你收集整理的Java 8系列之Future一、基本概念二、异步调用三、几种异步调用的时间对比(比较粗糙)四、几种异步调用的方式对比的全部内容,希望文章能够帮你解决Java 8系列之Future一、基本概念二、异步调用三、几种异步调用的时间对比(比较粗糙)四、几种异步调用的方式对比所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复