Java多線程:Executor,Executors,Future,Callable,Runnable等

平時工作中經(jīng)常碰到個各種多線程,有時候搞不清它們之間到底有什么區(qū)別,這次來個總體的總結(jié),主要是以下這些:
Executor,Executors,ExecutorService, CompletionServie,Future,Callable,Runnable,F(xiàn)utureTask

一、Runnable(interface)

public interface Runnable {
    public void run();
}

run()方法返回值為void類型,所以在執(zhí)行完任務(wù)之后無法返回任何結(jié)果。

二、Callable (interface)

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

與 Runnable 不同的是call()函數(shù)返回的類型就是傳遞進來的V類型,而且能夠拋出異常。一般情況下是配合ExecutorService來使用的

三、Future( interface)

Future是對于具體的Runnable或者Callable任務(wù)的執(zhí)行結(jié)果進行取消、查詢是否完成、獲取結(jié)果、設(shè)置結(jié)果操作。

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
  • cancel方法用來取消任務(wù),如果取消任務(wù)成功則返回true,如果取消任務(wù)失敗則返回false。參數(shù)mayInterruptIfRunning表示是否允許取消正在執(zhí)行卻沒有執(zhí)行完畢的任務(wù),如果設(shè)置true,則表示可以取消正在執(zhí)行過程中的任務(wù)。如果任務(wù)已經(jīng)完成,則無論mayInterruptIfRunning為true還是false,此方法肯定返回false,即如果取消已經(jīng)完成的任務(wù)會返回false;如果任務(wù)正在執(zhí)行,若mayInterruptIfRunning設(shè)置為true,則返回true,若mayInterruptIfRunning設(shè)置為false,則返回false;如果任務(wù)還沒有執(zhí)行,則無論mayInterruptIfRunning為true還是false,肯定返回true。
  • isCancelled方法表示任務(wù)是否被取消成功,如果在任務(wù)正常完成前被取消成功,則返回 true。
  • isDone方法表示任務(wù)是否已經(jīng)完成,若任務(wù)完成,則返回true;
  • get()方法用來獲取執(zhí)行結(jié)果,這個方法會產(chǎn)生阻塞,會一直等到任務(wù)執(zhí)行完畢才返回;
  • get(long timeout, TimeUnit unit)用來獲取執(zhí)行結(jié)果,如果在指定時間內(nèi),還沒獲取到結(jié)果,就直接返回null。

也就是說Future提供了三種功能:

  • 判斷任務(wù)是否完成;
  • 能夠中斷任務(wù);
  • 能夠獲取任務(wù)執(zhí)行結(jié)果。

四、FutureTask(Runnable, Future<V>的具體實現(xiàn))

public class FutureTask<V> implements RunnableFuture<V> 。。。

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

可以看出RunnableFuture繼承了Runnable接口和Future接口,而FutureTask實現(xiàn)了RunnableFuture接口。所以它既可以作為Runnable被線程執(zhí)行,又可以作為Future得到Callable的返回值,管理任務(wù)。
其中有兩個構(gòu)造方法

   //接受一個 Callable 參數(shù)
   public FutureTask(Callable<V> callable) {
       if (callable == null)
           throw new NullPointerException();
       this.callable = callable;
       this.state = NEW;       // ensure visibility of callable
   }

   //接受一個 Runnable ,利用 Executors.callable 將Runnable 轉(zhuǎn)換為Callable
   public FutureTask(Runnable runnable, V result) {
       this.callable = Executors.callable(runnable, result);
       this.state = NEW;       // ensure visibility of callable
   }

具體使用可以參考 AsyncTask 中的使用

五、Executor(interface)

在Executor框架中,使用執(zhí)行器(Exectuor)來管理Thread對象,從而簡化了并發(fā)編程。并發(fā)編程的一種編程方式把任務(wù)拆分為一系列的小任務(wù),即Runnable,然后將這些任務(wù)提交給一個Executor執(zhí)行,Executor.execute(Runnalbe) 。Executor在執(zhí)行時使用其內(nèi)部的線程池來完成操作。

Executor 接口中之定義了一個方法 execute(Runnable command),該方法接收一個 Runable 實例,它用來執(zhí)行一個任務(wù),任務(wù)即一個實現(xiàn)了 Runnable 接口的類。

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

為了避免調(diào)用 new Thread(new RunnableTask()).start()這樣的代碼我們可以

Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
...

Executor 并不是嚴格的要求一步執(zhí)行,我們可以簡單的直接在調(diào)用者線程執(zhí)行運行提交的任務(wù)

