1. 程式人生 > >深入學習理解(4):java :ExecutorService中submit和execute的區別

深入學習理解(4):java :ExecutorService中submit和execute的區別

在Java5之後,併發執行緒這塊發生了根本的變化,最重要的莫過於新的啟動、排程、管理執行緒的一大堆API了。在Java5以後,通過Executor來啟動執行緒比用Thread的start()更好。在新特徵中,可以很容易控制執行緒的啟動、執行和關閉過程,還可以很容易使用執行緒池的特性。
一、建立任務

任務就是一個實現了Runnable介面的類。
建立的時候實run方法即可。

二、執行任務

通過java.util.concurrent.ExecutorService介面物件來執行任務,該介面物件通過工具類java.util.concurrent.Executors的靜態方法來建立。

Executors此包中所定義的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 類的工廠和實用方法。

ExecutorService提供了管理終止的方法,以及可為跟蹤一個或多個非同步任務執行狀況而生成 Future 的方法。 可以關閉 ExecutorService,這將導致其停止接受新任務。關閉後,執行程式將最後終止,這時沒有任務在執行,也沒有任務在等待執行,並且無法提交新任務。
executorService.execute(new TestRunnable());

1、建立ExecutorService
通過工具類java.util.concurrent.Executors的靜態方法來建立。
Executors此包中所定義的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 類的工廠和實用方法。

比如,建立一個ExecutorService的例項,ExecutorService實際上是一個執行緒池的管理工具:
ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorService executorService = Executors.newFixedThreadPool(3);
ExecutorService executorService = Executors.newSingleThreadExecutor();

2、將任務新增到執行緒去執行
當將一個任務新增到執行緒池中的時候,執行緒池會為每個任務建立一個執行緒,該執行緒會在之後的某個時刻自動執行。
三、關閉執行服務物件


executorService.shutdown();
四例子


package concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* Created by IntelliJ IDEA.
*
* @author leizhimin 2008-11-25 14:28:59
*/
publicclass TestCachedThreadPool {
        publicstaticvoid main(String[] args) {
//                ExecutorService executorService = Executors.newCachedThreadPool();
                ExecutorService executorService = Executors.newFixedThreadPool(5);
//         ExecutorService executorService = Executors.newSingleThreadExecutor();

                for (int i = 0; i < 5; i++) {
                        executorService.execute(new TestRunnable());
                        System.out.println("************* a" + i + " *************");
                }
                executorService.shutdown();
        }
}

class TestRunnable implements Runnable {
        publicvoid run() {
                System.out.println(Thread.currentThread().getName() + "執行緒被呼叫了。");
                while (true) {
                        try {
                                Thread.sleep(5000);
                                System.out.println(Thread.currentThread().getName());
                        } catch (InterruptedException e) {
                                e.printStackTrace();
                        }
                }
        }
}
 
執行結果:
************* a0 *************
************* a1 *************
pool-1-thread-2執行緒被呼叫了。
************* a2 *************
pool-1-thread-3執行緒被呼叫了。
pool-1-thread-1執行緒被呼叫了。
************* a3 *************
************* a4 *************
pool-1-thread-4執行緒被呼叫了。
pool-1-thread-5執行緒被呼叫了。
pool-1-thread-2
pool-1-thread-1
pool-1-thread-3
pool-1-thread-5
pool-1-thread-4
pool-1-thread-2
pool-1-thread-1
pool-1-thread-3
pool-1-thread-5
pool-1-thread-4
     ......

五、獲取任務的執行的返回值
在Java5之後,任務分兩類:一類是實現了Runnable介面的類,一類是實現了Callable介面的類。兩者都可以被ExecutorService執行,但是Runnable任務沒有返回值,而Callable任務有返回值。並且Callable的call()方法只能通過ExecutorService的( task) 方法來執行,並且返回一個 ,是表示任務等待完成的 Future。

public interface Callable
返回結果並且可能丟擲異常的任務。實現者定義了一個不帶任何引數的叫做 call 的方法。
Callable 介面類似於,兩者都是為那些其例項可能被另一個執行緒執行的類設計的。但是 Runnable 不會返回結果,並且無法丟擲經過檢查的異常。
類包含一些從其他普通形式轉換成 Callable 類的實用方法。

