您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“java線程的原理和實(shí)現(xiàn)方式”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
我們都知道,實(shí)現(xiàn)多線程的方式是繼承Thread類和實(shí)現(xiàn)Runable接口,那么除了java中不允許多繼承的這個(gè)特性,他們之間還有什么區(qū)別呢?我們看下面這個(gè)例子:
//繼承Thread類 public class ThreadTest extends Thread { private AtomicInteger count = new AtomicInteger(0); @Override public void run() { for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName() + " " + count.incrementAndGet()); } } public static void main(String[] args) { new ThreadTest().start(); new ThreadTest().start(); } }
//實(shí)現(xiàn)Runable接口 public class RunnableTest implements Runnable { private AtomicInteger count = new AtomicInteger(0); @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " " + count.incrementAndGet()); } } public static void main(String[] args) { RunnableTest rab=new RunnableTest(); new Thread(rab).start(); new Thread(rab).start(); } }
通過(guò)觀察console信息我們可以看到,繼承Thread類,無(wú)法共享對(duì)象資源,而實(shí)現(xiàn)Runable則可以;這讓我們可以針對(duì)不同的業(yè)務(wù)情況作出不同的選擇。
Thread.State枚舉中定義了線程的六種狀態(tài)
狀態(tài) | 說(shuō)明 |
---|---|
NEW | new 對(duì)象后線程狀態(tài) |
RUNNABLE | start方法調(diào)用后;或者waiting狀態(tài)下的線程調(diào)用了notify、LockSupport.Unpark等方法 |
BLOCKED | 等待synchronized代碼塊時(shí)的狀態(tài) |
WAITING | 調(diào)用wait()、join(),LockSupport.park()方法后 |
TIMED_WAITING | 調(diào)用wait(long)、sleep(long),join(long)方法后 |
TERMINATED | 線程運(yùn)行完畢,或者調(diào)用terminate方法成功 |
關(guān)于線程狀態(tài)流轉(zhuǎn),看下面這幅圖;左邊是正常狀態(tài)流轉(zhuǎn),右邊是存在block和waiting的情況:
網(wǎng)上有些文章說(shuō)線程還分為ready和running狀態(tài),這里需要注意的是,這兩種執(zhí)行狀態(tài)都屬于Runable狀態(tài);ready和running狀態(tài)其實(shí)是描述VM/OS是否線程分配CPU資源,JVM并不能決定這一事情。當(dāng)然運(yùn)行中的線程通過(guò)調(diào)用yield方法是可以將running狀態(tài)的線程切換到ready狀態(tài),但這一過(guò)程是OS層面的事情而非JVM層面。
在實(shí)現(xiàn)多線程的過(guò)程中,我們需要用到相關(guān)方法來(lái)進(jìn)行線程狀態(tài)的切換,已達(dá)到不同的目的。
Thread類的相關(guān)方法
方法 | 說(shuō)明 |
---|---|
start | 啟動(dòng)線程,使線程進(jìn)入運(yùn)行狀態(tài) |
setPriority | 給線程設(shè)置優(yōu)先級(jí),有三個(gè)可選項(xiàng):Thread.MIN_PRIORITY 最低優(yōu)先級(jí)、Thread.NORM_PRIORITY 普通、Thread.MAX_PRIORITY 最高 |
sleep | 使線程進(jìn)入睡眠狀態(tài),需要指定時(shí)間,線程進(jìn)入等待狀態(tài),此時(shí)線程仍然持有鎖 |
yield | 使線程交出CPU資源給優(yōu)先級(jí)是同級(jí)及以上的線程,不可以指定時(shí)間,線程仍然是RUNABLE狀態(tài),此時(shí)線程仍然持有鎖 |
interrupt | 終止未執(zhí)行的線程,正在執(zhí)行的線程不受影響,被終止的線程進(jìn)入TERMINATED終止?fàn)顟B(tài) |
stop | 強(qiáng)行終止線程,不管線程是否在執(zhí)行中,此方法為廢棄方法,不是線程安全方法,因?yàn)闀?huì)釋放線程持有的鎖導(dǎo)致數(shù)據(jù)不一致的問(wèn)題產(chǎn)生 |
join | 等待線程執(zhí)行完畢,一般是在主線程中等待子線程執(zhí)行結(jié)束,此方法會(huì)阻塞當(dāng)前線程 |
Object類的相關(guān)方法
Obj類主要有wait()和notify()、notifyAll()三個(gè)方法來(lái)控制多線程讀共享數(shù)據(jù)的訪問(wèn);注意這三個(gè)方法不是Thread類的,故只能在同步代碼塊中才會(huì)產(chǎn)生效果。
方法 | 說(shuō)明 |
---|---|
wait | 使線程暫停,并釋放持有的鎖,需要手動(dòng)調(diào)用notify方法喚醒;線程暫停后會(huì)放入等待隊(duì)列 |
wait(long) | 同wait,但是可以指定暫停的時(shí)間 |
notify | 喚醒暫停的線程,將線程移出等待隊(duì)列;線程喚醒后并不立即執(zhí)行,而是放入獲取鎖的隊(duì)列中等待獲取鎖 |
notifyAll | 喚醒所有暫停的線程,將所有線程沖等待隊(duì)列中移出,并放入獲取鎖的隊(duì)列中 |
我們?cè)谙到y(tǒng)中直接new線程是可以實(shí)現(xiàn)多線程,但是存在以下弊端:
線程不能重用,過(guò)多的線程創(chuàng)建和銷毀會(huì)降低系統(tǒng)性能
不能控制線程并發(fā)數(shù)量
不能指定線程的定時(shí)執(zhí)行等
ThreadPoolExecutor讓我們可以自定義線程池,它有三個(gè)構(gòu)造函數(shù),它的參數(shù)含義如下 :
參數(shù) | 說(shuō)明 |
---|---|
int corePoolSize | 核心線程數(shù)量,當(dāng)線程池內(nèi)的線程數(shù)小于corePoolSize,會(huì)新建線程馬上運(yùn)行任務(wù);這些線程創(chuàng)建后不會(huì)進(jìn)行回收操作,即便是閑置狀態(tài) |
int maximumPoolSize | 最大線程數(shù)量,此參數(shù)需大于corePoolSize;當(dāng)添加任務(wù)時(shí),如果當(dāng)前線程池線程數(shù)量大于corePoolSize,會(huì)提交給等待隊(duì)列,當(dāng)隊(duì)列滿了后,會(huì)新建線程(運(yùn)行線程數(shù)不超過(guò)maximumPoolSize情況下),這一部分線程我們稱之為非核心線程數(shù),它和核心線程沒(méi)有區(qū)別,只是系統(tǒng)會(huì)定時(shí)回收線程,最終線程數(shù)會(huì)保持在corePoolSize數(shù)量 |
long keepAliveTime | 非核心線程的回收時(shí)間,默認(rèn)60 |
TimeUnit unit | keepAliveTime的單位,默認(rèn)秒 |
BlockingQueue<Runnable> workQueue | 線程池中的任務(wù)隊(duì)列,提交任務(wù)時(shí)如果核心線程數(shù)達(dá)到,則會(huì)提交到這里排隊(duì) |
ThreadFactory threadFactory | 創(chuàng)建線程的工廠,可以給每個(gè)創(chuàng)建出來(lái)的線程設(shè)置名字。一般情況下無(wú)須設(shè)置該參數(shù) |
RejectedExecutionHandler handler | 拒絕策略,這是當(dāng)任務(wù)隊(duì)列和線程池都滿了時(shí)所采取的應(yīng)對(duì)策略,默認(rèn)是AbordPolicy,表示直接拋出RejectedExecutionException 異常 |
任務(wù)隊(duì)列
workQueue指明了當(dāng)核心線程數(shù)達(dá)到最大時(shí),任務(wù)的排隊(duì)策略;有三種類型:
參數(shù) | 說(shuō)明 | 缺點(diǎn) |
---|---|---|
直接提交 | 例如:SynchronousQueue(默認(rèn)),這是一個(gè)沒(méi)有數(shù)據(jù)緩沖的阻塞隊(duì)列,隊(duì)列中只能存放一個(gè)元素,超出之后后續(xù)線程提交會(huì)阻塞(maximumPoolSize達(dá)到閾值情況下) | 線程阻塞 |
無(wú)界隊(duì)列 | 例如:不設(shè)定容量的LinkedBlockingQueue,當(dāng)核心線程數(shù)滿了之后,任務(wù)提交后可以一直存放在隊(duì)列中 | maximumPoolSize失效,且有OOM風(fēng)險(xiǎn) |
有界隊(duì)列 | 例如:ArrayBlockingQueue,當(dāng)核心線程數(shù)滿了之后,一定量的任務(wù)提交可以存放在隊(duì)列中,但是后續(xù)如果添加新的任務(wù),需要有拒絕策略 | 需要指定拒絕策略 |
拒絕策略
在使用有界隊(duì)列的前提下,如果工作隊(duì)列已滿,我們需要設(shè)定拒絕策略
參數(shù) | 說(shuō)明 |
---|---|
ThreadPoolExecutor.AbortPolicy | 默認(rèn)策略;直接拋出RejectedExecutionException異常 |
ThreadPoolExecutor.CallerRunsPolicy | 將任務(wù)交給主線程執(zhí)行,通過(guò)阻塞主線程達(dá)到減緩提交的作用 |
ThreadPoolExecutor.DiscardPolicy | 直接丟棄當(dāng)前任務(wù) |
ThreadPoolExecutor.DiscardOldestPolicy | 丟棄最老的任務(wù) |
以上拒絕策略中,除了CallerRunsPolicy其他的都好理解,下面我們使用CallerRunsPolicy策略來(lái)和DiscardPolicy進(jìn)行一個(gè)比對(duì):
我們定義了最大線程是3個(gè),排隊(duì)兩個(gè),線程睡眠0.5s以達(dá)到效果;使用DiscardPolicy策略我們可以看到10個(gè)任務(wù)丟棄了5個(gè)。
但是CallerRunsPolicy策略會(huì)讓主線程來(lái)執(zhí)行任務(wù),同時(shí)將主線程阻塞,已達(dá)到延緩提交任務(wù)的效果;當(dāng)主線程執(zhí)行完畢后,線程池內(nèi)任務(wù)也執(zhí)行完畢了,這時(shí)線程池會(huì)繼續(xù)接受后續(xù)任務(wù)。
我們可以通過(guò)submit、execute方法提交任務(wù)給線程池執(zhí)行,其中submit方法有三個(gè)重載,他們有以下區(qū)別
方法 | 說(shuō)明 | 是否關(guān)心結(jié)果 |
---|---|---|
void execute | 無(wú)返回值,提交后任務(wù)和主線程再無(wú)瓜葛 | 否 |
<T> Future<T> submit(Callable<T> task) | 返回一個(gè)代表執(zhí)行結(jié)果的Future對(duì)象,當(dāng)調(diào)用Future的get方法時(shí)會(huì)獲取到執(zhí)行結(jié)果,如果線程發(fā)生異常會(huì)獲取到異常信息 | 是 |
Future<?> submit(Runnable task) | 返回一個(gè)代表執(zhí)行結(jié)果的Future對(duì)象,,當(dāng)調(diào)用Future的get方法時(shí),成功返回null,如果線程發(fā)生異常會(huì)獲取到異常信息 | 是 |
<T> Future<T> submit(Runnable task, T result) | 當(dāng)線程正常結(jié)束的時(shí)候調(diào)用Future的get方法會(huì)返回result對(duì)象,當(dāng)線程拋出異常的時(shí)候會(huì)獲取到對(duì)應(yīng)的異常的信息 | 是 |
CPU密集型任務(wù),就需要盡量壓榨CPU,參考值可以設(shè)為 NCPU+1
IO密集型任務(wù),參考值可以設(shè)置為2*NCPU
上面ThreadExecutorPool提供給我們手動(dòng)實(shí)現(xiàn)線程池的方式,同時(shí)J.U.C包下面提供了線程池Executors類,可以讓我們方便的創(chuàng)建線程池。
Executors提供了5種線程池的實(shí)現(xiàn)方式,以針對(duì)不同的業(yè)務(wù)場(chǎng)景:
從上圖中我們可以看到,除了newWorkStealingPool以外,其他的線程池都只是通過(guò)ThreadExecutorPool構(gòu)造函數(shù),傳遞不同的參數(shù)而實(shí)現(xiàn)不同的效果,這也是本篇博客為什么先介紹ThreadExecutorPool的原因;雖然這幾種線程池區(qū)別已經(jīng)一目了然,我們還是列舉一下它們的特點(diǎn):
方法 | 說(shuō)明 | 缺點(diǎn) |
---|---|---|
newCachedThreadPool | 初始不指定線程池大小,提交任務(wù)后就創(chuàng)建線程,每隔60s回收一下空閑線程 | 最大并發(fā)數(shù)不可控制:導(dǎo)致系統(tǒng)資源耗盡;不拒絕任務(wù):存在OOM風(fēng)險(xiǎn) |
newFixedThreadPool | 指定固定大小線程池,不存在非核心線程,使用無(wú)界隊(duì)列 | 無(wú)界隊(duì)列:存在OOM風(fēng)險(xiǎn) |
newScheduledThreadPool | 在手動(dòng)創(chuàng)建ThreadExecutorPool的基礎(chǔ)上加了一個(gè)定期任務(wù),例如給線程池提交了兩個(gè)任務(wù),設(shè)置10s運(yùn)行一次這兩個(gè)任務(wù) | 需要注意ThreadExecutorPool參數(shù) |
newSingleThreadExecutor | 只有一個(gè)線程的線程池,使用無(wú)界隊(duì)列 | 單線程略顯單薄;無(wú)界隊(duì)列:存在OOM風(fēng)險(xiǎn) |
newWorkStealingPool | 返回一個(gè)ForkJoinPool而不是ThreadExecutorPool對(duì)象,可以指定線程數(shù),詳情見下面ForkJoinPool描述 | 適用于大任務(wù)情況 |
ForkJoinPool
ForkJoinPool適用于大型任務(wù),其核心是Fork和Join;Fork可以將一個(gè)大任務(wù)拆分為多個(gè)小的任務(wù),Join會(huì)將多個(gè)小的任務(wù)的結(jié)果匯總達(dá)到最終運(yùn)行效果。
舉個(gè)例子:假設(shè)一個(gè)線程池中并發(fā)數(shù)控制在兩個(gè),但是每個(gè)任務(wù)都要運(yùn)行1分鐘;假設(shè)我們當(dāng)前服務(wù)器有4個(gè)CPU,兩個(gè)在處理任務(wù),其他的則為空閑狀態(tài),這時(shí)剩下的兩個(gè)空閑CPU資源是浪費(fèi)的。
試想一下:如果們能使剩下的兩個(gè)空閑的CPU也能利用起來(lái)處理上面兩個(gè)任務(wù),任務(wù)運(yùn)行肯定會(huì)加快,這就是ForkJoinPool的設(shè)計(jì)出發(fā)點(diǎn)。
多數(shù)情況下,不推薦使用Executors,而推薦手動(dòng)創(chuàng)建ThreadExecutorPool,因?yàn)镋xecutors的五種實(shí)現(xiàn)方式有一下缺點(diǎn):
要么不能控制并發(fā):資源耗盡、OOM
要么不能設(shè)置等待隊(duì)列大小:OOM
不能指定拒絕策略
方法 | 說(shuō)明 |
---|---|
shutdown | 關(guān)閉線程池,執(zhí)行以前提交的任務(wù),但是不接受新提交的任務(wù) |
isTerminated | 如果調(diào)用了shutdown,并且所有任務(wù)已經(jīng)完成,則返回true,否則永遠(yuǎn)false |
getActiveCount | 線程池存貨的數(shù)量 |
getQueue().size | 等待隊(duì)列大小 |
getPoolSize | 線程池中當(dāng)前線程數(shù)量 |
getLargestPoolSize | 曾經(jīng)有過(guò)的最大線程數(shù)量 |
getTaskCount | 未完成的任務(wù)數(shù)量 |
getCompletedTaskCount | 完成的任務(wù)數(shù)量 |
“java線程的原理和實(shí)現(xiàn)方式”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。