Java多線程-線程池ThreadPoolExecutor的submit返回值Future

原文地址 http://blog.csdn.net/qq_25806863/article/details/71214033

一般使用線程池執(zhí)行任務(wù)都是調(diào)用的execute方法,這個方法定義在Executor接口中:

public interface Executor {
    void execute(Runnable command);
}

這個方法是沒有返回值的,而且只接受Runnable。

那么像得到線程的返回值怎嘛辦呢?

在ExecutorService接口中能找到這個方法:

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

這個方法接收兩種參數(shù),Callable和Runnable。返回值是Future。

下面具體看一下這些是什么東西。

Callable和Runnable

先看一下兩個接口的定義:

  • Callable

    public interface Callable<V> {
        V call() throws Exception;
    }
    
  • Runnable

    interface Runnable {
        public abstract void run();
    }
    

和明顯能看到區(qū)別:

  1. Callable能接受一個泛型,然后在call方法中返回一個這個類型的值。而Runnable的run方法沒有返回值
  2. Callable的call方法可以拋出異常,而Runnable的run方法不會拋出異常。

Future

返回值Future也是一個接口,通過他可以獲得任務(wù)執(zhí)行的返回值。

定義如下:

public interface Future<V> {
    boolean cancel(boolean var1);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long var1, TimeUnit var3) throws InterruptedException, ExecutionException, TimeoutException;
}

其中的get方法獲取的就是返回值。

來個例子

submit(Callable<T> task)

public class Main {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        //創(chuàng)建一個Callable,3秒后返回String類型
        Callable myCallable = new Callable() {
            @Override
            public String call() throws Exception {
                Thread.sleep(3000);
                System.out.println("calld方法執(zhí)行了");
                return "call方法返回值";
            }
        };
        System.out.println("提交任務(wù)之前 "+getStringDate());
        Future future = executor.submit(myCallable);
        System.out.println("提交任務(wù)之后,獲取結(jié)果之前 "+getStringDate());
        System.out.println("獲取返回值: "+future.get());
        System.out.println("獲取到結(jié)果之后 "+getStringDate());
    }
    public static String getStringDate() {
        Date currentTime = new Date();
        SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
        String dateString = formatter.format(currentTime);
        return dateString;
    }
}

通過executor.submit提交一個Callable,返回一個Future,然后通過這個Future的get方法取得返回值。

看一下輸出:

提交任務(wù)之前 12:13:01
提交任務(wù)之后,獲取結(jié)果之前 12:13:01
calld方法執(zhí)行了
獲取返回值: call方法返回值
獲取到結(jié)果之后 12:13:04

get()方法的阻塞性

通過上面的輸出可以看到,在調(diào)用submit提交任務(wù)之后,主線程本來是繼續(xù)運行了。但是運行到future.get()的時候就阻塞住了,一直等到任務(wù)執(zhí)行完畢,拿到了返回的返回值,主線程才會繼續(xù)運行。

這里注意一下,他的阻塞性是因為調(diào)用get()方法時,任務(wù)還沒有執(zhí)行完,所以會一直等到任務(wù)完成,形成了阻塞。

任務(wù)是在調(diào)用submit方法時就開始執(zhí)行了,如果在調(diào)用get()方法時,任務(wù)已經(jīng)執(zhí)行完畢,那么就不會造成阻塞。

下面在調(diào)用方法前先睡4秒,這時就能馬上得到返回值。

System.out.println("提交任務(wù)之前 "+getStringDate());
Future future = executor.submit(myCallable);
System.out.println("提交任務(wù)之后 "+getStringDate());
Thread.sleep(4000);
System.out.println("已經(jīng)睡了4秒,開始獲取結(jié)果 "+getStringDate());
System.out.println("獲取返回值: "+future.get());
System.out.println("獲取到結(jié)果之后 "+getStringDate());
提交任務(wù)之前 12:36:04
提交任務(wù)之后 12:36:04
calld方法執(zhí)行了
已經(jīng)睡了4秒,開始獲取結(jié)果 12:36:08
獲取返回值: call方法返回值
獲取到結(jié)果之后 12:36:08

可以看到嗎,因為睡了4秒,任務(wù)已經(jīng)執(zhí)行完畢,所以get方法立馬就得到了結(jié)果。

同樣的原因,submit兩個任務(wù)時,總阻塞時間是最長的那個。

例如,有兩個任務(wù),一個3秒,一個5秒。

Callable myCallable = new Callable() {
    @Override
    public String call() throws Exception {
        Thread.sleep(5000);
        System.out.println("calld方法執(zhí)行了");
        return "call方法返回值";
    }
};
Callable myCallable2 = new Callable() {
    @Override
    public String call() throws Exception {
        Thread.sleep(3000);
        System.out.println("calld2方法執(zhí)行了");
        return "call2方法返回值";
    }
};
System.out.println("提交任務(wù)之前 "+getStringDate());
        Future future = executor.submit(myCallable);
        Future future2 = executor.submit(myCallable2);
        System.out.println("提交任務(wù)之后 "+getStringDate());
        System.out.println("開始獲取第一個返回值 "+getStringDate());
        System.out.println("獲取返回值: "+future.get());
        System.out.println("獲取第一個返回值結(jié)束,開始獲取第二個返回值 "+getStringDate());
        System.out.println("獲取返回值2: "+future2.get());
        System.out.println("獲取第二個返回值結(jié)束 "+getStringDate());

