1. execute 和 submit 的區別
前面說了還需要介紹多執行緒中使用 execute 和 submit 的區別(這兩個方法都是執行緒池 ThreadPoolExecutor 的方法),
1.1 方法來源不同
execute 方法是執行緒池的頂層介面 Executor 定義的,在 ThreadPoolExecutor 中實作:
void execute(Runnable command);
submit()是在ExecutorService介面中定義的,并定義了三種多載方式:
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
在AbstractExecutorService類中有它們的具體實作,而ThreadPoolExecutor繼承了AbstractExecutorService類,所以 ThreadPoolExecutor 也有這三個方法,
1.2 接收引數不同
從上面的方法來源中可以看出,二者接收引數型別不同:
execute()方法只能接收實作Runnable介面型別的任務submit()方法則既可以接收Runnable型別的任務,也可以接收Callable型別的任務
1.3 回傳值不同
由于 Runnable 和 Callable 的區別就是,Runnable 無回傳值,Callable 有回傳值,
所以 execute 和 submit 的回傳值也不同,
-
execute()的回傳值是void,執行緒提交后不能得到執行緒的回傳值 -
submit()的回傳值是Future,通過Future的get()方法可以獲取到執行緒執行的回傳值,get()方法是同步的,執行get()方法時,如果執行緒還沒執行完,會同步等待,直到執行緒執行完成雖然submit()方法可以提交Runnable型別的引數,但執行Future方法的get()時,執行緒執行完會回傳null,不會有實際的回傳值,這是因為Runable本來就沒有回傳值
1.4 例外處理機制不同
- 用
submit提交任務,任務內有例外也不會列印例外資訊,而是呼叫get()方法時,列印出任務執行例外資訊 - 用
execute提交任務時,任務內有例外會直接列印出來
后面原始碼分析中會體現這個不同點!
2. execute 和 submit 原始碼分析
2.1 submit 原始碼分析
submit 方法是 ExecutorService 介面定義,由 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;
}
可以看一下上面 submit 的三個多載方法,方法體很相似,都呼叫了一個方法 newTaskFor(...) ,那么就來看看這個方法,可以看到它有兩個多載方法:
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);
}
解釋一下上面兩個多載方法吧:
- 第一個
newTaskFor(Runnable runnable, T value):可以看到它應該是將submit方法傳進來的Runnable轉化成了Callable,并給一個回傳值 - 第二個
newTaskFor(Callable<T> callable):就是submit直接傳進了一個Callable,包裝成FutureTask回傳,
上面代碼中可以看一下 RunnableFuture 和 FutureTask 的關系:
先看一下 RunnableFuture:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
RunnableFuture 實作了 Runnable 和 Future,它的子類就是 FutureTask
public class FutureTask<V> implements RunnableFuture<V> {
// ...
}
到這里就明白了吧,當 submit 傳入的引數是 Runnable 的時候,就需要 FutureTask的構造方法將 Runnable 轉化成 Callable
下面看一下 FutureTask 的兩個建構式:
// 傳入Runnable則是執行了一共方法,看一下這個方法,具體轉化邏輯就有了
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
// 傳入Callable直接賦值給類的成員變數
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
下面看一下當 submit 傳入 Runnable 的時候,其實到這里就是呼叫了 FutureTask(Runnable runnable, V result) 建構式,看一下這個建構式中將 Runable 轉化成了 Callable,看一下 Executors.callable(runnable, result) 方法:
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
看看這里有創建了一個類,就是 RunnableAdapter,下面再看一下這個內部類:
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
看看這個內部類,實作了 Callable 介面,并在 call 方法中 呼叫了 task 的 run 方法,就是相當于任務代碼直接在 call 方法中了,
這里必須說一下執行緒池中的執行緒是怎么執行的,這里就不說全部了,直說與這里相關的一部分:
- 看到上面
submit方法最終也是呼叫了execute方法,經過上main原始碼分析的一系列轉換,submit最終呼叫了ThreadPoolExecutor的execute方法 execute方法里面有一個很關鍵的方法是addWorker(command, true)- 進入
addWorker方法,可以看到里面 new 了一個Worker,Worker里面有一個屬性是Thread,后面直接呼叫了它的start方法啟動了執行緒 - 可以看一下
Worker類,它實作了Runnable,這里就要看看Worker的run方法了,呼叫了runWorker - 在
runWorker方法中,有一行是task.run(),呼叫submit時最終這個run方法就是RunnableFuture中的run()方法,具體實作在FutureTask中
下面就看一下 FutureTask 中實作的 run 方法:
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 {
//從這里可以看到,呼叫了call()方法
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
// 看看,這里是將例外放在了一個屬性中,所以 submit執行的時候不會拋出例外,只有在呼叫 get 方法時才會拋出例外
setException(ex);
}
if (ran)
//這里將回傳值設定到了outcome,執行完后可以通過get()獲取
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);
}
}
看一下上面兩個主要的方法(setException(ex) 和 set(result)):
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
可以看到兩個方法一個是將例外放進了 outcome ,一個是將 call 方法的回傳值放進了 outcome,不管是例外還是執行緒執行的回傳值,都會在get 方法中獲取到,下面看一下get 方法,方法在 FutureTask 類中:
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
看看get 方法呼叫了 report 方法,取到了上面setException(ex) 和 set(result)方法 放進 outcome 的值并回傳,
這里如果執行緒拋出了例外,這個執行緒會被從執行緒哈希表中移除,取消強參考,讓 GC 回收,并且重新創建一個新的執行緒,
到這里 submit 方法的原始碼就分析完了!!!
2.2 execute 原始碼分析
此方法是執行緒頂層介面 Executor 定義的,在 ThreadPoolExecutor 有其實作,直接看實作:
其實 submit 方法最終呼叫了 execute 也是這一段,不同的是最后呼叫的執行緒的 run 方法是不同實作類實作的
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);
}
這里主要就是看 submit 和 execute 的區別點,所以執行緒池的看具體原始碼就不看了,我之前寫的有一篇執行緒池原始碼的筆記很詳細:執行緒池原始碼
上面有個很重要的方法,是將執行緒加入佇列并執行的,就是 addWorker 方法,這里就不copy addWorker 的代碼了,只需要知道里面創建了一個 Worker 物件即可,Worker 物件中有一個屬性是 Thread,后面獲取到了這個 Thread ,并執行了 start 方法,
然而在 Worker 本身是實作了 Runnable 的,所以后面執行的 start 方法,實際是執行了 Worker 中的 run 方法:
public void run() {
runWorker(this);
}
看看 Worker 中run 方法呼叫的 runWorker 方法:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 這個地方是重點,run 方法
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
其實這里的原始碼就和 submit 一樣了,只是上面 task.run() 呼叫的是不同實作類的 run 方法,
execute方法傳進來的最終是呼叫的Runnable或其子類的run方法submit方法進來的最終是呼叫了FutureTask的run方法
基于上面的區別,再去看 FutureTask 中 run 方法的原始碼,就可以知道一下結論:
execute是沒回傳值的,submit有回傳值- 用
execute提交任務時,任務內有例外會直接列印出來;用submit提交任務,任務內有例外也不會列印例外資訊,而是呼叫get()方法時,列印出任務執行例外資訊
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/549392.html
標籤:其他
上一篇:數字營銷(一)客戶畫像淺談
