1. 程式人生 > >一心多用多執行緒-細談java執行緒池submit與execute的區別

一心多用多執行緒-細談java執行緒池submit與execute的區別

深夜學習,發現ThreadPoolExecutor裡面一個小知識點,故開熱點連wifi怒寫submit與execute方法的區別。

1.問題的來源

在看書的時候,涉及到java執行緒池問題的時候常常面臨這樣一個問題。當定義了一個Runnable物件想提交到執行緒池裡面總是會看到不同的提交方法,產生的尬題如下:

public class ThreadPoolDemo {
    public static class MyTask implements Runnable{
        @Override
        public void run() {
            System.out.println(System.currentTimeMillis()+":Thread ID:"
+Thread.currentThread().getId()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args){ MyTask task = new MyTask(); ExecutorService es = Executors.newCachedThreadPool(); for
(int i=0;i<10;i++){ es.submit(task);//問題出現在這裡! es.execute(task); } } } }

明明在看上一頁書的時候,向執行緒池提交任務的時候,用的是submit()方法,等到看下一頁的時候媽蛋,怎麼用成execute()了,這兩個搞什麼鬼,同一個功能難道有兩個方法可以呼叫?我不精陷入了深思,怒查java api文件。

2.java api文件對這兩個方法的描述

首先,記憶裡面對execute()方法是記憶比較深刻的,故查了一下該方法的api文件,發現資訊如下:

  • execute() 是在Executor介面中定義的,ThreadPoolExecutor繼承了AbstractExecutorService抽象類,該抽象類實現了ExecutorService介面(但並沒有覆蓋execute方法),而ExecutorService介面繼承了Executor介面。

簡而言之就是說ThreadPoolExecutor實現了execute()方法。然後我們來看看api文件對execute()方法是如何定義的:

execute public void execute(Runnable command)
在將來某個時間執行給定任務。可以在新執行緒中或者在現有池執行緒中執行該任務。如果無法將任務提交執行,或者因為此執行程式已關閉,或者因為已達到其容量,則該任務由當前 RejectedExecutionHandler處理。

引數: command - 要執行的任務。 丟擲: RejectedExecutionException -
如果無法接收要執行的任務,則由 RejectedExecutionHandler 決定是否丟擲
RejectedExecutionException NullPointerException - 如果命令為 null

看的是我一蒙一蒙的,主要是”在將來某個時間執行給定任務。”這一句讓我很費解,所以我決定再看看submit()方法是怎麼寫的。

  • submit方法是ExecutorService接口裡面定義的,具體的實現由AbstractExecutorService進行。

submit方法被過載了三次,分別對用三個不同的引數。對api摘錄如下:

submit public Future<?> submit(Runnable task)

提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future。該 Future 的 get 方法在成功 完成時將會返回null。

指定者: 介面 ExecutorService 中的 submit 引數: task - 要提交的任務 返回: 表示任務等待完成的 Future

submit public Future submit(Runnable task,T result) 提交一個
Runnable 任務用於執行,並返回一個表示該任務的 Future。該 Future 的 get 方法在成功完成時將會返回給定的結果。

指定者: 介面 ExecutorService 中的 submit 引數: task - 要提交的任務 result - 返回的結果
返回: 表示任務等待完成的 Future

submit public Future submit(Callable task)
提交一個返回值的任務用於執行,返回一個表示任務的未決結果的 Future。該 Future 的 get
方法在成功完成時將會返回該任務的結果。 如果想立即阻塞任務的等待,則可以使用 result =
exec.submit(aCallable).get(); 形式的構造。

注:Executors 類包括了一組方法,可以轉換某些其他常見的類似於閉包的物件,例如,將 PrivilegedAction 轉換為Callable 形式,這樣就可以提交它們了。

指定者: 介面 ExecutorService 中的 submit 引數: task - 要提交的任務 返回: 表示任務等待完成的Future

如上所示,第二個與第三個可以理解,不就是我記錄過的Future模式裡面的那一套東西嗎?就是說execute不支援Future這一套,而submit支援一套並可以返回一個Future給你到後面獲取結果的時候可以get一get。

但是看到第一個的時候我又矇蔽了,”提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future。該 Future 的 get 方法在成功 完成時將會返回null。”媽蛋,那這樣與execute又有什麼區別呀。何必這樣多此一舉呢?我不服,我認為execute與submit裡面肯定存在互相呼叫的關係,畢竟ExecutorService是Executor的子類嘛

3.怒開IDE,深入原始碼一探究竟

寫了一個執行緒池,ctrl+左鍵深入execute方法,發現程式碼如下:

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

這不是關於任務到執行緒池裡面一些具體的操作嗎!菜鳥太菜有些方法還是深入理解不了,不談。回到剛剛想的那個問題,這樣的話那麼execute方法就是具體對任務的操作,那麼submit方法呢?

點選進入了AbstractExecutorService抽象類原始碼,發現原始碼如下:

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }


    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

這媽蛋,不是都把拿到的Runnable任務都構造了RunnableFuture任務然後都拋給execute方法嗎!也是醉了,
得出結論1:如果提交的任務不需要一個結果的話直接用execute()會提升很多效能

那我奇怪了newTaskFor這個又是什麼jb玩意啊,用這個函式是怎麼構造一個RunnableFuture任務的,怒氣又來進入了方法,得結果如下:

    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

罵了個比啊,這個方法不是幫你new了FutureTask嗎!氣得我得出
結論二:就是相當於說如果你傳的任務是需要結果的,那你就使用你的類去繼承Callable介面,然後告訴submit方法就行了,如果你只需要一個特定的結果,就把那個特定的結果告訴submit方法然後把你想要的特定結果也告訴他,它只是幫你完成以前使用Future模式的時候你自己需要做的那些步驟而已,如果你不需要一個結果,那麼就老老實實使用execute,如果你需要的是一個空結果,那麼submit(yourRunnable)與submit(yourRunnable,null)是等價的!