FutrueTask概念
FutureTask一个可取消的异步计算,FutureTask 实现了Future的基本方法,提供 start cancel 操作,可以查询计算是否已经完成,并且可以获取计算的结果。结果只可以在计算完成之后获取,get方法会阻塞当计算没有完成的时候,一旦计算已经完成,那么计算就不能再次启动或是取消。
一个FutureTask 可以用来包装一个 Callable 或是一个runnable对象。因为FurtureTask实现了Runnable方法,所以一个 FutureTask可以提交(submit)给一个Excutor执行(excution).
FutureTask使用场景
FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。
FutureTask执行多任务计算场景
利用FutureTask和ExecutorService,可以用多线程的方式提交计算任务,主线程继续执行其他任务,当主线程需要子线程的计算结果时,在异步获取子线程的执行结果。
1 /**
2 * Created by zhumiao on 2018/8/17.
3 */
4 public class FutureTest1 {
5 public static void main(String[] args) {
6 Task task = new Task();// 新建异步任务
7 FutureTask<Integer> future = new FutureTask<Integer>(task) {
8 // 异步任务执行完成,回调
9 @Override
10 protected void done() {
11 try {
12 System.out.println("future.done():" + get());
13 } catch (InterruptedException e) {
14 e.printStackTrace();
15 } catch (ExecutionException e) {
16 e.printStackTrace();
17 }
18 }
19 };
20 // 创建线程池(使用了预定义的配置)
21 ExecutorService executor = Executors.newCachedThreadPool();
22 executor.execute(future);
23
24 try {
25 Thread.sleep(1000);
26 } catch (InterruptedException e1) {
27 e1.printStackTrace();
28 }
29 // 可以取消异步任务
30 // future.cancel(true);
31 try {
32 // 阻塞,等待异步任务执行完毕-获取异步任务的返回值
33 System.out.println("future.get():" + future.get());
34 } catch (InterruptedException e) {
35 e.printStackTrace();
36 } catch (ExecutionException e) {
37 e.printStackTrace();
38 }
39 }
40 // 异步任务
41 static class Task implements Callable<Integer> {
42 // 返回异步任务的执行结果
43 @Override
44 public Integer call() throws Exception {
45 int i = 0;
46 for (; i < 10; i++) {
47 try {
48 System.out.println(Thread.currentThread().getName() + "_"
49 + i);
50 Thread.sleep(500);
51 } catch (InterruptedException e) {
52 e.printStackTrace();
53 }
54 }
55 return i;
56 }
57 }
58 }
FutureTask在高并发下确保任务只执行一次
在很多高并发的环境下,往往我们只需要某些任务只执行一次。这种使用情景FutureTask的特性恰能胜任。举一个例子,假设有一个带key的连接池,当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。对于这样的应用场景,通常采用的方法为使用一个Map对象来存储key和连接池对应的对应关系,典型的代码如下面所示:
1 private Map<String, Connection> connectionPool = new HashMap<String, Connection>();
2 private ReentrantLock lock = new ReentrantLock();
3
4 public Connection getConnection(String key){
5 try{
6 lock.lock();
7 if(connectionPool.containsKey(key)){
8 return connectionPool.get(key);
9 }
10 else{
11 //创建 Connection
12 Connection conn = createConnection();
13 connectionPool.put(key, conn);
14 return conn;
15 }
16 }
17 finally{
18 lock.unlock();
19 }
20 }
21
22 //创建Connection(根据业务需求,自定义Connection)
23 private Connection createConnection(){
24 return null;
25 }
在上面的例子中,我们通过加锁确保高并发环境下的线程安全,也确保了connection只创建一次,然而确牺牲了性能。改用ConcurrentHash的情况下,几乎可以避免加锁的操作,性能大大提高,但是在高并发的情况下有可能出现Connection被创建多次的现象。这时最需要解决的问题就是当key不存在时,创建Connection的动作能放在connectionPool之后执行,这正是FutureTask发挥作用的时机,基于ConcurrentHashMap和FutureTask的改造代码如下:
1 private ConcurrentHashMap<String,FutureTask<Connection>>connectionPool = new ConcurrentHashMap<String, FutureTask<Connection>>();
2
3 public Connection getConnection(String key) throws Exception{
4 FutureTask<Connection>connectionTask=connectionPool.get(key);
5 if(connectionTask!=null){
6 return connectionTask.get();
7 }
8 else{
9 Callable<Connection> callable = new Callable<Connection>(){
10 @Override
11 public Connection call() throws Exception {
12 // TODO Auto-generated method stub
13 return createConnection();
14 }
15 };
16 FutureTask<Connection>newTask = new FutureTask<Connection>(callable);
17 connectionTask = connectionPool.putIfAbsent(key, newTask);
18 if(connectionTask==null){
19 connectionTask = newTask;
20 connectionTask.run();
21 }
22 return connectionTask.get();
23 }
24 }
25
26 //创建Connection(根据业务需求,自定义Connection)
27 private Connection createConnection(){
28 return null;
29 }
经过这样的改造,可以避免由于并发带来的多次创建连接及锁的出现。