class DirectExecutor implements Executor {
  public void execute(Runnable r) {
    r.run();    // 在調(diào)用者線程執(zhí)行
}}

一般來說任務(wù)在非調(diào)用者的線程中執(zhí)行,比如產(chǎn)生一個新的線程

class ThreadPerTaskExecutor implements Executor {
  public void execute(Runnable r) {
    new Thread(r).start();   //新啟一個線程,在非調(diào)用者線程中執(zhí)行
}}

有很多Executor 的實現(xiàn)是為了實現(xiàn)任務(wù)的某種調(diào)度,比如 AsyncTask 中的串行任務(wù)隊列

class SerialExecutor implements Executor {
   final Queue tasks = new ArrayDeque<>();
   final Executor executor;
   Runnable active;

   SerialExecutor(Executor executor) {
     this.executor = executor;
   

   public synchronized void execute(final Runnable r) {
     tasks.add(new Runnable() {
       public void run() {
         try {
           r.run();
         } finally {
           scheduleNext();
         }
       }
     });
     if (active == null) {
       scheduleNext();
     }
   }

   protected synchronized void scheduleNext() {
     if ((active = tasks.poll()) != null) {
       executor.execute(active);
     }
   }
 }}

六、ExecutorService(interface,繼承自Executor)

ExecutorService 接口繼承自 Executor 接口,它提供了更豐富的實現(xiàn)多線程的方法,比如,ExecutorService 提供了關(guān)閉自己的方法,以及可為跟蹤一個或多個異步任務(wù)執(zhí)行狀況而生成 Future 的方法。 可以調(diào)用 ExecutorService 的 shutdown()方法來平滑地關(guān)閉 ExecutorService,調(diào)用該方法后,將導致 ExecutorService 停止接受任何新的任務(wù)且等待已經(jīng)提交的任務(wù)執(zhí)行完成(已經(jīng)提交的任務(wù)會分兩類:一類是已經(jīng)在執(zhí)行的,另一類是還沒有開始執(zhí)行的),當所有已經(jīng)提交的任務(wù)執(zhí)行完畢后將會關(guān)閉 ExecutorService。因此我們一般用該接口來實現(xiàn)和管理多線程。

execute(Runnable)  
submit(Runnable)  
submit(Callable)  
invokeAny()  
invokeAll() 

execute(Runnable)
方法 execute(Runnable) 接收一個java.lang.Runnable 對象作為參數(shù),并且以異步的方式執(zhí)行它。

ExecutorService executorService = Executors.newSingleThreadExecutor();  
executorService.execute(new Runnable() {  
    public void run() {  
        System.out.println("Asynchronous task");  
    }  
});        
executorService.shutdown();  

使用這種方式?jīng)]有辦法獲取執(zhí)行 Runnable 之后的結(jié)果,如果你希望獲取運行之后的返回值,就必須使用接收 Callable 參數(shù)的 execute() 方法。
submit(Runnable)
方法 submit(Runnable) 同樣接收一個Runnable 的實現(xiàn)作為參數(shù),但是會返回一個Future 對象。這個Future 對象可以用于判斷 Runnable 是否結(jié)束執(zhí)行。如下是一個ExecutorService 的 submit() 方法的例子:

Future future = executorService.submit(new Runnable() {  
    public void run() {  
        System.out.println("Asynchronous task");  
    }  
});  
//如果任務(wù)結(jié)束執(zhí)行則返回 null  
System.out.println("future.get()=" + future.get());  

submit(Callable)
方法 submit(Callable) 和方法 submit(Runnable) 比較類似,但是區(qū)別則在于它們接收不同的參數(shù)類型。Callable 的實例與 Runnable 的實例很類似,但是 Callable 的 call() 方法可以返回一個結(jié)果。方法 Runnable.run() 則不能返回結(jié)果。
Callable 的返回值可以從方法 submit(Callable) 返回的 Future 對象中獲取。如下是一個 ExecutorService Callable 的樣例:

Future future = executorService.submit(new Callable(){  
    public Object call() throws Exception {  
        System.out.println("Asynchronous Callable");  
        return "Callable Result";  
    }  
});  
System.out.println("future.get() = " + future.get());  

輸出結(jié)果

Asynchronous Callable  
future.get() = Callable Result  