Callable中的call()方法類似Runnable的run()方法,就是前者有返回值,後者沒有。

當將一個Callable的物件傳遞給ExecutorService的submit方法,則該call方法自動在一個執行緒上執行,並且會返回執行結果Future物件。

同樣,將Runnable的物件傳遞給ExecutorService的submit方法,則該run方法自動在一個執行緒上執行,並且會返回執行結果Future物件,但是在該Future物件上呼叫get方法,將返回null。

遺憾的是,在Java API文件中,這塊介紹的很糊塗,估計是翻譯人員還沒搞清楚的緣故吧。或者說是註釋不到位。下面看個例子:


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
* Callable介面測試
*
*
*/
publicclass CallableDemo {
        publicstaticvoid main(String[] args) {
                ExecutorService executorService = Executors.newCachedThreadPool();
                List<Future<String>> resultList = new ArrayList<Future<String>>();

                //建立10個任務並執行
                for (int i = 0; i < 10; i++) {
                        //使用ExecutorService執行Callable型別的任務,並將結果儲存在future變數中
                        Future<String> future = executorService.submit(new TaskWithResult(i));
                        //將任務執行結果儲存到List中
                        resultList.add(future);
                }

                //遍歷任務的結果
                for (Future<String> fs : resultList) {
                        try {
                                System.out.println(fs.get());     //列印各個執行緒(任務)執行的結果
                        } catch (InterruptedException e) {
                                e.printStackTrace();
                        } catch (ExecutionException e) {
                                e.printStackTrace();
                        } finally {
                                //啟動一次順序關閉,執行以前提交的任務,但不接受新任務。如果已經關閉,則呼叫沒有其他作用。
                                executorService.shutdown();
                        }
                }
        }
}


class TaskWithResult implements Callable<String> {
        privateint id;

        public TaskWithResult(int id) {
                this.id = id;
        }

        /**
         * 任務的具體過程,一旦任務傳給ExecutorService的submit方法,則該方法自動在一個執行緒上執行。
         *
         * @return
         * @throws Exception
         */
        public String call() throws Exception {
                System.out.println("call()方法被自動呼叫,幹活!!!             " + Thread.currentThread().getName());
                //一個模擬耗時的操作
                for (int i = 999999; i > 0; i--) ;
                return"call()方法被自動呼叫,任務的結果是:" + id + "    " + Thread.currentThread().getName();
        }
}
 
執行結果:
call()方法被自動呼叫,幹活!!!             pool-1-thread-1
call()方法被自動呼叫,幹活!!!             pool-1-thread-3
call()方法被自動呼叫,幹活!!!             pool-1-thread-4
call()方法被自動呼叫,幹活!!!             pool-1-thread-6
call()方法被自動呼叫,幹活!!!             pool-1-thread-2
call()方法被自動呼叫,幹活!!!             pool-1-thread-5
call()方法被自動呼叫,任務的結果是:0    pool-1-thread-1
call()方法被自動呼叫,任務的結果是:1    pool-1-thread-2
call()方法被自動呼叫,幹活!!!             pool-1-thread-2
call()方法被自動呼叫,幹活!!!             pool-1-thread-6
call()方法被自動呼叫,幹活!!!             pool-1-thread-4
call()方法被自動呼叫,任務的結果是:2    pool-1-thread-3
call()方法被自動呼叫,幹活!!!             pool-1-thread-3
call()方法被自動呼叫,任務的結果是:3    pool-1-thread-4
call()方法被自動呼叫,任務的結果是:4    pool-1-thread-5
call()方法被自動呼叫,任務的結果是:5    pool-1-thread-6
call()方法被自動呼叫,任務的結果是:6    pool-1-thread-2
call()方法被自動呼叫,任務的結果是:7    pool-1-thread-6
call()方法被自動呼叫,任務的結果是:8    pool-1-thread-4
call()方法被自動呼叫,任務的結果是:9    pool-1-thread-3

三個區別:
1、接收的引數不一樣

2、submit有返回值,而execute沒有

Method submit extends base method Executor.execute by creating and returning a Future that can be used to cancel execution and/or wait for completion.

