一心多用多執行緒-細談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 - 返回的結果
返回: 表示任務等待完成的 Futuresubmit 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)是等價的!