inVokeAny()
方法 invokeAny() 接收一個包含 Callable 對象的集合作為參數(shù)。調(diào)用該方法不會返回 Future 對象,而是返回集合中某一個Callable 對象的結(jié)果,而且無法保證調(diào)用之后返回的結(jié)果是哪一個 Callable,只知道它是這些 Callable 中一個執(zhí)行結(jié)束的 Callable 對象。如果一個任務(wù)運行完畢或者拋出異常,方法會取消其它的 Callable 的執(zhí)行。
以下是一個樣例:

ExecutorService executorService = Executors.newSingleThreadExecutor();  
Set<Callable<String>> callables = new HashSet<Callable<String>>();  

callables.add(new Callable<String>() {  
    public String call() throws Exception {  
        return "Task 1";  
    }  
});  
callables.add(new Callable<String>() {  
    public String call() throws Exception {  
        return "Task 2";  
    }  
});  
callables.add(new Callable<String>() {  
    public String call() throws Exception {  
        return "Task 3";  
    }  
});  
String result = executorService.invokeAny(callables);  
System.out.println("result = " + result);  
executorService.shutdown();  

輸出結(jié)果:
以上樣例代碼會打印出在給定的集合中的某一個Callable 的返回結(jié)果。嘗試運行后發(fā)現(xiàn)每次結(jié)果都在改變。有時候返回結(jié)果是"Task 1",有時候是"Task 2",等等。
invokeAll()
方法 invokeAll() 會調(diào)用存在于參數(shù)集合中的所有 Callable 對象,并且返回一個包含 Future 對象的集合,你可以通過這個返回的集合來管理每個 Callable 的執(zhí)行結(jié)果。需要注意的是,任務(wù)有可能因為異常而導致運行結(jié)束,所以它可能并不是真的成功運行了。但是我們沒有辦法通過 Future 對象來了解到這個差異。
ExecutorService服務(wù)的關(guān)閉(shutdown() 或 shutdownNow())
當使用 ExecutorService 完畢之后,我們應(yīng)該關(guān)閉它,這樣才能保證線程不會繼續(xù)保持運行狀態(tài)。

舉例來說,如果你的程序通過 main() 方法啟動,并且主線程退出了你的程序,
如果還有一個活動的 ExecutorService 存在于程序中,那么程序?qū)^續(xù)保持運行狀態(tài)。存在于 ExecutorService 中的活動線程會阻止Java虛擬機關(guān)閉。

為了關(guān)閉在 ExecutorService 中的線程,需要調(diào)用** shutdown() **方法。但ExecutorService 并不會馬上關(guān)閉,而是不再接收新的任務(wù),一旦所有的線程結(jié)束執(zhí)行當前任務(wù),ExecutorServie 才會真的關(guān)閉。所有在調(diào)用 shutdown() 方法之前提交到 ExecutorService 的任務(wù)都會執(zhí)行。

如果你希望立即關(guān)閉 ExecutorService,你可以調(diào)用** shutdownNow() **方法。這個方法會嘗試馬上關(guān)閉所有正在執(zhí)行的任務(wù),并且跳過所有已經(jīng)提交但是還沒有運行的任務(wù)。但是對于正在執(zhí)行的任務(wù),是否能夠成功關(guān)閉它是無法保證的,有可能他們真的被關(guān)閉掉了,也有可能它會一直執(zhí)行到任務(wù)結(jié)束。這是一個最好的嘗試。

ExecutorService 接口在 java.util.concurrent 包中有如下實現(xiàn)類:
ThreadPoolExecutor
ScheduledThreadPoolExecutor

七、Executors(class)

Executors 提供了一系列工廠方法用于創(chuàng)先線程池,返回的線程池都實現(xiàn)了 ExecutorService 接口。

public static ExecutorService newFixedThreadPool(int nThreads)
// 創(chuàng)建固定數(shù)目線程的線程池。

public static ExecutorService newCachedThreadPool()
// 創(chuàng)建一個可緩存的線程池,調(diào)用execute將重用以前構(gòu)造的線程(如果線程可用)。
// 如果現(xiàn)有線程沒有可用的,則創(chuàng)建一個新線 程并添加到池中。
// 終止并從緩存中移除那些已有 60 秒鐘未被使用的線程。

public static ExecutorService newSingleThreadExecutor()
// 創(chuàng)建一個單線程化的Executor。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
// 創(chuàng)建一個支持定時及周期性的任務(wù)執(zhí)行的線程池,多數(shù)情況下可用來替代Timer類。

Executors的使用:

