線程池中execute與submit的區(qū)別說明
線程池execute與submit區(qū)別
在使用線程池的時(shí)候,看到execute()與submit()方法。都可以使用線程池執(zhí)行一個(gè)任務(wù),但是兩者有什么區(qū)別呢?
execute
void execute(Runnable command);
submit
<T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task);
相同點(diǎn):
1 都可以執(zhí)行任務(wù)
2 參數(shù)都支持runnable
不同點(diǎn):
1 submit支持接收返回值 詳見例1。
2 execute 任務(wù)里面的異常必須捕獲,不能向上拋出;submit支持的Callable支持向上拋出異常,需要由返回值.get()來進(jìn)行接收。詳見例2。
例1
public class ExecutorTest {? ? ? public static ExecutorService executorService = Executors.newFixedThreadPool(3);? ? ? public static void main(String[] args) { ? ? ? ? Future<?> result1 = executorService.submit(new Callable() { ? ? ? ? ? ? @Override ? ? ? ? ? ? public Object call() throws Exception { ? ? ? ? ? ? ? ? TimeUnit.SECONDS.sleep(10); ? ? ? ? ? ? ? ? System.out.println("Thread1 sleep 10 seconds"); ? ? ? ? ? ? ? ? return true; ? ? ? ? ? ? } ? ? ? ? }); ? ? ? ? Future<?> result2 = executorService.submit(new Callable() { ? ? ? ? ? ? @Override ? ? ? ? ? ? public Object call() throws Exception { ? ? ? ? ? ? ? ? TimeUnit.SECONDS.sleep(5); ? ? ? ? ? ? ? ? System.out.println("Thread2 sleep 5 seconds"); ? ? ? ? ? ? ? ? return false; ? ? ? ? ? ? } ? ? ? ? }); ? ? ? ? try { ? ? ? ? ? ? System.out.println("Thread1 return:"+result1.get()); ? ? ? ? ? ? System.out.println("Thread2 return:"+result2.get()); ? ? ? ? ? ? System.out.println("finished"); ? ? ? ? } catch (InterruptedException e) { ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? } catch (ExecutionException e) { ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? }? ? ? } }
該例子的返回結(jié)果:
Thread2 sleep 5 seconds
Thread1 sleep 10 seconds
Thread1 return:true
Thread2 return:false
finished
解釋:可以看到接收到了兩個(gè)線程的返回結(jié)果。利用thread.sleep來模擬耗時(shí)操作,直到兩個(gè)線程執(zhí)行完畢之后,才會(huì)輸出finished。利用Callable的返回阻塞,來等待這n個(gè)線程的執(zhí)行完畢,然后將這n個(gè)線程的結(jié)果響應(yīng)回去;其執(zhí)行時(shí)間,基本上取決于最耗時(shí)的那個(gè)線程。
適用場景:在某些情況下,需要獲得一組線程的結(jié)果,給調(diào)用端。
例2
而execute不支持向上拋出異常,必須將異常捕獲。
線程池submit和execute方法原理
線程池的作用
1. 避免大量的線程強(qiáng)占資源
2. 避免大量的線程創(chuàng)建和銷毀帶來的開銷
線程池的原理
創(chuàng)建線程池的時(shí)候,可以使用executors的靜態(tài)方法,也可以使用new ThreadPoolExecutor的方式手動(dòng)創(chuàng)建線程池,通過在線程池中指定參數(shù)達(dá)到創(chuàng)建不同類型的線程池的效果
其中,executors底層其實(shí)也是調(diào)用的new ThreadPoolExecutor()的方式創(chuàng)建的,是對不同線程池的封裝,
線程的執(zhí)行有兩種方式,一種是submit(runnable v)的形式,一種是execute(runnable b) 的形式,不同的是submit可以返回一個(gè)future的實(shí)現(xiàn)類,相同的一點(diǎn)是submit底層其實(shí)也是調(diào)用的execute
調(diào)用execut方法,首先判斷傳入的參數(shù)是否為空,如果為空,拋出異常,如果不為空,使用獲取ctl值,計(jì)算出當(dāng)前線程狀態(tài)碼,通過狀態(tài)碼計(jì)算出當(dāng)前線程池工作線程是否小于核心線程數(shù)量
如果小于,判斷添加工作線程操作是否正常,如果正常,直接返回,如果不正常,繼續(xù)執(zhí)行獲取ctl值,在添加工作線程的過程中,首先通過循環(huán)的方式保證ctl在加1的情況下狀態(tài)同步,如果不同步,一直循環(huán)到同步為止,添加完成后,創(chuàng)建線程工作對象,把工作線程添加到set集合中,并執(zhí)行.start,如果執(zhí)行不成功,從set中刪除添加的worker對象,并且ctl回滾到之前沒有自增的值.
如果上述中添加工作線程失敗,或者當(dāng)前線程池中工作線程數(shù)量操作和信息數(shù)量,執(zhí)行下列邏輯
判斷當(dāng)前線程池狀態(tài)是否是running狀態(tài):
如果不是running狀態(tài),或者是running狀態(tài),并且添加到線程隊(duì)列失敗,重新添加個(gè)工作線程,此時(shí)入?yún)⒅械诙€(gè)參數(shù)用于添加工作線程的邏輯中當(dāng)前工作線程數(shù)量與最大線程數(shù)量做對比,如果添加失敗,執(zhí)行reject處理類處理
如果是running狀態(tài),并且添加隊(duì)列成功,重新獲取ctl值,判斷當(dāng)前線程池狀態(tài)如果是不是running狀態(tài),并且從對象中刪除成功,則當(dāng)前線程交給拒絕線程處理器處理,如果不滿足上面條件,判斷當(dāng)前線程池的工作線程數(shù)如果為0,重新添加一個(gè)不帶任務(wù)的線程.
//AbstractExecutorService.java文件 ? ? // executorService 中的 submit 方法 ? ? public Future<?> submit(Runnable task) { ? ? ? ? // 首先判斷傳入的runnable 對象是否為空 ? ? ? ? if (task == null) throw new NullPointerException(); ? ? ? ? // 創(chuàng)建一個(gè) futuretask 對象 ? ? ? ? RunnableFuture<Void> ftask = newTaskFor(task, null); ? ? ? ? execute(ftask); ? ? ? ? return ftask; ? ? } ? ? // 根據(jù)runnable 創(chuàng)建一個(gè)futuretask對象 ? ? protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { ? ? ? ? return new FutureTask<T>(runnable, value); ? ? } ? ? // ThreadPoolExecutor.java文件 ? ? // 執(zhí)行創(chuàng)建線程池的方法 ? ? public void execute(Runnable command) { ? ? ? ? // 首先判斷傳入的線程是否為空 ? ? ? ? if (command == null) ? ? ? ? ? ? // 為空,拋出異常 ? ? ? ? ? ? throw new NullPointerException(); ? ? ? ? // 獲取線程池的狀態(tài)碼, 這個(gè)狀態(tài)碼是自增的,原子類型的自增, 在執(zhí)行addworker后ctl會(huì)加1 ? ? ? ? int c = ctl.get(); ? ? ? ? // 通過狀態(tài)碼,獲取線程池中的線程的數(shù)量,如果小于核心數(shù)量 ? ? ? ? if (workerCountOf(c) < corePoolSize) { ? ? ? ? ? ? // 添加線程到線程池,并且為true時(shí)使用核心線程數(shù)作為邊界,如果false ,使用最大數(shù)量線程數(shù)作為邊界 ? ? ? ? ? ? if (addWorker(command, true)) ? ? ? ? ? ? ? ? // 添加完成后,返回 ? ? ? ? ? ? ? ? return; ? ? ? ? ? ? // 如果添加失敗,重新獲取狀態(tài)值 ? ? ? ? ? ? c = ctl.get(); ? ? ? ? } ? ? ? ? // 執(zhí)行下面邏輯有兩種情況 ? ? ? ? // ? ? ?1. 工作線程數(shù)大于核心線程 ? ? ? ? // ? ? ?2. 添加線程時(shí)出錯(cuò) ? ? ? ? // 如果線程池中線程的數(shù)量大于核心的數(shù)量, 判斷如果是運(yùn)行狀態(tài), 并且也把線程加進(jìn)了阻塞隊(duì)列 workQueue 中 ? ? ? ? if (isRunning(c) && workQueue.offer(command)) { ? ? ? ? ? ? // 重新獲取 線程池 狀態(tài)值 ? ? ? ? ? ? int recheck = ctl.get(); ? ? ? ? ? ? // 判斷當(dāng)前線程池如果不是運(yùn)行狀態(tài),并且成功從隊(duì)列中移除(從workQueue中移除線程, 并嘗試終止線程池) ? ? ? ? ? ? if (! isRunning(recheck) && remove(command)) ? ? ? ? ? ? ? ? // 執(zhí)行拒絕執(zhí)行線程的處理 ? ? ? ? ? ? ? ? reject(command); ? ? ? ? ? ? ? ? // 如果工作線程數(shù)為0 ? ? ? ? ? ? else if (workerCountOf(recheck) == 0) ? ? ? ? ? ? ? ? // 添加一個(gè)null的工作包裝對象 ? ? ? ? ? ? ? ? addWorker(null, false); ? ? ? ? } else if (!addWorker(command, false)) ? ? ? ? ? ? // 如果添加到線程池中出錯(cuò),執(zhí)行拒接的線程 ? ? ? ? ? ? reject(command); ? ? } ? ? // 創(chuàng)建一個(gè)原子類對象用于計(jì)算線程的中狀態(tài) ? ? private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); ? ? // integer.size 為 32 ? ? private static final int COUNT_BITS = Integer.SIZE - 3; ? ? private static final int CAPACITY ? = (1 << COUNT_BITS) - 1; ? ? // 即高3位為1,低29位為0,該狀態(tài)的線程池會(huì)接收新任務(wù),也會(huì)處理在阻塞隊(duì)列中等待處理的任務(wù) ? ? private static final int RUNNING ? ?= -1 << COUNT_BITS; ? ? // 即高3位為0,低29位為0,該狀態(tài)的線程池不會(huì)再接收新任務(wù),但還會(huì)處理已經(jīng)提交到阻塞隊(duì)列中等待處理的任務(wù) ? ? private static final int SHUTDOWN ? = ?0 << COUNT_BITS; ? ? // 即高3位為001,低29位為0,該狀態(tài)的線程池不會(huì)再接收新任務(wù),不會(huì)處理在阻塞隊(duì)列中等待的任務(wù),而且還會(huì)中斷正在運(yùn)行的任務(wù) ? ? private static final int STOP ? ? ? = ?1 << COUNT_BITS; ? ? // 即高3位為010,低29位為0,所有任務(wù)都被終止了,workerCount為0,為此狀態(tài)時(shí)還將調(diào)用terminated()方法 ? ? private static final int TIDYING ? ?= ?2 << COUNT_BITS; ? ? // 即高3位為100,低29位為0,terminated()方法調(diào)用完成后變成此狀態(tài) ? ? ? private static final int TERMINATED = ?3 << COUNT_BITS; ? ? // 用戶計(jì)算線程的狀態(tài) 32位中 高3位為1 低29位為0? ? ? private static int runStateOf(int c) ? ? { return c & ~CAPACITY; } ? ? // 用于計(jì)算線程池中線程的數(shù)量 32位中 高3位為0 ?低29位為1 ? ? private static int workerCountOf(int c) ?{ return c & CAPACITY; } ? ? // rs 為 runState, wc 為 workerCount 通過工作狀態(tài)和線程數(shù)量來計(jì)算出 ctl ? ? private static int ctlOf(int rs, int wc) { return rs | wc; } ? ? // 添加工作線程的方法 ? ? private boolean addWorker(Runnable firstTask, boolean core) { ? ? ? ? // 設(shè)置循環(huán)跳出點(diǎn),如果執(zhí)行到某個(gè)位置,使用break,直接跳出的是這個(gè)標(biāo)簽范圍內(nèi)的所有循環(huán) ? ? ? ? retry: ? ? ? ? for (;;) { ? ? ? ? ? ? // 獲取線程狀態(tài) ? ? ? ? ? ? int c = ctl.get(); ? ? ? ? ? ? int rs = runStateOf(c); ? ? ? ? ? ? // 判斷線程池狀態(tài)是否在shutdown上以及 狀態(tài)不是關(guān)閉并且添加的線程不為空,并且線程隊(duì)列中的線程不是空的 ? ? ? ? ? ? if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) ? ? ? ? ? ? ? ? // 如果滿足上面條件,說明線程池已經(jīng)不適合添加新的線程了, 直接返回false ? ? ? ? ? ? ? ? return false; ? ? ? ? ? ? // 如果不滿足上面條件,說明線程池可以添加線程, 下面這個(gè)循環(huán)主要是對ctl進(jìn)行操作,保證在增1后線程狀態(tài)保持同步 ? ? ? ? ? ? for (;;) { ? ? ? ? ? ? ? ? // 獲取工作線程數(shù)量 ? ? ? ? ? ? ? ? int wc = workerCountOf(c); ? ? ? ? ? ? ? ? // 判斷當(dāng)前線程池中工作線程數(shù)量是否大于線程容量,大于核心線程數(shù)或最大線程數(shù) ? ? ? ? ? ? ? ? if (wc >= CAPACITY || ? ? ? ? ? ? ? ? ? ? wc >= (core ? corePoolSize : maximumPoolSize)) ? ? ? ? ? ? ? ? ? ? // 滿足條件,說明當(dāng)前線程不是適合添加新的線程的 ? ? ? ? ? ? ? ? ? ? return false; ? ? ? ? ? ? ? ? // 如果工作數(shù)量少于最大量或者核心線程數(shù)或最大線程數(shù), 工作線程數(shù)加1,即操作ctl,通過cas的方式 ? ? ? ? ? ? ? ? if (compareAndIncrementWorkerCount(c)) ? ? ? ? ? ? ? ? ? ? // 如果添加成功,跳出內(nèi)循環(huán), ? ? ? ? ? ? ? ? ? ? break retry; ? ? ? ? ? ? ? ? // 如果添加失敗,重新獲取ctl ? ? ? ? ? ? ? ? c = ctl.get(); ?// Re-read ctl ? ? ? ? ? ? ? ? // 判斷此時(shí)線程池狀態(tài)是否已經(jīng)改變 ? ? ? ? ? ? ? ? if (runStateOf(c) != rs) ? ? ? ? ? ? ? ? ? ? //如果狀態(tài)不一致,跳過,重新循環(huán) ? ? ? ? ? ? ? ? ? ? continue retry; ? ? ? ? ? ? ? ? // else CAS failed due to workerCount change; retry inner loop ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? boolean workerStarted = false; ? ? ? ? boolean workerAdded = false; ? ? ? ? // 創(chuàng)建一個(gè)線程包裝對象,用于包裝線程 ? ? ? ? Worker w = null; ? ? ? ? try { ? ? ? ? ? ? w = new Worker(firstTask); ? ? ? ? ? ? // 創(chuàng)建一個(gè)worker 工作線程 ? ? ? ? ? ? final Thread t = w.thread; ? ? ? ? ? ? // 判斷創(chuàng)建的線程是否為空 ? ? ? ? ? ? if (t != null) { ? ? ? ? ? ? ? ? ?// 如果不為空,獲取鎖對象 ? ? ? ? ? ? ? ? final ReentrantLock mainLock = this.mainLock; ? ? ? ? ? ? ? ? // 開始加鎖 ? ? ? ? ? ? ? ? mainLock.lock(); ? ? ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? ? ? // 獲取線程池狀態(tài) ? ? ? ? ? ? ? ? ? ? int rs = runStateOf(ctl.get()); ? ? ? ? ? ? ? ? ? ? // 如果線程池狀態(tài)是running或者線程池狀態(tài)關(guān)閉并且傳入的線程是空的 ? ? ? ? ? ? ? ? ? ? if (rs < SHUTDOWN || ? ? ? ? ? ? ? ? ? ? ? ? (rs == SHUTDOWN && firstTask == null)) { ? ? ? ? ? ? ? ? ? ? ? ? // 判斷創(chuàng)建的工作線程是否是活動(dòng)狀態(tài)(已經(jīng)開始還沒有死掉) ? ? ? ? ? ? ? ? ? ? ? ? if (t.isAlive()) // precheck that t is startable ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 如果是活動(dòng)狀態(tài),拋出 非法線程狀態(tài)異常? ? ? ? ? ? ? ? ? ? ? ? ? ? ? throw new IllegalThreadStateException(); ? ? ? ? ? ? ? ? ? ? ? ? // 如果不是活動(dòng)狀態(tài), 添加到set集合中,這個(gè)set集合只有持有mainlock才可以訪問 ? ? ? ? ? ? ? ? ? ? ? ? workers.add(w); ? ? ? ? ? ? ? ? ? ? ? ? // 獲取集合長度 ? ? ? ? ? ? ? ? ? ? ? ? int s = workers.size(); ? ? ? ? ? ? ? ? ? ? ? ? // 如果存放剛才創(chuàng)建的workers工作線程的集合中的線程數(shù)超過最大的池的大小 ? ? ? ? ? ? ? ? ? ? ? ? if (s > largestPoolSize) ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 把set集合中的數(shù)量代替原線程池最大值 ? ? ? ? ? ? ? ? ? ? ? ? ? ? largestPoolSize = s; ? ? ? ? ? ? ? ? ? ? ? ? workerAdded = true; ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } finally { ? ? ? ? ? ? ? ? ? ? // 釋放鎖 ? ? ? ? ? ? ? ? ? ? mainLock.unlock(); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? // 根據(jù)前面的判斷是否需要開啟線程,如果線程已經(jīng)是活動(dòng)的,不需要開啟,如果不是活動(dòng)線程,開啟線程 ? ? ? ? ? ? ? ? if (workerAdded) { ? ? ? ? ? ? ? ? ? ? t.start(); ? ? ? ? ? ? ? ? ? ? // 開啟成功,設(shè)置workerStarted 為 true ? ? ? ? ? ? ? ? ? ? workerStarted = true; ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } finally { ? ? ? ? ? ? // 如果工作線程開啟失敗,調(diào)用添加到失敗的線程中 ? ? ? ? ? ? if (! workerStarted) ? ? ? ? ? ? ? ? // 從set中移除失敗的線程,并且ctl減1, 并且嘗試終止線程池 ? ? ? ? ? ? ? ? addWorkerFailed(w); ? ? ? ? } ? ? ? ? return workerStarted; ? ? } ? ? // 線程開啟失敗后的方法 ? ? private void addWorkerFailed(Worker w) { ? ? ? ? // 獲取鎖 ? ? ? ? final ReentrantLock mainLock = this.mainLock; ? ? ? ? mainLock.lock(); ? ? ? ? try { ? ? ? ? ? ? if (w != null) ? ? ? ? ? ? ? ? // 如果線程不為空,從set集合中移除沒有開啟成功的線程 ? ? ? ? ? ? ? ? workers.remove(w); ? ? ? ? ? ? // 減去之前ctl增加的1 ? ? ? ? ? ? decrementWorkerCount(); ? ? ? ? ? ? // 嘗試中斷線程 ? ? ? ? ? ? tryTerminate(); ? ? ? ? } finally { ? ? ? ? ? ? mainLock.unlock(); ? ? ? ? } ? ? } ? ? // 通過cas方式ctl加1 ? ? private boolean compareAndIncrementWorkerCount(int expect) { ? ? ? ? return ctl.compareAndSet(expect, expect + 1); ? ? } ? ? // 移除線程 ? ? public boolean remove(Runnable task) { ? ? ? ? // 從等待隊(duì)列中一尺線程 ? ? ? ? boolean removed = workQueue.remove(task); ? ? ? ? // 嘗試終止線程池 ? ? ? ? tryTerminate(); // In case SHUTDOWN and now empty ? ? ? ? return removed; ? ? } ? ? // 使用拒絕處理對象執(zhí)行拒接指定線程 ? ? final void reject(Runnable command) { ? ? ? ? handler.rejectedExecution(command, this); ? ? }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Druid關(guān)閉監(jiān)控頁面關(guān)閉不了的問題及解決
這篇文章主要介紹了Druid關(guān)閉監(jiān)控頁面關(guān)閉不了的問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05詳解Springboot如何通過注解實(shí)現(xiàn)接口防刷
本文主要為大家介紹一種極簡潔、靈活通用接口防刷實(shí)現(xiàn)方式、通過在需要防刷的方法加上@Prevent?注解即可實(shí)現(xiàn)短信防刷,感興趣的可以了解一下2022-09-09IDEA設(shè)置JVM可分配內(nèi)存大小和其他參數(shù)的教程
這篇文章主要介紹了IDEA設(shè)置JVM可分配內(nèi)存大小和其他參數(shù)的教程,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01springboot引用kettle實(shí)現(xiàn)對接oracle數(shù)據(jù)的示例代碼
這篇文章主要介紹了springboot引用kettle實(shí)現(xiàn)對接oracle數(shù)據(jù),其實(shí)kettle集成到springboot里面沒有多少代碼,這個(gè)功能最主要的還是ktr文件的編寫,只要ktr編寫好了,放到指定文件夾下,寫個(gè)定時(shí)任務(wù)就完事了,需要的朋友可以參考下2022-12-12Java中實(shí)現(xiàn)兩個(gè)線程交替運(yùn)行的方法
這篇文章主要介紹了Java中實(shí)現(xiàn)兩個(gè)線程交替運(yùn)行的方法,本文將給大家分享操作流程,通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-12-12spring boot定時(shí)任務(wù)接收郵件并且存儲(chǔ)附件的方法講解
今天小編就為大家分享一篇關(guān)于spring boot定時(shí)任務(wù)接收郵件并且存儲(chǔ)附件的方法講解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03Java Netty實(shí)現(xiàn)心跳機(jī)制過程解析
這篇文章主要介紹了Java Netty實(shí)現(xiàn)心跳機(jī)制過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03