Java線程中yield與join方法的區(qū)別
長(zhǎng)期以來,多線程問題頗為受到面試官的青睞。雖然我個(gè)人認(rèn)為我們當(dāng)中很少有人能真正獲得機(jī)會(huì)開發(fā)復(fù)雜的多線程應(yīng)用(在過去的七年中,我得到了一個(gè)機(jī)會(huì)),但是理解多線程對(duì)增加你的信心很有用。之前,我討論了一個(gè)wait()和sleep()方法區(qū)別的問題,這一次,我將會(huì)討論join()和yield()方法的區(qū)別。坦白的說,實(shí)際上我并沒有用過其中任何一個(gè)方法,所以,如果你感覺有不恰當(dāng)?shù)牡胤?,?qǐng)?zhí)岢鲇懻摗?/p>Java線程調(diào)度的一點(diǎn)背景
在各種各樣的線程中,Java虛擬機(jī)必須實(shí)現(xiàn)一個(gè)有優(yōu)先權(quán)的、基于優(yōu)先級(jí)的調(diào)度程序。這意味著Java程序中的每一個(gè)線程被分配到一定的優(yōu)先權(quán),使用定義好的范圍內(nèi)的一個(gè)正整數(shù)表示。優(yōu)先級(jí)可以被開發(fā)者改變。即使線程已經(jīng)運(yùn)行了一定時(shí)間,Java虛擬機(jī)也不會(huì)改變其優(yōu)先級(jí)
優(yōu)先級(jí)的值很重要,因?yàn)镴ava虛擬機(jī)和下層的操作系統(tǒng)之間的約定是操作系統(tǒng)必須選擇有最高優(yōu)先權(quán)的Java線程運(yùn)行。所以我們說Java實(shí)現(xiàn)了一個(gè)基于優(yōu)先權(quán)的調(diào)度程序。該調(diào)度程序使用一種有優(yōu)先權(quán)的方式實(shí)現(xiàn),這意味著當(dāng)一個(gè)有更高優(yōu)先權(quán)的線程到來時(shí),無論低優(yōu)先級(jí)的線程是否在運(yùn)行,都會(huì)中斷(搶占)它。這個(gè)約定對(duì)于操作系統(tǒng)來說并不總是這樣,這意味著操作系統(tǒng)有時(shí)可能會(huì)選擇運(yùn)行一個(gè)更低優(yōu)先級(jí)的線程。(我憎恨多線程的這一點(diǎn),因?yàn)檫@不能保證任何事情)
注意Java并不限定線程是以時(shí)間片運(yùn)行,但是大多數(shù)操作系統(tǒng)卻有這樣的要求。在術(shù)語中經(jīng)常引起混淆:搶占經(jīng)常與時(shí)間片混淆。事實(shí)上,搶占意味著只有擁有高優(yōu)先級(jí)的線程可以優(yōu)先于低優(yōu)先級(jí)的線程執(zhí)行,但是當(dāng)線程擁有相同優(yōu)先級(jí)的時(shí)候,他們不能相互搶占。它們通常受時(shí)間片管制,但這并不是Java的要求。
理解線程的優(yōu)先權(quán)接下來,理解線程優(yōu)先級(jí)是多線程學(xué)習(xí)很重要的一步,尤其是了解yield()函數(shù)的工作過程。
記住當(dāng)線程的優(yōu)先級(jí)沒有指定時(shí),所有線程都攜帶普通優(yōu)先級(jí)。優(yōu)先級(jí)可以用從1到10的范圍指定。10表示最高優(yōu)先級(jí),1表示最低優(yōu)先級(jí),5是普通優(yōu)先級(jí)。記住優(yōu)先級(jí)最高的線程在執(zhí)行時(shí)被給予優(yōu)先。但是不能保證線程在啟動(dòng)時(shí)就進(jìn)入運(yùn)行狀態(tài)。與在線程池中等待運(yùn)行機(jī)會(huì)的線程相比,當(dāng)前正在運(yùn)行的線程可能總是擁有更高的優(yōu)先級(jí)。由調(diào)度程序決定哪一個(gè)線程被執(zhí)行。t.setPriority()用來設(shè)定線程的優(yōu)先級(jí)。記住在線程開始方法被調(diào)用之前,線程的優(yōu)先級(jí)應(yīng)該被設(shè)定。你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY來設(shè)定優(yōu)先級(jí)現(xiàn)在,當(dāng)我們對(duì)線程調(diào)度和線程優(yōu)先級(jí)有一定理解后,讓我們進(jìn)入主題。
yield()方法理論上,yield意味著放手,放棄,投降。一個(gè)調(diào)用yield()方法的線程告訴虛擬機(jī)它樂意讓其他線程占用自己的位置。這表明該線程沒有在做一些緊急的事情。注意,這僅是一個(gè)暗示,并不能保證不會(huì)產(chǎn)生任何影響。
在Thread.java中yield()定義如下:
/** * A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore * this hint. Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilize a CPU. * Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect. */public static native void yield();
讓我們列舉一下關(guān)于以上定義重要的幾點(diǎn):
Yield是一個(gè)靜態(tài)的原生(native)方法Yield告訴當(dāng)前正在執(zhí)行的線程把運(yùn)行機(jī)會(huì)交給線程池中擁有相同優(yōu)先級(jí)的線程。Yield不能保證使得當(dāng)前正在運(yùn)行的線程迅速轉(zhuǎn)換到可運(yùn)行的狀態(tài)它僅能使一個(gè)線程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài),而不是等待或阻塞狀態(tài)yield()方法使用示例在下面的示例程序中,我隨意的創(chuàng)建了名為生產(chǎn)者和消費(fèi)者的兩個(gè)線程。生產(chǎn)者設(shè)定為最小優(yōu)先級(jí),消費(fèi)者設(shè)定為最高優(yōu)先級(jí)。在Thread.yield()注釋和非注釋的情況下我將分別運(yùn)行該程序。沒有調(diào)用yield()方法時(shí),雖然輸出有時(shí)改變,但是通常消費(fèi)者行先打印出來,然后事生產(chǎn)者。
調(diào)用yield()方法時(shí),兩個(gè)線程依次打印,然后將執(zhí)行機(jī)會(huì)交給對(duì)方,一直這樣進(jìn)行下去。
package test.core.threads;public class YieldExample{ public static void main(String[] args) { Thread producer = new Producer(); Thread consumer = new Consumer(); producer.setPriority(Thread.MIN_PRIORITY); //Min Priority consumer.setPriority(Thread.MAX_PRIORITY); //Max Priority producer.start(); consumer.start(); }}class Producer extends Thread{ public void run() { for (int i = 0; i < 5; i++) { System.out.println('I am Producer : Produced Item ' + i); Thread.yield(); } }}class Consumer extends Thread{ public void run() { for (int i = 0; i < 5; i++) { System.out.println('I am Consumer : Consumed Item ' + i); Thread.yield(); } }}上述程序在沒有調(diào)用yield()方法情況下的輸出:
I am Consumer : Consumed Item 0 I am Consumer : Consumed Item 1 I am Consumer : Consumed Item 2 I am Consumer : Consumed Item 3 I am Consumer : Consumed Item 4 I am Producer : Produced Item 0 I am Producer : Produced Item 1 I am Producer : Produced Item 2 I am Producer : Produced Item 3 I am Producer : Produced Item 4上述程序在調(diào)用yield()方法情況下的輸出:
I am Producer : Produced Item 0 I am Consumer : Consumed Item 0 I am Producer : Produced Item 1 I am Consumer : Consumed Item 1 I am Producer : Produced Item 2 I am Consumer : Consumed Item 2 I am Producer : Produced Item 3 I am Consumer : Consumed Item 3 I am Producer : Produced Item 4 I am Consumer : Consumed Item 4join()方法
線程實(shí)例的方法join()方法可以使得在另一個(gè)線程的執(zhí)行結(jié)束后再開始執(zhí)行這個(gè)線程。如果join()方法被在一個(gè)線程實(shí)例上調(diào)用,當(dāng)前運(yùn)行著的線程將阻塞直到線程實(shí)例完成了執(zhí)行。
//Waits for this thread to die.public final void join() throws InterruptedException
在join()方法內(nèi)設(shè)定超時(shí),使得join()方法的影響在特定超時(shí)后無效。當(dāng)超時(shí)時(shí),主方法和任務(wù)線程申請(qǐng)運(yùn)行的時(shí)候是平等的。然而,當(dāng)涉及sleep時(shí),join()方法依靠操作系統(tǒng)計(jì)時(shí),所以你不應(yīng)該假定join()方法將會(huì)等待你指定的時(shí)間。
像sleep,join通過拋出InterruptedException對(duì)中斷做出回應(yīng)。
join()方法使用示例package test.core.threads;public class JoinExample{ public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Runnable() { public void run() { System.out.println('First task started'); System.out.println('Sleeping for 2 seconds'); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println('First task completed'); } }); Thread t1 = new Thread(new Runnable() { public void run() { System.out.println('Second task completed'); } }); t.start(); // Line 15 t.join(); // Line 16 t1.start(); }}Output:First task startedSleeping for 2 secondsFirst task completedSecond task completed
這是一些很小卻很重要的概念。在評(píng)論部分讓我知道你的想法。
相關(guān)文章:
1. 完美解決vue 中多個(gè)echarts圖表自適應(yīng)的問題2. JAMon(Java Application Monitor)備忘記3. 利用CSS制作3D動(dòng)畫4. Java GZip 基于內(nèi)存實(shí)現(xiàn)壓縮和解壓的方法5. jsp+servlet簡(jiǎn)單實(shí)現(xiàn)上傳文件功能(保存目錄改進(jìn))6. idea配置jdk的操作方法7. SpringBoot+TestNG單元測(cè)試的實(shí)現(xiàn)8. Springboot 全局日期格式化處理的實(shí)現(xiàn)9. 存儲(chǔ)于xml中需要的HTML轉(zhuǎn)義代碼10. idea打開多個(gè)窗口的操作方法
