摘要:典型地,和被用在等待另一個(gè)線(xiàn)程產(chǎn)生的結(jié)果的情形測(cè)試發(fā)現(xiàn)結(jié)果還沒(méi)有產(chǎn)生后,讓線(xiàn)程阻塞,另一個(gè)線(xiàn)程產(chǎn)生了結(jié)果后,調(diào)用使其恢復(fù)。使當(dāng)前線(xiàn)程放棄當(dāng)前已經(jīng)分得的時(shí)間,但不使當(dāng)前線(xiàn)程阻塞,即線(xiàn)程仍處于可執(zhí)行狀態(tài),隨時(shí)可能再次分得時(shí)間。
1、說(shuō)說(shuō)進(jìn)程,線(xiàn)程,協(xié)程之間的區(qū)別
簡(jiǎn)而言之,進(jìn)程是程序運(yùn)行和資源分配的基本單位,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線(xiàn)程.進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線(xiàn)程共享內(nèi)存資源,減少切換次數(shù),從而效率更高.線(xiàn)程是進(jìn)程的一個(gè)實(shí)體,是cpu調(diào)度和分派的基本單位,是比程序更小的能獨(dú)立運(yùn)行的基本單位.同一進(jìn)程中的多個(gè)線(xiàn)程之間可以并發(fā)執(zhí)行.
2、你了解守護(hù)線(xiàn)程嗎?它和非守護(hù)線(xiàn)程有什么區(qū)別
程序運(yùn)行完畢,jvm會(huì)等待非守護(hù)線(xiàn)程完成后關(guān)閉,但是jvm不會(huì)等待守護(hù)線(xiàn)程.守護(hù)線(xiàn)程最典型的例子就是GC線(xiàn)程
3、什么是多線(xiàn)程上下文切換
多線(xiàn)程的上下文切換是指CPU控制權(quán)由一個(gè)已經(jīng)正在運(yùn)行的線(xiàn)程切換到另外一個(gè)就緒并等待獲取CPU執(zhí)行權(quán)的線(xiàn)程的過(guò)程。
4、創(chuàng)建兩種線(xiàn)程的方式?他們有什么區(qū)別?
通過(guò)實(shí)現(xiàn)java.lang.Runnable或者通過(guò)擴(kuò)展java.lang.Thread類(lèi).相比擴(kuò)展Thread,實(shí)現(xiàn)Runnable接口可能更優(yōu).原因有二:
Java不支持多繼承.因此擴(kuò)展Thread類(lèi)就代表這個(gè)子類(lèi)不能擴(kuò)展其他類(lèi).而實(shí)現(xiàn)Runnable接口的類(lèi)還可能擴(kuò)展另一個(gè)類(lèi).
類(lèi)可能只要求可執(zhí)行即可,因此繼承整個(gè)Thread類(lèi)的開(kāi)銷(xiāo)過(guò)大.
5、Thread類(lèi)中的start()和run()方法有什么區(qū)別?
start()方法被用來(lái)啟動(dòng)新創(chuàng)建的線(xiàn)程,而且start()內(nèi)部調(diào)用了run()方法,這和直接調(diào)用run()方法的效果不一樣。當(dāng)你調(diào)用run()方法的時(shí)候,只會(huì)是在原來(lái)的線(xiàn)程中調(diào)用,沒(méi)有新的線(xiàn)程啟動(dòng),start()方法才會(huì)啟動(dòng)新線(xiàn)程。
6、怎么檢測(cè)一個(gè)線(xiàn)程是否持有對(duì)象監(jiān)視器
Thread類(lèi)提供了一個(gè)holdsLock(Object obj)方法,當(dāng)且僅當(dāng)對(duì)象obj的監(jiān)視器被某條線(xiàn)程持有的時(shí)候才會(huì)返回true,注意這是一個(gè)static方法,這意味著”某條線(xiàn)程”指的是當(dāng)前線(xiàn)程。
7、Runnable和Callable的區(qū)別
Runnable接口中的run()方法的返回值是void,它做的事情只是純粹地去執(zhí)行run()方法中的代碼而已;Callable接口中的call()方法是有返回值的,是一個(gè)泛型,和Future、FutureTask配合可以用來(lái)獲取異步執(zhí)行的結(jié)果。
這其實(shí)是很有用的一個(gè)特性,因?yàn)槎嗑€(xiàn)程相比單線(xiàn)程更難、更復(fù)雜的一個(gè)重要原因就是因?yàn)槎嗑€(xiàn)程充滿(mǎn)著未知性,某條線(xiàn)程是否執(zhí)行了?某條線(xiàn)程執(zhí)行了多久?某條線(xiàn)程執(zhí)行的時(shí)候我們期望的數(shù)據(jù)是否已經(jīng)賦值完畢?無(wú)法得知,我們能做的只是等待這條多線(xiàn)程的任務(wù)執(zhí)行完畢而已。而Callable+Future/FutureTask卻可以方便獲取多線(xiàn)程運(yùn)行的結(jié)果,可以在等待時(shí)間太長(zhǎng)沒(méi)獲取到需要的數(shù)據(jù)的情況下取消該線(xiàn)程的任務(wù)
8、什么導(dǎo)致線(xiàn)程阻塞
阻塞指的是暫停一個(gè)線(xiàn)程的執(zhí)行以等待某個(gè)條件發(fā)生(如某資源就緒),學(xué)過(guò)操作系統(tǒng)的同學(xué)對(duì)它一定已經(jīng)很熟悉了。Java 提供了大量方法來(lái)支持阻塞,下面讓我們逐一分析。
方法 | 說(shuō)明 |
---|---|
sleep() | sleep() 允許 指定以毫秒為單位的一段時(shí)間作為參數(shù),它使得線(xiàn)程在指定的時(shí)間內(nèi)進(jìn)入阻塞狀態(tài),不能得到CPU 時(shí)間,指定的時(shí)間一過(guò),線(xiàn)程重新進(jìn)入可執(zhí)行狀態(tài)。 典型地,sleep() 被用在等待某個(gè)資源就緒的情形:測(cè)試發(fā)現(xiàn)條件不滿(mǎn)足后,讓線(xiàn)程阻塞一段時(shí)間后重新測(cè)試,直到條件滿(mǎn)足為止 |
suspend() 和 resume() | 兩個(gè)方法配套使用,suspend()使得線(xiàn)程進(jìn)入阻塞狀態(tài),并且不會(huì)自動(dòng)恢復(fù),必須其對(duì)應(yīng)的resume() 被調(diào)用,才能使得線(xiàn)程重新進(jìn)入可執(zhí)行狀態(tài)。典型地,suspend() 和 resume() 被用在等待另一個(gè)線(xiàn)程產(chǎn)生的結(jié)果的情形:測(cè)試發(fā)現(xiàn)結(jié)果還沒(méi)有產(chǎn)生后,讓線(xiàn)程阻塞,另一個(gè)線(xiàn)程產(chǎn)生了結(jié)果后,調(diào)用 resume() 使其恢復(fù)。 |
yield() | yield() 使當(dāng)前線(xiàn)程放棄當(dāng)前已經(jīng)分得的CPU 時(shí)間,但不使當(dāng)前線(xiàn)程阻塞,即線(xiàn)程仍處于可執(zhí)行狀態(tài),隨時(shí)可能再次分得 CPU 時(shí)間。調(diào)用 yield() 的效果等價(jià)于調(diào)度程序認(rèn)為該線(xiàn)程已執(zhí)行了足夠的時(shí)間從而轉(zhuǎn)到另一個(gè)線(xiàn)程 |
wait() 和 notify() | 兩個(gè)方法配套使用,wait() 使得線(xiàn)程進(jìn)入阻塞狀態(tài),它有兩種形式,一種允許 指定以毫秒為單位的一段時(shí)間作為參數(shù),另一種沒(méi)有參數(shù),前者當(dāng)對(duì)應(yīng)的 notify() 被調(diào)用或者超出指定時(shí)間時(shí)線(xiàn)程重新進(jìn)入可執(zhí)行狀態(tài),后者則必須對(duì)應(yīng)的 notify() 被調(diào)用. |
9、wait(),notify()和suspend(),resume()之間的區(qū)別
初看起來(lái)它們與 suspend() 和 resume() 方法對(duì)沒(méi)有什么分別,但是事實(shí)上它們是截然不同的。區(qū)別的核心在于,前面敘述的所有方法,阻塞時(shí)都不會(huì)釋放占用的鎖(如果占用了的話(huà)),而這一對(duì)方法則相反。上述的核心區(qū)別導(dǎo)致了一系列的細(xì)節(jié)上的區(qū)別。
首先,前面敘述的所有方法都隸屬于 Thread 類(lèi),但是這一對(duì)卻直接隸屬于 Object 類(lèi),也就是說(shuō),所有對(duì)象都擁有這一對(duì)方法。初看起來(lái)這十分不可思議,但是實(shí)際上卻是很自然的,因?yàn)檫@一對(duì)方法阻塞時(shí)要釋放占用的鎖,而鎖是任何對(duì)象都具有的,調(diào)用任意對(duì)象的 wait() 方法導(dǎo)致線(xiàn)程阻塞,并且該對(duì)象上的鎖被釋放。而調(diào)用 任意對(duì)象的notify()方法則導(dǎo)致從調(diào)用該對(duì)象的 wait() 方法而阻塞的線(xiàn)程中隨機(jī)選擇的一個(gè)解除阻塞(但要等到獲得鎖后才真正可執(zhí)行)。
其次,前面敘述的所有方法都可在任何位置調(diào)用,但是這一對(duì)方法卻必須在 synchronized 方法或塊中調(diào)用,理由也很簡(jiǎn)單,只有在synchronized 方法或塊中當(dāng)前線(xiàn)程才占有鎖,才有鎖可以釋放。同樣的道理,調(diào)用這一對(duì)方法的對(duì)象上的鎖必須為當(dāng)前線(xiàn)程所擁有,這樣才有鎖可以釋放。因此,這一對(duì)方法調(diào)用必須放置在這樣的 synchronized 方法或塊中,該方法或塊的上鎖對(duì)象就是調(diào)用這一對(duì)方法的對(duì)象。若不滿(mǎn)足這一條件,則程序雖然仍能編譯,但在運(yùn)行時(shí)會(huì)出現(xiàn)IllegalMonitorStateException 異常。
wait() 和 notify() 方法的上述特性決定了它們經(jīng)常和synchronized關(guān)鍵字一起使用,將它們和操作系統(tǒng)進(jìn)程間通信機(jī)制作一個(gè)比較就會(huì)發(fā)現(xiàn)它們的相似性:synchronized方法或塊提供了類(lèi)似于操作系統(tǒng)原語(yǔ)的功能,它們的執(zhí)行不會(huì)受到多線(xiàn)程機(jī)制的干擾,而這一對(duì)方法則相當(dāng)于 block 和wakeup 原語(yǔ)(這一對(duì)方法均聲明為 synchronized)。它們的結(jié)合使得我們可以實(shí)現(xiàn)操作系統(tǒng)上一系列精妙的進(jìn)程間通信的算法(如信號(hào)量算法),并用于解決各種復(fù)雜的線(xiàn)程間通信問(wèn)題。
關(guān)于 wait() 和 notify() 方法最后再說(shuō)明兩點(diǎn):
第一:調(diào)用 notify() 方法導(dǎo)致解除阻塞的線(xiàn)程是從因調(diào)用該對(duì)象的 wait() 方法而阻塞的線(xiàn)程中隨機(jī)選取的,我們無(wú)法預(yù)料哪一個(gè)線(xiàn)程將會(huì)被選擇,所以編程時(shí)要特別小心,避免因這種不確定性而產(chǎn)生問(wèn)題。
第二:除了 notify(),還有一個(gè)方法 notifyAll() 也可起到類(lèi)似作用,唯一的區(qū)別在于,調(diào)用 notifyAll() 方法將把因調(diào)用該對(duì)象的 wait() 方法而阻塞的所有線(xiàn)程一次性全部解除阻塞。當(dāng)然,只有獲得鎖的那一個(gè)線(xiàn)程才能進(jìn)入可執(zhí)行狀態(tài)。
談到阻塞,就不能不談一談死鎖,略一分析就能發(fā)現(xiàn),suspend() 方法和不指定超時(shí)期限的 wait() 方法的調(diào)用都可能產(chǎn)生死鎖。遺憾的是,Java 并不在語(yǔ)言級(jí)別上支持死鎖的避免,我們?cè)诰幊讨斜仨毿⌒牡乇苊馑梨i。
以上我們對(duì) Java 中實(shí)現(xiàn)線(xiàn)程阻塞的各種方法作了一番分析,我們重點(diǎn)分析了 wait() 和 notify() 方法,因?yàn)樗鼈兊墓δ茏顝?qiáng)大,使用也最靈活,但是這也導(dǎo)致了它們的效率較低,較容易出錯(cuò)。實(shí)際使用中我們應(yīng)該靈活使用各種方法,以便更好地達(dá)到我們的目的。
11、產(chǎn)生死鎖的條件
1.互斥條件:一個(gè)資源每次只能被一個(gè)進(jìn)程使用。
2.請(qǐng)求與保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放。
3.不剝奪條件:進(jìn)程已獲得的資源,在末使用完之前,不能強(qiáng)行剝奪。
4.循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
12、為什么wait()方法和notify()/notifyAll()方法要在同步塊中被調(diào)用
這是JDK強(qiáng)制的,wait()方法和notify()/notifyAll()方法在調(diào)用前都必須先獲得對(duì)象的鎖
wait()方法和notify()/notifyAll()方法在放棄對(duì)象監(jiān)視器時(shí)有什么區(qū)別
wait()方法和notify()/notifyAll()方法在放棄對(duì)象監(jiān)視器的時(shí)候的區(qū)別在于:wait()方法立即釋放對(duì)象監(jiān)視器,notify()/notifyAll()方法則會(huì)等待線(xiàn)程剩余代碼執(zhí)行完畢才會(huì)放棄對(duì)象監(jiān)視器。
13、wait()與sleep()的區(qū)別
關(guān)于這兩者已經(jīng)在上面進(jìn)行詳細(xì)的說(shuō)明,這里就做個(gè)概括好了:
sleep()來(lái)自Thread類(lèi),和wait()來(lái)自O(shè)bject類(lèi).調(diào)用sleep()方法的過(guò)程中,線(xiàn)程不會(huì)釋放對(duì)象鎖。而 調(diào)用 wait 方法線(xiàn)程會(huì)釋放對(duì)象鎖
sleep()睡眠后不出讓系統(tǒng)資源,wait讓其他線(xiàn)程可以占用CPU
sleep(milliseconds)需要指定一個(gè)睡眠時(shí)間,時(shí)間一到會(huì)自動(dòng)喚醒.而wait()需要配合notify()或者notifyAll()使用
14、為什么wait,nofity和nofityAll這些方法不放在Thread類(lèi)當(dāng)中
一個(gè)很明顯的原因是JAVA提供的鎖是對(duì)象級(jí)的而不是線(xiàn)程級(jí)的,每個(gè)對(duì)象都有鎖,通過(guò)線(xiàn)程獲得。如果線(xiàn)程需要等待某些鎖那么調(diào)用對(duì)象中的wait()方法就有意義了。如果wait()方法定義在Thread類(lèi)中,線(xiàn)程正在等待的是哪個(gè)鎖就不明顯了。簡(jiǎn)單的說(shuō),由于wait,notify和notifyAll都是鎖級(jí)別的操作,所以把他們定義在Object類(lèi)中因?yàn)殒i屬于對(duì)象。
15、怎么喚醒一個(gè)阻塞的線(xiàn)程
如果線(xiàn)程是因?yàn)檎{(diào)用了wait()、sleep()或者join()方法而導(dǎo)致的阻塞,可以中斷線(xiàn)程,并且通過(guò)拋出InterruptedException來(lái)喚醒它;如果線(xiàn)程遇到了IO阻塞,無(wú)能為力,因?yàn)镮O是操作系統(tǒng)實(shí)現(xiàn)的,Java代碼并沒(méi)有辦法直接接觸到操作系統(tǒng)。
16、什么是多線(xiàn)程的上下文切換
多線(xiàn)程的上下文切換是指CPU控制權(quán)由一個(gè)已經(jīng)正在運(yùn)行的線(xiàn)程切換到另外一個(gè)就緒并等待獲取CPU執(zhí)行權(quán)的線(xiàn)程的過(guò)程。
17、synchronized和ReentrantLock的區(qū)別
synchronized是和if、else、for、while一樣的關(guān)鍵字,ReentrantLock是類(lèi),這是二者的本質(zhì)區(qū)別。既然ReentrantLock是類(lèi),那么它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類(lèi)變量,ReentrantLock比synchronized的擴(kuò)展性體現(xiàn)在幾點(diǎn)上:
(1)ReentrantLock可以對(duì)獲取鎖的等待時(shí)間進(jìn)行設(shè)置,這樣就避免了死鎖
(2)ReentrantLock可以獲取各種鎖的信息
(3)ReentrantLock可以靈活地實(shí)現(xiàn)多路通知
另外,二者的鎖機(jī)制其實(shí)也是不一樣的:ReentrantLock底層調(diào)用的是Unsafe的park方法加鎖,synchronized操作的應(yīng)該是對(duì)象頭中mark word.
18、FutureTask是什么
這個(gè)其實(shí)前面有提到過(guò),F(xiàn)utureTask表示一個(gè)異步運(yùn)算的任務(wù)。FutureTask里面可以傳入一個(gè)Callable的具體實(shí)現(xiàn)類(lèi),可以對(duì)這個(gè)異步運(yùn)算的任務(wù)的結(jié)果進(jìn)行等待獲取、判斷是否已經(jīng)完成、取消任務(wù)等操作。當(dāng)然,由于FutureTask也是Runnable接口的實(shí)現(xiàn)類(lèi),所以FutureTask也可以放入線(xiàn)程池中。
19、一個(gè)線(xiàn)程如果出現(xiàn)了運(yùn)行時(shí)異常怎么辦?
如果這個(gè)異常沒(méi)有被捕獲的話(huà),這個(gè)線(xiàn)程就停止執(zhí)行了。另外重要的一點(diǎn)是:如果這個(gè)線(xiàn)程持有某個(gè)某個(gè)對(duì)象的監(jiān)視器,那么這個(gè)對(duì)象監(jiān)視器會(huì)被立即釋放
20、Java當(dāng)中有哪幾種鎖
自旋鎖: 自旋鎖在JDK1.6之后就默認(rèn)開(kāi)啟了?;谥暗挠^察,共享數(shù)據(jù)的鎖定狀態(tài)只會(huì)持續(xù)很短的時(shí)間,為了這一小段時(shí)間而去掛起和恢復(fù)線(xiàn)程有點(diǎn)浪費(fèi),所以這里就做了一個(gè)處理,讓后面請(qǐng)求鎖的那個(gè)線(xiàn)程在稍等一會(huì),但是不放棄處理器的執(zhí)行時(shí)間,看看持有鎖的線(xiàn)程能否快速釋放。為了讓線(xiàn)程等待,所以需要讓線(xiàn)程執(zhí)行一個(gè)忙循環(huán)也就是自旋操作。在jdk6之后,引入了自適應(yīng)的自旋鎖,也就是等待的時(shí)間不再固定了,而是由上一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者狀態(tài)來(lái)決定
偏向鎖: 在JDK1.之后引入的一項(xiàng)鎖優(yōu)化,目的是消除數(shù)據(jù)在無(wú)競(jìng)爭(zhēng)情況下的同步原語(yǔ)。進(jìn)一步提升程序的運(yùn)行性能。 偏向鎖就是偏心的偏,意思是這個(gè)鎖會(huì)偏向第一個(gè)獲得他的線(xiàn)程,如果接下來(lái)的執(zhí)行過(guò)程中,改鎖沒(méi)有被其他線(xiàn)程獲取,則持有偏向鎖的線(xiàn)程將永遠(yuǎn)不需要再進(jìn)行同步。偏向鎖可以提高帶有同步但無(wú)競(jìng)爭(zhēng)的程序性能,也就是說(shuō)他并不一定總是對(duì)程序運(yùn)行有利,如果程序中大多數(shù)的鎖都是被多個(gè)不同的線(xiàn)程訪(fǎng)問(wèn),那偏向模式就是多余的,在具體問(wèn)題具體分析的前提下,可以考慮是否使用偏向鎖。
輕量級(jí)鎖: 為了減少獲得鎖和釋放鎖所帶來(lái)的性能消耗,引入了“偏向鎖”和“輕量級(jí)鎖”,所以在Java SE1.6里鎖一共有四種狀態(tài),無(wú)鎖狀態(tài),偏向鎖狀態(tài),輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài),它會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)。鎖可以升級(jí)但不能降級(jí),意味著偏向鎖升級(jí)成輕量級(jí)鎖后不能降級(jí)成偏向鎖
21、如何在兩個(gè)線(xiàn)程間共享數(shù)據(jù)
通過(guò)在線(xiàn)程之間共享對(duì)象就可以了,然后通過(guò)wait/notify/notifyAll、await/signal/signalAll進(jìn)行喚起和等待,比方說(shuō)阻塞隊(duì)列BlockingQueue就是為線(xiàn)程之間共享數(shù)據(jù)而設(shè)計(jì)的
22、如何正確的使用wait()?使用if還是while?
wait() 方法應(yīng)該在循環(huán)調(diào)用,因?yàn)楫?dāng)線(xiàn)程獲取到 CPU 開(kāi)始執(zhí)行的時(shí)候,其他條件可能還沒(méi)有滿(mǎn)足,所以在處理前,循環(huán)檢測(cè)條件是否滿(mǎn)足會(huì)更好。下面是一段標(biāo)準(zhǔn)的使用 wait 和 notify 方法的代碼:
synchronized (obj) { while (condition does not hold) obj.wait(); // (Releases lock, and reacquires on wakeup) ... // Perform action appropriate to condition }
23、什么是線(xiàn)程局部變量ThreadLocal
線(xiàn)程局部變量是局限于線(xiàn)程內(nèi)部的變量,屬于線(xiàn)程自身所有,不在多個(gè)線(xiàn)程間共享。Java提供ThreadLocal類(lèi)來(lái)支持線(xiàn)程局部變量,是一種實(shí)現(xiàn)線(xiàn)程安全的方式。但是在管理環(huán)境下(如 web 服務(wù)器)使用線(xiàn)程局部變量的時(shí)候要特別小心,在這種情況下,工作線(xiàn)程的生命周期比任何應(yīng)用變量的生命周期都要長(zhǎng)。任何線(xiàn)程局部變量一旦在工作完成后沒(méi)有釋放,Java 應(yīng)用就存在內(nèi)存泄露的風(fēng)險(xiǎn)。
24、ThreadLoal的作用是什么?
簡(jiǎn)單說(shuō)ThreadLocal就是一種以空間換時(shí)間的做法在每個(gè)Thread里面維護(hù)了一個(gè)ThreadLocal.ThreadLocalMap把數(shù)據(jù)進(jìn)行隔離,數(shù)據(jù)不共享,自然就沒(méi)有線(xiàn)程安全方面的問(wèn)題了.
25、生產(chǎn)者消費(fèi)者模型的作用是什么?
(1)通過(guò)平衡生產(chǎn)者的生產(chǎn)能力和消費(fèi)者的消費(fèi)能力來(lái)提升整個(gè)系統(tǒng)的運(yùn)行效率,這是生產(chǎn)者消費(fèi)者模型最重要的作用
(2)解耦,這是生產(chǎn)者消費(fèi)者模型附帶的作用,解耦意味著生產(chǎn)者和消費(fèi)者之間的聯(lián)系少,聯(lián)系越少越可以獨(dú)自發(fā)展而不需要收到相互的制約
26.寫(xiě)一個(gè)生產(chǎn)者-消費(fèi)者隊(duì)列
可以通過(guò)阻塞隊(duì)列實(shí)現(xiàn),也可以通過(guò)wait-notify來(lái)實(shí)現(xiàn).
使用阻塞隊(duì)列來(lái)實(shí)現(xiàn)
//消費(fèi)者 public class Producer implements Runnable{ private final BlockingQueuequeue; public Producer(BlockingQueue q){ this.queue=q; } @Override public void run() { try { while (true){ Thread.sleep(1000);//模擬耗時(shí) queue.put(produce()); } }catch (InterruptedException e){ } } private int produce() { int n=new Random().nextInt(10000); System.out.println("Thread:" + Thread.currentThread().getId() + " produce:" + n); return n; } } //消費(fèi)者 public class Consumer implements Runnable { private final BlockingQueue queue; public Consumer(BlockingQueue q){ this.queue=q; } @Override public void run() { while (true){ try { Thread.sleep(2000);//模擬耗時(shí) consume(queue.take()); }catch (InterruptedException e){ } } } private void consume(Integer n) { System.out.println("Thread:" + Thread.currentThread().getId() + " consume:" + n); } } //測(cè)試 public class Main { public static void main(String[] args) { BlockingQueue queue=new ArrayBlockingQueue (100); Producer p=new Producer(queue); Consumer c1=new Consumer(queue); Consumer c2=new Consumer(queue); new Thread(p).start(); new Thread(c1).start(); new Thread(c2).start(); } }
使用wait-notify來(lái)實(shí)現(xiàn)
該種方式應(yīng)該最經(jīng)典,這里就不做說(shuō)明了
27、如果你提交任務(wù)時(shí),線(xiàn)程池隊(duì)列已滿(mǎn),這時(shí)會(huì)發(fā)生什么
如果你使用的LinkedBlockingQueue,也就是無(wú)界隊(duì)列的話(huà),沒(méi)關(guān)系,繼續(xù)添加任務(wù)到阻塞隊(duì)列中等待執(zhí)行,因?yàn)長(zhǎng)inkedBlockingQueue可以近乎認(rèn)為是一個(gè)無(wú)窮大的隊(duì)列,可以無(wú)限存放任務(wù);如果你使用的是有界隊(duì)列比方說(shuō)ArrayBlockingQueue的話(huà),任務(wù)首先會(huì)被添加到ArrayBlockingQueue中,ArrayBlockingQueue滿(mǎn)了,則會(huì)使用拒絕策略RejectedExecutionHandler處理滿(mǎn)了的任務(wù),默認(rèn)是AbortPolicy。
28、為什么要使用線(xiàn)程池
避免頻繁地創(chuàng)建和銷(xiāo)毀線(xiàn)程,達(dá)到線(xiàn)程對(duì)象的重用。另外,使用線(xiàn)程池還可以根據(jù)項(xiàng)目靈活地控制并發(fā)的數(shù)目。
29、java中用到的線(xiàn)程調(diào)度算法是什么
搶占式。一個(gè)線(xiàn)程用完CPU之后,操作系統(tǒng)會(huì)根據(jù)線(xiàn)程優(yōu)先級(jí)、線(xiàn)程饑餓情況等數(shù)據(jù)算出一個(gè)總的優(yōu)先級(jí)并分配下一個(gè)時(shí)間片給某個(gè)線(xiàn)程執(zhí)行。
30、Thread.sleep(0)的作用是什么
由于Java采用搶占式的線(xiàn)程調(diào)度算法,因此可能會(huì)出現(xiàn)某條線(xiàn)程常常獲取到CPU控制權(quán)的情況,為了讓某些優(yōu)先級(jí)比較低的線(xiàn)程也能獲取到CPU控制權(quán),可以使用Thread.sleep(0)手動(dòng)觸發(fā)一次操作系統(tǒng)分配時(shí)間片的操作,這也是平衡CPU控制權(quán)的一種操作。
31、什么是CAS
CAS,全稱(chēng)為Compare and Swap,即比較-替換。假設(shè)有三個(gè)操作數(shù):內(nèi)存值V、舊的預(yù)期值A(chǔ)、要修改的值B,當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),才會(huì)將內(nèi)存值修改為B并返回true,否則什么都不做并返回false。當(dāng)然CAS一定要volatile變量配合,這樣才能保證每次拿到的變量是主內(nèi)存中最新的那個(gè)值,否則舊的預(yù)期值A(chǔ)對(duì)某條線(xiàn)程來(lái)說(shuō),永遠(yuǎn)是一個(gè)不會(huì)變的值A(chǔ),只要某次CAS操作失敗,永遠(yuǎn)都不可能成功
32、什么是樂(lè)觀鎖和悲觀鎖
樂(lè)觀鎖:樂(lè)觀鎖認(rèn)為競(jìng)爭(zhēng)不總是會(huì)發(fā)生,因此它不需要持有鎖,將比較-替換這兩個(gè)動(dòng)作作為一個(gè)原子操作嘗試去修改內(nèi)存中的變量,如果失敗則表示發(fā)生沖突,那么就應(yīng)該有相應(yīng)的重試邏輯。
悲觀鎖:悲觀鎖認(rèn)為競(jìng)爭(zhēng)總是會(huì)發(fā)生,因此每次對(duì)某資源進(jìn)行操作時(shí),都會(huì)持有一個(gè)獨(dú)占的鎖,就像synchronized,不管三七二十一,直接上了鎖就操作資源了。
33、ConcurrentHashMap的并發(fā)度是什么?
ConcurrentHashMap的并發(fā)度就是segment的大小,默認(rèn)為16,這意味著最多同時(shí)可以有16條線(xiàn)程操作ConcurrentHashMap,這也是ConcurrentHashMap對(duì)Hashtable的最大優(yōu)勢(shì),任何情況下,Hashtable能同時(shí)有兩條線(xiàn)程獲取Hashtable中的數(shù)據(jù)嗎?
34、ConcurrentHashMap的工作原理
ConcurrentHashMap在jdk 1.6和jdk 1.8實(shí)現(xiàn)原理是不同的.
jdk 1.6:
ConcurrentHashMap是線(xiàn)程安全的,但是與Hashtablea相比,實(shí)現(xiàn)線(xiàn)程安全的方式不同。Hashtable是通過(guò)對(duì)hash表結(jié)構(gòu)進(jìn)行鎖定,是阻塞式的,當(dāng)一個(gè)線(xiàn)程占有這個(gè)鎖時(shí),其他線(xiàn)程必須阻塞等待其釋放鎖。ConcurrentHashMap是采用分離鎖的方式,它并沒(méi)有對(duì)整個(gè)hash表進(jìn)行鎖定,而是局部鎖定,也就是說(shuō)當(dāng)一個(gè)線(xiàn)程占有這個(gè)局部鎖時(shí),不影響其他線(xiàn)程對(duì)hash表其他地方的訪(fǎng)問(wèn)。
具體實(shí)現(xiàn):ConcurrentHashMap內(nèi)部有一個(gè)Segment
jdk 1.8
在jdk 8中,ConcurrentHashMap不再使用Segment分離鎖,而是采用一種樂(lè)觀鎖CAS算法來(lái)實(shí)現(xiàn)同步問(wèn)題,但其底層還是“數(shù)組+鏈表->紅黑樹(shù)”的實(shí)現(xiàn)。
37、CyclicBarrier和CountDownLatch區(qū)別
這兩個(gè)類(lèi)非常類(lèi)似,都在java.util.concurrent下,都可以用來(lái)表示代碼運(yùn)行到某個(gè)點(diǎn)上,二者的區(qū)別在于:
CyclicBarrier的某個(gè)線(xiàn)程運(yùn)行到某個(gè)點(diǎn)上之后,該線(xiàn)程即停止運(yùn)行,直到所有的線(xiàn)程都到達(dá)了這個(gè)點(diǎn),所有線(xiàn)程才重新運(yùn)行;CountDownLatch則不是,某線(xiàn)程運(yùn)行到某個(gè)點(diǎn)上之后,只是給某個(gè)數(shù)值-1而已,該線(xiàn)程繼續(xù)運(yùn)行
CyclicBarrier只能喚起一個(gè)任務(wù),CountDownLatch可以喚起多個(gè)任務(wù)
CyclicBarrier可重用,CountDownLatch不可重用,計(jì)數(shù)值為0該CountDownLatch就不可再用了
39、java中的++操作符線(xiàn)程安全么?
不是線(xiàn)程安全的操作。它涉及到多個(gè)指令,如讀取變量值,增加,然后存儲(chǔ)回內(nèi)存,這個(gè)過(guò)程可能會(huì)出現(xiàn)多個(gè)線(xiàn)程交差
40、你有哪些多線(xiàn)程開(kāi)發(fā)良好的實(shí)踐?
給線(xiàn)程命名
最小化同步范圍
優(yōu)先使用volatile
盡可能使用更高層次的并發(fā)工具而非wait和notify()來(lái)實(shí)現(xiàn)線(xiàn)程通信,如BlockingQueue,Semeaphore
優(yōu)先使用并發(fā)容器而非同步容器.
考慮使用線(xiàn)程池
關(guān)于volatile關(guān)鍵字1、可以創(chuàng)建Volatile數(shù)組嗎?
Java 中可以創(chuàng)建 volatile類(lèi)型數(shù)組,不過(guò)只是一個(gè)指向數(shù)組的引用,而不是整個(gè)數(shù)組。如果改變引用指向的數(shù)組,將會(huì)受到volatile 的保護(hù),但是如果多個(gè)線(xiàn)程同時(shí)改變數(shù)組的元素,volatile標(biāo)示符就不能起到之前的保護(hù)作用了
2、volatile能使得一個(gè)非原子操作變成原子操作嗎?
一個(gè)典型的例子是在類(lèi)中有一個(gè) long 類(lèi)型的成員變量。如果你知道該成員變量會(huì)被多個(gè)線(xiàn)程訪(fǎng)問(wèn),如計(jì)數(shù)器、價(jià)格等,你最好是將其設(shè)置為 volatile。為什么?因?yàn)?Java 中讀取 long 類(lèi)型變量不是原子的,需要分成兩步,如果一個(gè)線(xiàn)程正在修改該 long 變量的值,另一個(gè)線(xiàn)程可能只能看到該值的一半(前 32 位)。但是對(duì)一個(gè) volatile 型的 long 或 double 變量的讀寫(xiě)是原子。
一種實(shí)踐是用 volatile 修飾 long 和 double 變量,使其能按原子類(lèi)型來(lái)讀寫(xiě)。double 和 long 都是64位寬,因此對(duì)這兩種類(lèi)型的讀是分為兩部分的,第一次讀取第一個(gè) 32 位,然后再讀剩下的 32 位,這個(gè)過(guò)程不是原子的,但 Java 中 volatile 型的 long 或 double 變量的讀寫(xiě)是原子的。volatile 修復(fù)符的另一個(gè)作用是提供內(nèi)存屏障(memory barrier),例如在分布式框架中的應(yīng)用。簡(jiǎn)單的說(shuō),就是當(dāng)你寫(xiě)一個(gè) volatile 變量之前,Java 內(nèi)存模型會(huì)插入一個(gè)寫(xiě)屏障(write barrier),讀一個(gè) volatile 變量之前,會(huì)插入一個(gè)讀屏障(read barrier)。意思就是說(shuō),在你寫(xiě)一個(gè) volatile 域時(shí),能保證任何線(xiàn)程都能看到你寫(xiě)的值,同時(shí),在寫(xiě)之前,也能保證任何數(shù)值的更新對(duì)所有線(xiàn)程是可見(jiàn)的,因?yàn)閮?nèi)存屏障會(huì)將其他所有寫(xiě)的值更新到緩存。
3、volatile類(lèi)型變量提供什么保證?
volatile 主要有兩方面的作用:1.避免指令重排2.可見(jiàn)性保證.例如,JVM 或者 JIT為了獲得更好的性能會(huì)對(duì)語(yǔ)句重排序,但是 volatile 類(lèi)型變量即使在沒(méi)有同步塊的情況下賦值也不會(huì)與其他語(yǔ)句重排序。 volatile 提供 happens-before 的保證,確保一個(gè)線(xiàn)程的修改能對(duì)其他線(xiàn)程是可見(jiàn)的。某些情況下,volatile 還能提供原子性,如讀 64 位數(shù)據(jù)類(lèi)型,像 long 和 double 都不是原子的(低32位和高32位),但 volatile 類(lèi)型的 double 和 long 就是原子的.
微信掃一掃關(guān)注公眾號(hào)【好好學(xué)java】,優(yōu)質(zhì)文章第一時(shí)間了解
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76495.html
摘要:做一個(gè)靠譜且有責(zé)任心的人很多公司在內(nèi)部的面試細(xì)則上面都會(huì)注明這一點(diǎn),如果價(jià)值觀或是人品問(wèn)題會(huì)直接否決。沒(méi)有一個(gè)面試官不想找一個(gè)技術(shù)出眾又有責(zé)任心的人,請(qǐng)相信我,責(zé)任心非常重要,更有利于今后的晉升。 關(guān)注微信公眾號(hào):進(jìn)擊的java程序員K 每日精選BAT技術(shù)文章,面試真題,源碼資料。 今天分享的BAT等一線(xiàn)互聯(lián)網(wǎng)公司面試經(jīng)驗(yàn): 面試前的心態(tài)準(zhǔn)備(3點(diǎn)建議)技術(shù)硬實(shí)力包含的范圍(50題目...
摘要:做一個(gè)靠譜且有責(zé)任心的人很多公司在內(nèi)部的面試細(xì)則上面都會(huì)注明這一點(diǎn),如果價(jià)值觀或是人品問(wèn)題會(huì)直接否決。沒(méi)有一個(gè)面試官不想找一個(gè)技術(shù)出眾又有責(zé)任心的人,請(qǐng)相信我,責(zé)任心非常重要,更有利于今后的晉升。 關(guān)注微信公眾號(hào):進(jìn)擊的java程序員K 每日精選BAT技術(shù)文章,面試真題,源碼資料。 今天分享的BAT等一線(xiàn)互聯(lián)網(wǎng)公司面試經(jīng)驗(yàn): 面試前的心態(tài)準(zhǔn)備(3點(diǎn)建議)技術(shù)硬實(shí)力包含的范圍(50題目...
閱讀 1780·2021-10-13 09:39
閱讀 1357·2019-08-30 13:58
閱讀 1445·2019-08-29 16:42
閱讀 3592·2019-08-29 15:41
閱讀 3017·2019-08-29 15:11
閱讀 2550·2019-08-29 14:10
閱讀 3440·2019-08-29 13:29
閱讀 2120·2019-08-26 13:27