在Java并发编程领域,FutureTask可以说是一个非常强大的利器,它通过实现RunnableFuture接口间接拥有了Runnable和Future接口的相关特性,既可以用于充当线程执行的任务(Runnable),也可以用于获取线程异步执行任务后返回的结果(Future);本文将通过剖析解读FutureTask底层相关的核心源码,并基于FutureTask自设计并实战一款“池容器”,即池化思想的设计和实战;
值得一提的是,Future或者FutureTask需要通过线程池才能发挥出实际的功效,因此在实际应用中它跟线程池又有着千丝万缕的联系,本文将从源码的角度进行剖析,通过解读FutureTask底层相关的核心源码,并基于FutureTask自设计并实战一款“池容器”,即池化思想的设计和实战;
(1)在上篇文章中想必各位观看老爷们已经基本知道了Future、FutureTask需要结合线程池来使用,看下方代码:
ArrayBlockingQueue queue=new ArrayBlockingQueue(2);
ExecutorService executor=new ThreadPoolExecutor(2,4,1, TimeUnit.MINUTES,queue);
FutureTask<Map<String,Object>> futureTask=new FutureTask<Map<String, Object>>(new ProductThread());
executor.execute(futureTask);
Map<String,Object> resMap=futureTask.get();
System.out.println("--子线程执行任务后得到的结果:"+resMap);
简短解说:在上述该代码中,ProductThread是一个实现了Callable接口的类,其中的call()方法便是真正要执行的任务代码逻辑,在此就不贴出来了(在上篇文章有源码);然后通过它构造一FutureTask,最后便是提交给线程池的execute()方法进行执行,执行完成之后,通过futureTask的get()方法获取线程异步执行后返回的结果;
而本文我们将基于这一段“解说”进行核心源码的剖析。
(2)首先是new FutureTask<Map<String, Object>>(new ProductThread()),即创建FutureTask,其底层源码如下所示:
//创建任务.等待被线程池中的线程执行
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
在上述源码中,值得一提的是,任务的运行状态变量state的设计个人觉得相当巧妙,采用volatile关键字进行定义,而volatile的作用想必有些小伙伴是比较熟悉的:
(1)保证线程之间可见性:即对 用volatile关键字修饰的变量 的可见性,即 “当一个线程修改了这个变量的值时,volatile 可以保证新值能立即同步到主内存,以及每次使用前立即从主内存刷新”
(2)禁止指令重排序:A.那么什么是“指令重排序”呢,我们写的代码最终都将转化为相应的指令交付给底层控制单元、计算单元执行,即CPU执行,重排序 则指的是CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理,这样做的弊端在于多核处理器下各处理器会发生乱序执行,从而导致我们所谓的 “并发安全”问题,而volatile关键词就禁止了这种现象,即通过在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行;
OK,回到线程待执行任务的运行状态变量state,其定义和取值如下所示(总共有6个状态的取值,其含义直接翻译过来就行了):
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
从官方的源码注释中可以看出 一个任务从创建到完毕,可能经历的状态变化为:
NEW -> COMPLETING -> NORMAL
NEW -> COMPLETING -> EXCEPTIONAL
NEW -> CANCELLED
NEW -> INTERRUPTING -> INTERRUPTED
(3)FutureTask任务定义好了之后,接下来就应该是交给线程准备执行了,即:executor.execute(futureTask) ,其底层源码如下所示:
public void execute(Runnable command){
//如果任务对象为null,抛异常
if (command == null)
throw new NullPointerException();
//先获取当前池中工作中的线程(活跃的、可用的线程数)
//如果当前池中的线程数小于核心线程数,就会调用addWorker检查运行状态和正在运行的
//线程数量
//通过return操作可以用于防止错误地添加线程、然后执行当前任务
//(因为随意的添加线程只会造成资源浪费)
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//否则当 池中的线程数大于核心线程数的时候 且 任务可以被加入任务队列时
//我们需要来个双重判断,判断是否真的需要添加一个新的线程来执行这个任务,
//因为可能已经存在这样的情况:线程执行完毕任务后的那一刻可能处于空闲状态,
//这个时候该线程就可以直接复用;
//否则直接创建一个新的线程来执行此任务
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//判断池中是否有可用的线程,如果没有 而且 也无法将当前任务加入到任务队列时
//则拒绝执行当前的任务(拒绝的策略取决于创建线程池时指定的策略)
if (!isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果不能创建新的线程去执行新任务的话,就拒绝当前任务
else if (!addWorker(command, false))
reject(command);
}
更多请见:http://www.mark-to-win.com/tutorial/51112.html