輸出

提交任務(wù)之前 14:14:47
提交任務(wù)之后 14:14:48
開始獲取第一個返回值 14:14:48
calld2方法執(zhí)行了
calld方法執(zhí)行了
獲取返回值: call方法返回值
獲取第一個返回值結(jié)束,開始獲取第二個返回值 14:14:53
獲取返回值2: call2方法返回值
獲取第二個返回值結(jié)束 14:14:53

獲取第一個結(jié)果阻塞了5秒,所以獲取第二個結(jié)果立馬就得到了。

submit(Runnable task)

因為Runnable是沒有返回值的,所以如果submit一個Runnable的話,get得到的為null:

Runnable myRunnable = new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + " run time: " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
};

Future future = executor.submit(myRunnable);
        System.out.println("獲取的返回值: "+future.get());

輸出為:

pool-1-thread-1 run time: 1493966762524
獲取的返回值: null

submit(Runnable task, T result)

雖然submit傳入Runnable不能直接返回內(nèi)容,但是可以通過submit(Runnable task, T result)傳入一個載體,通過這個載體獲取返回值。這個其實不能算返回值了,是交給線程處理一下。

先新建一個載體類Data:

public static class Data {
    String name;
    String sex;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

然后在Runnable的構(gòu)造方法中傳入:

static class MyThread implements Runnable {
    Data data;

    public MyThread(Data name) {
        this.data = name;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println("線程  執(zhí)行:");
            data.setName("新名字");
            data.setSex("新性別");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

然后調(diào)用:

Data data = new Data();
Future<Data> future = executor.submit(new MyThread(data), data);
System.out.println("返回的結(jié)果  name: " + future.get().getName()+", sex: "+future.get().getSex());
System.out.println("原來的Data  name: " + data.getName()+", sex: "+data.getSex());

輸出:

線程  執(zhí)行:
返回的結(jié)果  name: 新名字, sex: 新性別
原來的Data  name: 新名字, sex: 新性別

發(fā)現(xiàn)原來的data也變了。

get(long var1, TimeUnit var3)

前面都是用的get()方法獲取返回值,那么因為這個方法是阻塞的,有時需要等很久。所以有時候需要設(shè)置超時時間。

get(long var1, TimeUnit var3)這個方法就是設(shè)置等待時間的。

如下面的任務(wù)需要5秒才能返回結(jié)果:

Callable myCallable = new Callable() {
    @Override
    public String call() throws Exception {
        Thread.sleep(5000);
        return "我是結(jié)果";
    }
};

使用get:

Future future1 = executor.submit(myCallable);
System.out.println("開始拿結(jié)果 "+getStringDate());
System.out.println("返回的結(jié)果是: "+future1.get()+ " "+getStringDate());
System.out.println("結(jié)束拿結(jié)果 "+getStringDate());

輸出是:

開始拿結(jié)果 16:00:43
返回的結(jié)果是: 我是結(jié)果 16:00:48
結(jié)束拿結(jié)果 16:00:48

現(xiàn)在要求最多等3秒,拿不到返回值就不要了,所以用get(long var1, TimeUnit var3)這個方法

方法的第一個參數(shù)是長整形數(shù)字,第二個參數(shù)是單位,跟線程池ThreadPoolExecutor的構(gòu)造方法里一樣的。

Future future1 = executor.submit(myCallable);
System.out.println("開始拿結(jié)果 "+getStringDate());
try {
    System.out.println("返回的結(jié)果是: "+future1.get(3, TimeUnit.SECONDS)+ " "+getStringDate());
} catch (TimeoutException e) {
    e.printStackTrace();
    System.out.println("超時了 "+getStringDate());
}
System.out.println("結(jié)束拿結(jié)果 "+getStringDate());

然后輸出是

過了三秒就拋出超時異常了,主線程繼續(xù)運行,不會再繼續(xù)阻塞。

異常

使用submit方法還有一個特點就是,他的異常可以在主線程中catch到。

而使用execute方法執(zhí)行任務(wù)是捕捉不到異常的。

用下面這個Runnable來說,這個 里面一定會拋出一個異常

Runnable myRunnable = new Runnable() {
    @Override
    public void run() {
        executor.execute(null);
    }
};

使用execute

這里如果捕捉到異常,只打印一行異常信息。

try {
            executor.execute(myRunnable);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("抓到異常 "+e.getMessage());
        }

輸出

并沒有出現(xiàn)抓到異常哪行日志。而且這個異常輸出是在線程pool-1-thread-1中,并不是在主線程中。說明主線程的catch不能捕捉到這個異常。

使用submit

try {
           Future future1= executor.submit(myCallable);
            future1.get();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("抓到異常 "+e.getMessage());
        }

輸出

這個就能抓到異常了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容