FutureTask是future的实现类,它同时实现了两个接口:Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
因此我们可以:
- 调用FutureTask对象的run()方法执行
- 调用FutureTask对象的get()方法取结果
- 也可以将FutureTask对象作为callable的实现用线程池或者Thread类去执行。
FutureTask有两个很重要的属性分别是 state 、runner 。futureTask之所以可以支持cancel操作 就是因为这两个属性
其中 state为 枚举值:
NEW 新建 0
COMPLETING 执行中 1
NORMAL 正常 2
EXCEPTIONAL 异常 3
CANCELLED 取消 4
INTERRUPTING 中断中 5
INTERRUNPED 被中断 6
state的状态变化可以有四种方式
NEW->COMPLETING->NORMAL 正常完成的流程
NEW->COMPLETING->EXCEPTIONAL 出现异常的流程
NEW->CANCELED 被取消
NEW->INTERRUNPING->INTERRRUNPTED 被中断
我们用FutureTask改写一下前文的例子:
public class FutureDemo {
public static void main(String[] args) {
/* 定义生产者:用来做月饼的Callable */
final Callable callable = new Callable() {
public Integer call() throws Exception {
/*模拟耗时操作,需要5秒*/
Thread.sleep(5000);
/*返回一盒做好的月饼编号*/
return new Random().nextInt(10000);
}
};
/*开启线程B--消费者:获取月饼*/
Runnable runnable=new Runnable() {
public void run() {
try {
ExecutorService tPool = Executors.newSingleThreadExecutor();
System.out.println("老板,给我开始做月饼...");
/*启动线程A--生产者:运行耗时操作,生产月饼
*同时获得一张月饼券CookTicket*/
//final Future CookTicket = tPool.submit(callable);
FutureTask CookTicket = new FutureTask(callable);
tPool.submit(CookTicket);
//CookTicket.run();另一种调用方式
//new Thread(CookTicket).run();另一种调用方式
/*拿到月饼*/
System.out.println("5秒钟后用月饼券兑换到月饼,该盒月饼编号:"+CookTicket.get());
System.out.println("拿饼回家...");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
};
new Thread(runnable).start();
}
}
FutureTask有一个方法 void done()会在每个线程执行完成return结果时回调。
假现在需要实现每个线程完成任务执行后主动执行后续任务。
public class FutureDemo {
public static void main(String[] args) {
/* 定义生产者:用来做月饼的Callable */
final Callable callable = new Callable() {
public Integer call() throws Exception {
/*模拟耗时操作,需要5秒*/
Thread.sleep(5000);
/*返回一盒做好的月饼编号*/
return new Random().nextInt(10000);
}
};
/*开启线程B--消费者:获取月饼*/
Runnable runnable = new Runnable() {
public void run() {
System.out.println("老板,给我开始做月饼...");
/*用缓存线程池同时开多个线程工作*/
ExecutorService tPool = Executors.newCachedThreadPool();
/*启动线程A--生产者:运行耗时操作,三条生产线开始生产月饼*/
for(int i=0;i<3;i++){
FutureTask CookTicket = new FutureTask(callable){
/*当某个线程完成任务后,马上回调done函数,执行消费任务。*/
protected void done() {
super.done();
try {
/*get()是提取结果的方法*/
System.out.println("5秒钟后用月饼券兑换到月饼,该盒月饼编号:"+get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
};
tPool.submit(CookTicket);
//new Thread(CookTicket).run();
}
}
};
new Thread(runnable).start();
}
}
网上有篇例子,但是中间讲的不是很清楚。我重新梳理了一下。
在很多高并发的环境下,往往我们只需要某些任务只执行一次。这种使用情景FutureTask的特性恰能胜任。举一个例子,假设有一个带key的连接池,当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。对于这样的应用场景,通常采用的方法为使用一个Map对象来存储key和连接池对应的对应关系,典型的代码如下面所示:
private Map connectionPool = new HashMap();
private ReentrantLock lock = new ReentrantLock();
public Connection getConnection(String key){
try{
lock.lock();
if(connectionPool.containsKey(key)){
return connectionPool.get(key);
}
else{
//创建 Connection
Connection conn = createConnection();
connectionPool.put(key, conn);
return conn;
}
}
finally{
lock.unlock();
}
}
在上面的例子中,我们通过加锁确保高并发环境下的线程安全,也确保了connection只创建一次,然而却牺牲了性能。改用ConcurrentHash的情况下,几乎可以避免加锁的操作,性能大大提高。
private ConcurrentHashMap connectionPool = new ConcurrentHashMap();
public Connection getConnection(String key){
Connection conn=connectionPool.get(key);
if(conn!=null){
return conn;
}else {
conn=createConnection();
Connection return_conn = connectionPool.putIfAbsent(key,conn);
//根据putIfAbsent的返回值判断是否有线程抢先插入了
if(return_conn!=null){
conn=return_conn;
}
}
return conn;
}
//创建Connection
private Connection createConnection(){
return null;
}
但是在高并发的情况下有可能出现Connection被创建多次的现象。
为什么呢?因为创建Connection是一个耗时操作,假设多个线程涌入getConnection方法,都发现key对应的键不存在,于是所有涌入的线程都开始执行conn=createConnection()
,只不过最终只有一个线程能将connection插入到map里。但是这样以来,其它线程创建的的connection就没啥价值,浪费系统开销。
这时最需要解决的问题就是当key不存在时,创建Connection的动作(conn=createConnection();
)能放在connectionPool.putIfAbsent()之后执行,这正是FutureTask发挥作用的时机,基于ConcurrentHashMap和FutureTask的改造代码如下:
private ConcurrentHashMap>connectionPool =
new ConcurrentHashMap>();
public Connection getConnection(String key) throws Exception{
FutureTaskconnectionTask=connectionPool.get(key);
if(connectionTask!=null){
return connectionTask.get();
}
else{
Callable callable = new Callable(){
@Override
public Connection call() throws Exception {
// TODO Auto-generated method stub
return createConnection();
}
};
FutureTask newTask = new FutureTask(callable);
connectionTask = connectionPool.putIfAbsent(key, newTask);
if(connectionTask==null){
connectionTask = newTask;
connectionTask.run();
}
return connectionTask.get();
}
}
//创建Connection
private Connection createConnection(){
return null;
}