Java 線程池的submit的使用與分析.md

在Java5以後,通過Executor來啓動線程比用Thread的start()更好。在新特徵中,可以很容易控制線程的啓動、執行和關閉過程,還可以很容易使用線程池的特性。我們用的最多的execute方法,

0.異步返回值的場景

我們先不用線程池,來實現一個功能。加入週末了,幾個小夥伴要一塊做飯,假設做飯需要3個步驟,分別是:
1.打掃廚房衛生,準備廚具
2.買菜
3.炒菜

現在我們怎麼分工呢,現實中一般是這樣的,一部分小夥伴去買菜,一部分小夥伴打掃準備廚具,等這兩樣都做完之後才炒菜。也就是說第1步和第2步是同時進行的。
我們使用Java代碼來實現下:

public class Cook{
 public static void futureCook() throws ExecutionException, InterruptedException {
        Callable<Food> first = new Callable<Food>() {
            @Override
            public Food call() throws Exception {
                //do something
                return new Food();
            }

        };
        FutureTask<Food> task1 = new FutureTask<Food>(first);
        new Thread(task1).start();//先去做第一步

        Object task2 = null;
       //.... 第二步做了一些事

        // 第三步 需要用到第一步和第二部的結果
        if (!task1.isDone()) { // 第一步是否完成
            System.out.println("task1 is done");
        }
        Food foods = task1.get();//獲取第一步的結果

        cook(foods, task2);

    }
    private static void cook(Food foods, Object task2) {
    }
    static class Food{
        Object food;
    }
}

我們先用一個線程去買菜,執行第一步,同時執行第二步,第二步完成之後,我們試圖獲取第一步的結果,如果第一步沒有執行完,那麼就阻塞在這裏等待,直到完成。前兩步完成之後,我們就可以愉快的做飯了。

下面我們分析下使用到的技術

1.Callable

Callable是一個接口,它只有一個方法call,調用call可以異步執行,並返回結果。

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

2.FutureTask

在不用線程池的情況下,我們使用Callable來執行任務,使用Future來獲取任務結果,FutureTask實現了RunnableFuture接口:

public interface RunnableFuture<V> extends Runnable, Future<V> {  
    void run();  
} 

具體的步驟如下:
1.創建對象實現Callable接口,實現其call方法
2.使用Callable對象創建FutureTask對象
3.執行使用FutureTask創建線程執行
4.FutureTask獲取執行結果

 Callable<Object> c= new Callable<Object>() {
            @Override
            public Objectcall() throws Exception {
                return new Object();
            }
        };//1
        FutureTask<Object> task = new FutureTask<Object>(c);//2
        new Thread(task ).start();//3
        Object obj = task.get();//4

這是沒有用線程池ThreadPoolExecutor的情況下,異步返回值的最簡單實現。
下面我們看下在線程池中是如何操作的

1.ThreadPoolExecutor的submit的使用場景

上面的例子我們知道了,如果想要異步執行得到返回值,就需要使用Callable。在線程池ExecutorService接口中,我們如果異步執行得到返回值呢?

ExecutorService的接口定義:

ExecutorService{
void execute(Runnable command);
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
}

其中execute方法是繼承Executor的,只接受Runnable的參數,並且無返回值。
submit方法可以接收Callable和Runnable的參數,並且有返回值Future。具體的使用方法會在後面講

Future

Future就是對於具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。必要時可以通過get方法獲取執行結果,該方法會阻塞直到任務返回結果。

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方法用來取消任務。如果取消任務成功則返回true,如果取消任務失敗則返回false。參數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設置true,則表示可以取消正在執行過程中的任務。如果任務已經完成,則無論mayInterruptIfRunning爲true還是false,此方法肯定返回false.

  • isCancelled方法表示任務是否被取消成功,如果在任務正常完成前被取消成功,則返回 true。

  • isDone方法表示任務是否已經完成,若任務完成,則返回true;

  • get()方法用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回;

  • get(long timeout, TimeUnit unit)用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null。

Future是一個接口,真正完成功能的是FutureTask,它的基本用法我們上一節已經說明了,我們看下它的實現:

public class FutureTask{
  public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW; // ensure visibility of callable
    }
    public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

}

FutureTask內部定義了一個Callable變量。執行異步任務的時候,還是執行run方法。
1.判斷任務的狀態,如果任務不是初始狀態(NEW),則返回
2.result = c.call();調用Callable的call方法,保存返回值
3.如果執行c.call()拋出異常,則保存異常
4. set(result);保存執行結果,更新任務狀態

實踐

 public static void main(String[] args) {
        submitTest();
    }

private static ExecutorService executorService = null;
public static class CallRunner implements Callable<Counter>{

    @Override
    public Counter call() throws Exception {
        Counter counter = new Counter();
        for (int i=0;i<10000;i++){
            counter.i++;
        }
        System.out.println("CallRunner 執行結束");
        return counter;
    }
}

public static void submitTest(){
    executorService = createThreadPool();
    Future<Counter> future = executorService.submit(new CallRunner());
    executorService.execute(new T(2));
    try {
        Counter counter = future.get();
        System.out.println("count = "+counter.i);

    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }

}
private static ThreadPoolExecutor createThreadPool() {
    int corePoolSize = 3;
    int maximumPoolSize = 5;
    BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(20);
    ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 5, TimeUnit.MINUTES, workQueue);
    return executor;
}



    public static class Counter{
        int i = 0;
    }

    public static class T implements Runnable{

        @Override
        public void run() {
            System.out.println("Runnable 執行完畢");
        }
    }

上面的例子中我們使用 Future submit(Callable task)這個接口:

Future future = executorService.submit(new CallRunner());
在看下這3個接口,

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

Future submit(Runnable task, T result)怎麼使用呢?很簡單,我們定義一個對象result,然後當做第二個參數傳入submit方法中。在call中將結果設置到result中。這樣就不用Future.get()方法獲取結果了。當然這樣用的很少,具體看使用場景了。

Future<?> submit(Runnable task)這個接口有什麼用的?我們知道Runnable 的run方法是沒有返回值的,但是仍然返回Future。雖然不要發返回值,但是我們可以用Future獲取異步任務的狀態啊,是否完成、取消;get()方法阻塞直到任務執行完

2.execute 和 submit的區別

看了上面的例子,就容易理解execute 和 submit的區別了。
1.參數不同
execute只能接收Runable參數
submit接收Runable,Callable
2.返回值不同
execute沒有返回值,
submit返回Future對象,可以查詢任務的執行狀態和執行結果

3.異常處理不同
execute中的是Runnable接口的實現,execute中出現的異常,會被直接拋出來,或者只能在run方法中捕獲。
submit後出現的異常,可以在call方法中捕獲處理,也可以在外部處理。而你又希望外面的調用者能夠感知這些exception並做出及時的處理,那麼就需要用到submit,通過捕獲Future.get拋出的異常。如果不調用Future.get方法,則即便有錯誤異常,也不會拋出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章