ExecutorService executorService = Executors.newFixedThreadPool(10);  
executorService.execute(new Runnable() {  
    public void run() {  
        System.out.println("Asynchronous task");  
    }  
});  
executorService.shutdown();  

該示例代碼首先使用 newFixedThreadPool() 工廠方法創(chuàng)建一個ExecutorService ,上述代碼創(chuàng)建了一個可以容納10個線程任務(wù)的線程池。其次,向 execute() 方法中傳遞一個異步的 Runnable 接口的實現(xiàn),這樣做會讓 ExecutorService 中的某個線程執(zhí)行這個Runnable 線程。

八、CompletionServie

為什么需要CompletionServie
如果你向Executor提交了一個批處理任務(wù),并且希望在它們完成后獲得結(jié)果。為此你可以保存與每個任務(wù)相關(guān)聯(lián)的Future,然后不斷地調(diào)用timeout為零的get,來檢驗Future是否完成。這樣做固然可以,但卻相當乏味。幸運的是,還有一個更好的方法:完成服務(wù)(Completion service)。

CompletionService整合了Executor和BlockingQueue的功能。你可以將Callable任務(wù)提交給它去執(zhí)行,然后使用類似于隊列中的take和poll方法,在結(jié)果完整可用時獲得這個結(jié)果,像一個打包的Future。ExecutorCompletionService是實現(xiàn)CompletionService接口的一個類,并將計算任務(wù)委托給一個Executor。
CompletionService與ExecutorService的對比使用

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        case1();
//        case2();
        case3();
    }


    /**
     * <一>
     * 1. 用List收集任務(wù)結(jié)果 (List記錄每個submit返回的Future)
     * 2. 循環(huán)查看結(jié)果, Future不一定完成, 如果沒有完成, 那么調(diào)用get會租塞
     * 3. 如果排在前面的任務(wù)沒有完成, 那么就會阻塞, 這樣后面已經(jīng)完成的任務(wù)就沒法獲得結(jié)果了, 導致了不必要的等待時間.
     *    更為嚴重的是: 第一個任務(wù)如果幾個小時或永遠完成不了, 而后面的任務(wù)幾秒鐘就完成了, 那么后面的任務(wù)的結(jié)果都將得不到處理
     *
     * 導致: 已完成的任務(wù)可能得不到及時處理
     */
    private static void case1() throws ExecutionException, InterruptedException {
        final Random random = new Random();
        ExecutorService service = Executors.newFixedThreadPool(10);
        List<Future<String>> taskResultHolder = new ArrayList<>();
        for(int i=0; i<50; i++) {
            //搜集任務(wù)結(jié)果
            taskResultHolder.add(service.submit(new Callable<String>() {
                public String call() throws Exception {
                    Thread.sleep(random.nextInt(5000));
                    return Thread.currentThread().getName();
                }
            }));
        }
        // 處理任務(wù)結(jié)果
        int count = 0;
        System.out.println("handle result begin");
        for(Future<String> future : taskResultHolder) {
            System.out.println(future.get());
            count++;
        }
        System.out.println("handle result end");
        System.out.println(count + " task done !");

        //關(guān)閉線程池
        service.shutdown();
    }

    /**
     * <二> 只對第一種情況進行的改進
     *      1. 查看任務(wù)是否完成, 如果完成, 就獲取任務(wù)的結(jié)果, 讓后重任務(wù)列表中刪除任務(wù).
     *      2. 如果任務(wù)未完成, 就跳過此任務(wù), 繼續(xù)查看下一個任務(wù)結(jié)果.
     *      3. 如果到了任務(wù)列表末端, 那么就從新回到任務(wù)列表開始, 然后繼續(xù)從第一步開始執(zhí)行
     *
     *      這樣就可以及時處理已完成任務(wù)的結(jié)果了
     */
    private static void case2() throws ExecutionException, InterruptedException {
        final Random random = new Random();
        ExecutorService service = Executors.newFixedThreadPool(10);
        List<Future<String>> results = new ArrayList<>();

        for(int i=0; i<50; i++) {
            Callable<String> task = new Callable<String>() {
                public String call() throws Exception {
                    Thread.sleep(random.nextInt(5000)); //模擬耗時操作
                    return Thread.currentThread().getName();
                }
            };
            Future<String> future = service.submit(task);
            results.add(future); // 搜集任務(wù)結(jié)果
        }

        int count = 0;
        //自旋, 獲取結(jié)果
        System.out.println("handle result begin");
        for(int i=0; i<results.size(); i++) {
            Future<String> taskHolder = results.get(i);

            if(taskHolder.isDone()) { //任務(wù)完成
                String result = taskHolder.get(); //獲取結(jié)果, 進行某些操作
                System.out.println("result: " + result);
                results.remove(taskHolder);
                i--;

                count++; //完成的任務(wù)的計數(shù)器
            }

            //回到列表開頭, 從新獲取結(jié)果
            if(i == results.size() - 1) i = -1;
        }
        System.out.println("handle result end");
        System.out.println(count + " task done !");

        //線程池使用完必須關(guān)閉
        service.shutdown();
    }


    /**
     * <三> 使用ExecutorCompletionService管理異步任務(wù)
     * 1. Java中的ExecutorCompletionService<V>本身有管理任務(wù)隊列的功能
     *    i. ExecutorCompletionService內(nèi)部維護列一個隊列, 用于管理已完成的任務(wù)
     *    ii. 內(nèi)部還維護列一個Executor, 可以執(zhí)行任務(wù)
     *
     * 2. ExecutorCompletionService內(nèi)部維護了一個BlockingQueue, 只有完成的任務(wù)才被加入到隊列中
     *
     * 3. 任務(wù)一完成就加入到內(nèi)置管理隊列中, 如果隊列中的數(shù)據(jù)為空時, 調(diào)用take()就會阻塞 (等待任務(wù)完成)
     *    i. 關(guān)于完成任務(wù)是如何加入到完成隊列中的, 請參考ExecutorCompletionService的內(nèi)部類QueueingFuture的done()方法
     *
     * 4. ExecutorCompletionService的take/poll方法是對BlockingQueue對應(yīng)的方法的封裝, 關(guān)于BlockingQueue的take/poll方法:
     *    i. take()方法, 如果隊列中有數(shù)據(jù), 就返回數(shù)據(jù), 否則就一直阻塞;
     *    ii. poll()方法: 如果有值就返回, 否則返回null
     *    iii. poll(long timeout, TimeUnit unit)方法: 如果有值就返回, 否則等待指定的時間; 如果時間到了如果有值, 就返回值, 否則返回null
     *
     * 解決了已完成任務(wù)得不到及時處理的問題
     */
    static void case3() throws InterruptedException, ExecutionException {
        Random random = new Random();

        ExecutorService service = Executors.newFixedThreadPool(10);
        ExecutorCompletionService<String> completionService = new ExecutorCompletionService<String>(service);

        for(int i=0; i<50; i++) {
            completionService.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    Thread.sleep(random.nextInt(5000));
                    return Thread.currentThread().getName();
                }
            });
        }

        int completionTask = 0;
        while(completionTask < 50) {
            //如果完成隊列中沒有數(shù)據(jù), 則阻塞; 否則返回隊列中的數(shù)據(jù)
            Future<String> resultHolder = completionService.take();
            System.out.println("result: " + resultHolder.get());
            completionTask++;
        }

        System.out.println(completionTask + " task done !");

        //ExecutorService使用完一定要關(guān)閉 (回收資源, 否則系統(tǒng)資源耗盡! .... 呵呵...)
        service.shutdown();
    }
}

