文章目录
- 1、Future接口
- 2、FutureTask结合线程池提升性能
- 3、Future的缺点一:get导致阻塞
- 4、Future的缺点二:轮询耗费CPU
- 5、其余场景
相关文章: 【Callable与FutureTask】
1、Future接口
Future接口,定义了操作异步任务执行的一些方法,如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务是否执行完毕等,其最常用的实现类就是FutureTask,以下整理的Future的优缺点,也即FutureTask这个实现类的优缺点。
当主线程需要执行一个很耗时的子任务时,可通过Future把这个任务放到异步线程去执行,主线程接着做它的事或者先行结束,等过一会儿再去获取子任务执行的结果即可,这就是Future接口做的事。关键点:
- 异步线程执行任务
- 有返回
2、FutureTask结合线程池提升性能
比如有三个任务,用一个线程来同步执行,那就挨个来:
public class FutureTaskDemo {
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
//任务1,用sleep模拟耗时
TimeUnit.MILLISECONDS.sleep(500);
//任务2
TimeUnit.MILLISECONDS.sleep(300);
//任务3
TimeUnit.MILLISECONDS.sleep(300);
long endTime = System.currentTimeMillis();
System.out.println("耗时:" + (endTime-startTime) + "ms");
}
}
将任务放到异步线程去执行
public static void main(String[] args) throws InterruptedException {
FutureTask<String> task1 = new FutureTask<>(() -> {
TimeUnit.MILLISECONDS.sleep(500);
return "task1 over";
});
Thread t1 = new Thread(task1, "t1");
t1.start();
//重复再new两个线程,传入两个FutureTask
//...
}
改进下,不要频繁创建和销毁线程,使用线程池
public class FutureTaskDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
long startTime = System.currentTimeMillis();
FutureTask<String> task1 = new FutureTask<>(() -> {
TimeUnit.MILLISECONDS.sleep(500);
return "task1 over";
});
//提交task1到线程池
threadPool.submit(task1);
FutureTask<String> task2 = new FutureTask<>(() -> {
TimeUnit.MILLISECONDS.sleep(300);
return "task2 over";
});
//提交task2到线程池
threadPool.submit(task2);
//任务3由main线程来
TimeUnit.MILLISECONDS.sleep(300);
long endTime = System.currentTimeMillis();
System.out.println("耗时:" + (endTime-startTime) + "ms");
threadPool.shutdown();
}
}
异步显然效率更高,但这不是重点
以上只是异步执行任务,并没有获取异步线程上任务的执行结果,统计结束时间前,再加入get:
...
//获取下执行结果
System.out.println(task1.get());
System.out.println(task2.get());
long endTime = System.currentTimeMillis();
System.out.println("耗时:" + (endTime-startTime) + "ms");
//...
3、Future的缺点一:get导致阻塞
试下get的阻塞情况:
public class FutureTask2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<String>(() ->{
System.out.println(Thread.currentThread().getName() + "start execute task");
TimeUnit.SECONDS.sleep(5);
return "task over";
});
new Thread(futureTask,"t1").start();
System.out.println(Thread.currentThread().getName() + "接着去忙其他的事了");
System.out.println(Thread.currentThread().getName() + "忙完了,准备获取futureTask的计算结果并处理,获取中...");
System.out.println(futureTask.get());
System.out.println(Thread.currentThread().getName() + "结算结果获取成功,全流程结束!");
}
}
- get方法一旦调用,讲究不见不散,非要等到结果才会离开,不管FutureTask是否计算完成,因此容易导致阻塞。
- 如果不想一直等,而是过时不候,那就
get(3,TimeUnit.SECOND)
public class FutureTask2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<String>(() ->{
System.out.println(Thread.currentThread().getName() + "start execute task");
TimeUnit.SECONDS.sleep(5);
return "task over";
});
new Thread(futureTask,"t1").start();
System.out.println(Thread.currentThread().getName() + "接着去忙其他的事了");
System.out.println(Thread.currentThread().getName() + "忙完了,准备获取futureTask的计算结果并处理,获取中...");
try {
System.out.println(futureTask.get(3,TimeUnit.SECONDS));
} catch (TimeoutException e) {
e.printStackTrace();
System.out.println("等待futureTask已到三秒,main过时不候了");
}
System.out.println(Thread.currentThread().getName() + "结算结果获取成功,全流程结束!");
}
}
这里别再抛了,get的TimeOut异常,异常出来就是提高程序健壮性的,这里mian再抛就到JVM,程序会终止运行在这一行,选择捕捉处理,打印点信息后完继续往下走:
4、Future的缺点二:轮询耗费CPU
既然get容易阻塞,那就先调isDone,判断futureTask是否执行完成,完成再get,没完成就休息一会儿再调isDone看是否完成,轮询查看执行结果。
public class FutureTask2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<String>(() -> {
System.out.println(Thread.currentThread().getName() + "start execute task");
TimeUnit.SECONDS.sleep(5);
return "task over";
});
new Thread(futureTask, "t1").start();
System.out.println(Thread.currentThread().getName() + "接着去忙其他的事了");
System.out.println(Thread.currentThread().getName() + "忙完了,准备获取futureTask的计算结果并处理,获取中...");
while (true) {
if (futureTask.isDone()) {
System.out.println(futureTask.get());
break;
} else {
//没有完成时,不要频繁查,休息几秒再继续isDone+get
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("futureTask线程正在处理中");
}
}
Sy
stem.out.println(Thread.currentThread().getName() + "结算结果获取成功,全流程结束!");
}
}
但while(true)+break,不停的循环,下面的程序也没被接着执行,只是输出更加友好了,我们可以看到正在等get,而不是直接扔个异常出来。而这样的轮询代价确是容易导致CPU空转,耗费更多无谓的CPU资源
5、其余场景
除了阻塞和轮询耗费CPU两个缺点外,以下场景,用Future也不能优雅的实现:
回调
上面用while(true)+break不停的问futureTask完成没,繁琐且耗无谓的CPU资源,考虑FutureTask完成任务后,自己来告诉我,即回调通知。
多个任务相互依赖或者组合
再比如当多个异步线程之间有依赖关系时:想将多个异步任务的计算结果组合起来,且后一个异步任务的计算结果需要用前一个异步任务的值
计算速度最快
当Future任务集合中,某个任务最快结束时,需要返回结果,即比赛的第一名
使用Future之前提供的那点API就囊中羞涩,处理起来不够优雅,这时候还是让CompletableFuture以声明式的方式优雅的处理这些需求,Future(FutureTask这个实现类)能完成的,CompletableFuture都能完成。 ⇒ Future接口新的实现类CompletableFuture出现