用到返回值的例子,比如說我有很多個做validation的task,我希望所有的task執行完,然後每個task告訴我它的執行結果,是成功還是失敗,如果是失敗,原因是什麼。然後我就可以把所有失敗的原因綜合起來發給呼叫者。

個人覺得cancel execution這個用處不大,很少有需要去取消執行的。

而最大的用處應該是第二點。

3、submit方便Exception處理

There is a difference when looking at exception handling. If your tasks throws an exception and if it was submitted with execute this exception will go to the uncaught exception handler (when you don’t have provided one explicitly, the default one will just print the stack trace to System.err). If you submitted the task with submit any thrown exception, checked or not, is then part of the task’s return status. For a task that was submitted with submit and that terminates with an exception, the Future.get will rethrow this exception, wrapped in an ExecutionException.

意思就是如果你在你的task裡會丟擲checked或者unchecked exception,而你又希望外面的呼叫者能夠感知這些exception並做出及時的處理,那麼就需要用到submit,通過捕獲Future.get丟擲的異常。

比如說,我有很多更新各種資料的task,我希望如果其中一個task失敗,其它的task就不需要執行了。那我就需要catch Future.get丟擲的異常,然後終止其它task的執行,程式碼如下:


    import java.util.ArrayList;  
    import java.util.List;  
    import java.util.Random;  
    import java.util.concurrent.Callable;  
    import java.util.concurrent.ExecutionException;  
    import java.util.concurrent.ExecutorService;  
    import java.util.concurrent.Executors;  
    import java.util.concurrent.Future;  
      
    public class ExecutorServiceTest {  
        public static void main(String[] args) {  
            ExecutorService executorService = Executors.newCachedThreadPool();  
            List<Future<String>> resultList = new ArrayList<Future<String>>();  
      
            // 建立10個任務並執行  
            for (int i = 0; i < 10; i++) {  
                // 使用ExecutorService執行Callable型別的任務,並將結果儲存在future變數中  
                Future<String> future = executorService.submit(new TaskWithResult(i));  
                // 將任務執行結果儲存到List中  
                resultList.add(future);  
            }  
            executorService.shutdown();  
      
            // 遍歷任務的結果  
            for (Future<String> fs : resultList) {  
                try {  
                    System.out.println(fs.get()); // 列印各個執行緒(任務)執行的結果  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                } catch (ExecutionException e) {  
                    executorService.shutdownNow();  
                    e.printStackTrace();  
                    return;  
                }  
            }  
        }  
    }  
      
    class TaskWithResult implements Callable<String> {  
        private int id;  
      
        public TaskWithResult(int id) {  
            this.id = id;  
        }  
      
        /** 
         * 任務的具體過程,一旦任務傳給ExecutorService的submit方法,則該方法自動在一個執行緒上執行。 
         *  
         * @return 
         * @throws Exception 
         */  
        public String call() throws Exception {  
            System.out.println("call()方法被自動呼叫,幹活!!!             " + Thread.currentThread().getName());  
            if (new Random().nextBoolean())  
                throw new TaskException("Meet error in task." + Thread.currentThread().getName());  
            // 一個模擬耗時的操作  
            for (int i = 999999999; i > 0; i--)  
                ;  
            return "call()方法被自動呼叫,任務的結果是:" + id + "    " + Thread.currentThread().getName();  
        }  
    }  
      
    class TaskException extends Exception {  
        public TaskException(String message) {  
            super(message);  
        }  
    }  

 call()方法被自動呼叫,幹活!!!             pool-1-thread-1  
call()方法被自動呼叫,幹活!!!             pool-1-thread-2  
call()方法被自動呼叫,幹活!!!             pool-1-thread-3  
call()方法被自動呼叫,幹活!!!             pool-1-thread-5  
call()方法被自動呼叫,幹活!!!             pool-1-thread-7  
call()方法被自動呼叫,幹活!!!             pool-1-thread-4  
call()方法被自動呼叫,幹活!!!             pool-1-thread-6  
call()方法被自動呼叫,幹活!!!             pool-1-thread-7  
call()方法被自動呼叫,幹活!!!             pool-1-thread-5  
call()方法被自動呼叫,幹活!!!             pool-1-thread-8  
call()方法被自動呼叫,任務的結果是:0    pool-1-thread-1  
call()方法被自動呼叫,任務的結果是:1    pool-1-thread-2