九、參考

Java并發(fā)編程:Callable、Future和FutureTask
Java中的Runnable、Callable、Future、FutureTask的區(qū)別與示例
Java并發(fā)編程 - Executor,Executors,ExecutorService, CompletionServie,Future,Callable

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 導讀目錄 線程組(ThreadGroup) 線程池(Thread Pool) Fork/Join框架和Execut...
    ql2012jz閱讀 1,467評論 0 0
  • 先看幾個概念:線程:進程中負責程序執(zhí)行的執(zhí)行單元。一個進程中至少有一個線程。多線程:解決多任務(wù)同時執(zhí)行的需求,合理...
    yeying12321閱讀 570評論 0 0
  • 一.線程安全性 線程安全是建立在對于對象狀態(tài)訪問操作進行管理,特別是對共享的與可變的狀態(tài)的訪問 解釋下上面的話: ...
    黃大大吃不胖閱讀 865評論 0 3
  • 家有妹者也, 畢有積三世之德, 稍年長,久于大學。 習得言辭之精妙, 知漢之蜀黎。 固久之,言行皆在為其師所知也,...
    書生里即氣閱讀 344評論 1 1
  • 我有一個謬論,每每談及這套思想時,總是讓旁人不以為然,我想這得怪我太沉浸在自己的象牙塔裡。每個人聽取他人說話時,都...
    灣那閱讀 358評論 0 1