您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“線程間的協(xié)作有哪些”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
Java中線程中狀態(tài)可分為五種:New(新建狀態(tài)),Runnable(就緒狀態(tài)),Running(運(yùn)行狀態(tài)),Blocked(阻塞狀態(tài)),Dead(死亡狀態(tài))。
New:新建狀態(tài),當(dāng)線程創(chuàng)建完成時(shí)為新建狀態(tài),即new Thread(…),還沒有調(diào)用start方法時(shí),線程處于新建狀態(tài)。
Runnable:就緒狀態(tài),當(dāng)調(diào)用線程的的start方法后,線程進(jìn)入就緒狀態(tài),等待CPU資源。處于就緒狀態(tài)的線程由Java運(yùn)行時(shí)系統(tǒng)的線程調(diào)度程序(thread scheduler)來調(diào)度。
Running:運(yùn)行狀態(tài),就緒狀態(tài)的線程獲取到CPU執(zhí)行權(quán)以后進(jìn)入運(yùn)行狀態(tài),開始執(zhí)行run方法。
Blocked:阻塞狀態(tài),線程沒有執(zhí)行完,由于某種原因(如,I/O操作等)讓出CPU執(zhí)行權(quán),自身進(jìn)入阻塞狀態(tài)。
Dead:死亡狀態(tài),線程執(zhí)行完成或者執(zhí)行過程中出現(xiàn)異常,線程就會(huì)進(jìn)入死亡狀態(tài)。
這五種狀態(tài)之間的轉(zhuǎn)換關(guān)系如下圖所示:
有了對這五種狀態(tài)的基本了解,現(xiàn)在我們來看看Java中是如何實(shí)現(xiàn)這幾種狀態(tài)的轉(zhuǎn)換的。
大家好,我是AIO生活,關(guān)注我,后續(xù)連載更多技術(shù)重難點(diǎn),文章有不足之處,歡迎大家留言指正,謝謝大家啦!
順便提一下,幫大家整理了些JAVA電子書,掃碼回復(fù)“1024”獲取電子書合集,也可簽到領(lǐng)現(xiàn)金紅包。
暖冬回血~~~~
JDK中一共提供了這三個(gè)版本的方法,
wait()方法的作用是將當(dāng)前運(yùn)行的線程掛起(即讓其進(jìn)入阻塞狀態(tài)),直到notify或notifyAll方法來喚醒線程.
wait(long timeout),該方法與wait()方法類似,唯一的區(qū)別就是在指定時(shí)間內(nèi),如果沒有notify或notifAll方法的喚醒,也會(huì)自動(dòng)喚醒。
至于wait(long timeout,long nanos),本意在于更精確的控制調(diào)度時(shí)間,不過從目前版本來看,該方法貌似沒有完整的實(shí)現(xiàn)該功能,其源碼(JDK1.8)如下:
public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && timeout == 0)) { timeout++; } wait(timeout); }
從源碼來看,JDK8中對納秒的處理,只做了四舍五入,所以還是按照毫秒來處理的,可能在未來的某個(gè)時(shí)間點(diǎn)會(huì)用到納秒級別的精度。雖然JDK提供了這三個(gè)版本,其實(shí)最后都是調(diào)用wait(long timeout)方法來實(shí)現(xiàn)的,wait()方法與wait(0)等效,而wait(long timeout,int nanos)從上面的源碼可以看到也是通過wait(long timeout)來完成的。
下面我們通過一個(gè)簡單的例子來演示wait()方法的使用:
package com.paddx.test.concurrent; public class WaitTest { public void testWait(){ System.out.println("Start-----"); try { wait(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("End-------"); } public static void main(String[] args) { final WaitTest test = new WaitTest(); new Thread(new Runnable() { @Override public void run() { test.testWait(); } }).start(); } }
這段代碼的意圖很簡單,就是程序執(zhí)行以后,讓其暫停一秒,然后再執(zhí)行。運(yùn)行上述代碼,查看結(jié)果:
Start----- Exception in thread "Thread-0">這段程序并沒有按我們的預(yù)期輸出相應(yīng)結(jié)果,而是拋出了一個(gè)異常。大家可能會(huì)覺得奇怪為什么會(huì)拋出異常?而拋出的IllegalMonitorStateException異常又是什么?我們可以看一下JDK中對IllegalMonitorStateException的描述:Thrown to indicate that a thread has attempted to wait on an object's monitor or to notify other threads waiting on an object's monitor without owning the specified monitor.這句話的意思大概就是:線程試圖等待對象的監(jiān)視器或者試圖通知其他正在等待對象監(jiān)視器的線程,但本身沒有對應(yīng)的監(jiān)視器的所有權(quán)。其實(shí)這個(gè)問題在《【68期】面試官:對并發(fā)熟悉嗎?說說Synchronized及實(shí)現(xiàn)原理》一文中有提到過,wait方法是一個(gè)本地方法,其底層是通過一個(gè)叫做監(jiān)視器鎖的對象來完成的。所以上面之所以會(huì)拋出異常,是因?yàn)樵谡{(diào)用wait方式時(shí)沒有獲取到monitor對象的所有權(quán),那如何獲取monitor對象所有權(quán)?Java中只能通過Synchronized關(guān)鍵字來完成,修改上述代碼,增加Synchronized關(guān)鍵字:package com.paddx.test.concurrent; public class WaitTest { public synchronized void testWait(){//增加Synchronized關(guān)鍵字 System.out.println("Start-----"); try { wait(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("End-------"); } public static void main(String[] args) { final WaitTest test = new WaitTest(); new Thread(new Runnable() { @Override public void run() { test.testWait(); } }).start(); } } 現(xiàn)在再運(yùn)行上述代碼,就能看到預(yù)期的效果了:Start----- End------- 所以,通過這個(gè)例子,大家應(yīng)該很清楚,wait方法的使用必須在同步的范圍內(nèi),否則就會(huì)拋出IllegalMonitorStateException異常,wait方法的作用就是阻塞當(dāng)前線程等待notify/notifyAll方法的喚醒,或等待超時(shí)后自動(dòng)喚醒。 2、notify/notifyAll方法有了對wait方法原理的理解,notify方法和notifyAll方法就很容易理解了。既然wait方式是通過對象的monitor對象來實(shí)現(xiàn)的,所以只要在同一對象上去調(diào)用notify/notifyAll方法,就可以喚醒對應(yīng)對象monitor上等待的線程了。notify和notifyAll的區(qū)別在于前者只能喚醒monitor上的一個(gè)線程,對其他線程沒有影響,而notifyAll則喚醒所有的線程,看下面的例子很容易理解這兩者的差別:package com.paddx.test.concurrent; public class NotifyTest { public synchronized void testWait(){ System.out.println(Thread.currentThread().getName() +"> 輸出結(jié)果如下:Thread-0 Start----- Thread-1 Start----- Thread-2 Start----- Thread-3 Start----- Thread-4 Start----- Thread-0 End------- -----------分割線------------- Thread-4 End------- Thread-3 End------- Thread-2 End------- Thread-1 End------- 從結(jié)果可以看出:調(diào)用notify方法時(shí)只有線程Thread-0被喚醒,但是調(diào)用notifyAll時(shí),所有的線程都被喚醒了。最后,有兩點(diǎn)需要注意:1.調(diào)用wait方法后,線程是會(huì)釋放對monitor對象的所有權(quán)的。2.一個(gè)通過wait方法阻塞的線程,必須同時(shí)滿足以下兩個(gè)條件才能被真正執(zhí)行:線程需要被喚醒(超時(shí)喚醒或調(diào)用notify/notifyll)。線程喚醒后需要競爭到鎖(monitor)。 三、sleep/yield/join方法解析上面我們已經(jīng)清楚了wait和notify方法的使用和原理,現(xiàn)在我們再來看另外一組線程間協(xié)作的方法。這組方法跟上面方法的最明顯區(qū)別是:這幾個(gè)方法都位于Thread類中,而上面三個(gè)方法都位于Object類中。至于為什么,大家可以先思考一下。現(xiàn)在我們逐個(gè)分析sleep/yield/join方法: 1、sleepsleep方法的作用是讓當(dāng)前線程暫停指定的時(shí)間(毫秒),sleep方法是最簡單的方法,在上述的例子中也用到過,比較容易理解。唯一需要注意的是其與wait方法的區(qū)別。最簡單的區(qū)別是,wait方法依賴于同步,而sleep方法可以直接調(diào)用。而更深層次的區(qū)別在于sleep方法只是暫時(shí)讓出CPU的執(zhí)行權(quán),并不釋放鎖。而wait方法則需要釋放鎖。package com.paddx.test.concurrent; public class SleepTest { public synchronized void sleepMethod(){ System.out.println("Sleep start-----"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Sleep end-----"); } public synchronized void waitMethod(){ System.out.println("Wait start-----"); synchronized (this){ try { wait(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Wait end-----"); } public static void main(String[] args) { final SleepTest test1 = new SleepTest(); for(int i = 0;i<3;i++){ new Thread(new Runnable() { @Override public void run() { test1.sleepMethod(); } }).start(); } try { Thread.sleep(10000);//暫停十秒,等上面程序執(zhí)行完成 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----分割線-----"); final SleepTest test2 = new SleepTest(); for(int i = 0;i<3;i++){ new Thread(new Runnable() { @Override public void run() { test2.waitMethod(); } }).start(); } } } 執(zhí)行結(jié)果:Sleep start----- Sleep end----- Sleep start----- Sleep end----- Sleep start----- Sleep end----- -----分割線----- Wait start----- Wait start----- Wait start----- Wait end----- Wait end----- Wait end----- 這個(gè)結(jié)果的區(qū)別很明顯,通過sleep方法實(shí)現(xiàn)的暫停,程序是順序進(jìn)入同步塊的,只有當(dāng)上一個(gè)線程執(zhí)行完成的時(shí)候,下一個(gè)線程才能進(jìn)入同步方法,sleep暫停期間一直持有monitor對象鎖,其他線程是不能進(jìn)入的。而wait方法則不同,當(dāng)調(diào)用wait方法后,當(dāng)前線程會(huì)釋放持有的monitor對象鎖,因此,其他線程還可以進(jìn)入到同步方法,線程被喚醒后,需要競爭鎖,獲取到鎖之后再繼續(xù)執(zhí)行。 2、yield方法yield方法的作用是暫停當(dāng)前線程,以便其他線程有機(jī)會(huì)執(zhí)行,不過不能指定暫停的時(shí)間,并且也不能保證當(dāng)前線程馬上停止。yield方法只是將Running狀態(tài)轉(zhuǎn)變?yōu)镽unnable狀態(tài)。我們還是通過一個(gè)例子來演示其使用:package com.paddx.test.concurrent; public class YieldTest implements Runnable { @Override public void run() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName() + ": " + i); Thread.yield(); } } public static void main(String[] args) { YieldTest runn = new YieldTest(); Thread t1 = new Thread(runn,"FirstThread"); Thread t2 = new Thread(runn,"SecondThread"); t1.start(); t2.start(); } } 運(yùn)行結(jié)果如下:FirstThread: 0 SecondThread: 0 FirstThread: 1 SecondThread: 1 FirstThread: 2 SecondThread: 2 FirstThread: 3 SecondThread: 3 FirstThread: 4 SecondThread: 4 這個(gè)例子就是通過yield方法來實(shí)現(xiàn)兩個(gè)線程的交替執(zhí)行。不過請注意:這種交替并不一定能得到保證,源碼中也對這個(gè)問題進(jìn)行說明:/** * 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. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */ 這段話主要說明了三個(gè)問題:調(diào)度器可能會(huì)忽略該方法。使用的時(shí)候要仔細(xì)分析和測試,確保能達(dá)到預(yù)期的效果。很少有場景要用到該方法,主要使用的地方是調(diào)試和測試。 3、join方法join方法的作用是父線程等待子線程執(zhí)行完成后再執(zhí)行,換句話說就是將異步執(zhí)行的線程合并為同步的線程。JDK中提供三個(gè)版本的join方法,其實(shí)現(xiàn)與wait方法類似,join()方法實(shí)際上執(zhí)行的join(0),而join(long millis, int nanos)也與wait(long millis, int nanos)的實(shí)現(xiàn)方式一致,暫時(shí)對納秒的支持也是不完整的。我們可以看下join方法的源碼,這樣更容易理解:public final void join() throws InterruptedException { join(0); } public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } } public final synchronized void join(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } join(millis); } 大家重點(diǎn)關(guān)注一下join(long millis)方法的實(shí)現(xiàn),可以看出join方法就是通過wait方法來將線程的阻塞,如果join的線程還在執(zhí)行,則將當(dāng)前線程阻塞起來,直到j(luò)oin的線程執(zhí)行完成,當(dāng)前線程才能執(zhí)行。不過有一點(diǎn)需要注意,這里的join只調(diào)用了wait方法,卻沒有對應(yīng)的notify方法,原因是Thread的start方法中做了相應(yīng)的處理,所以當(dāng)join的線程執(zhí)行完成以后,會(huì)自動(dòng)喚醒主線程繼續(xù)往下執(zhí)行。下面我們通過一個(gè)例子來演示join方法的作用:(1)不使用join方法:package com.paddx.test.concurrent; public class JoinTest implements Runnable{ @Override public void run() { try { System.out.println(Thread.currentThread().getName() + ">執(zhí)行結(jié)果如下:Thread-0 start----- Thread-1 start----- Thread-2 start----- Thread-3 start----- Finished~~~ Thread-4 start----- Thread-2 end------ Thread-4 end------ Thread-1 end------ Thread-0 end------ Thread-3 end------ (2)使用join方法:package com.paddx.test.concurrent; public class JoinTest implements Runnable{ @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " start-----"); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " end------"); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { for (int i=0;i<5;i++) { Thread test = new Thread(new JoinTest()); test.start(); try { test.join(); //調(diào)用join方法 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Finished~~~"); } } 執(zhí)行結(jié)果如下:Thread-0 start----- Thread-0 end------ Thread-1 start----- Thread-1 end------ Thread-2 start----- Thread-2 end------ Thread-3 start----- Thread-3 end------ Thread-4 start----- Thread-4 end------ Finished~~~ 對比兩段代碼的執(zhí)行結(jié)果很容易發(fā)現(xiàn),在沒有使用join方法之間,線程是并發(fā)執(zhí)行的,而使用join方法后,所有線程是順序執(zhí)行的。 四、總結(jié)本文主要詳細(xì)講解了wait/notify/notifyAll和sleep/yield/join方法。最后回答一下上面提出的問題:wait/notify/notifyAll方法的作用是實(shí)現(xiàn)線程間的協(xié)作,那為什么這三個(gè)方法不是位于Thread類中,而是位于Object類中?位于Object中,也就相當(dāng)于所有類都包含這三個(gè)方法(因?yàn)镴ava中所有的類都繼承自O(shè)bject類)。要回答這個(gè)問題,還是得回過來看wait方法的實(shí)現(xiàn)原理,大家需要明白的是,wait等待的到底是什么東西?如果對上面內(nèi)容理解的比較好的話,我相信大家應(yīng)該很容易知道wait等待其實(shí)是對象monitor,由于Java中的每一個(gè)對象都有一個(gè)內(nèi)置的monitor對象,自然所有的類都理應(yīng)有wait/notify方法。暖冬回血~~~~大家好,我是AIO生活,關(guān)注我,后續(xù)連載更多技術(shù)重難點(diǎn),文章有不足之處,歡迎大家留言指正,謝謝大家啦!順便提一下,幫大家整理了些JAVA電子書,掃碼回復(fù)“1024”獲取電子書合集,也可簽到領(lǐng)現(xiàn)金紅包。
“線程間的協(xié)作有哪些”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。