Java多线程/并发20、Future实现类:FutureTask

FutureTask是future的实现类,它同时实现了两个接口:Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

Java多线程/并发20、Future实现类:FutureTask_第1张图片

因此我们可以:
- 调用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在高并发环境下确保任务只执行一次

网上有篇例子,但是中间讲的不是很清楚。我重新梳理了一下。

在很多高并发的环境下,往往我们只需要某些任务只执行一次。这种使用情景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;  
}  

你可能感兴趣的:(Java多线程/并发)