關(guān)于多線程的問題及答案三關(guān)于多線程的問題及答案三
這些多線程的問題辛臊,有些來源于各大網(wǎng)站鸭轮、有些來源于自己的思考藕畔∏砀唬可能有些問題網(wǎng)上有盘榨、可能有些問題對(duì)應(yīng)的答案也有娜搂、也可能有些各位網(wǎng)友也都看過漠秋,但是本文寫作的重心就是所有的問題都會(huì)按照自己的理解回答一遍懈息,不會(huì)去看網(wǎng)上的答案,因此可能有些問題講的不對(duì)咨跌,能指正的希望大家不吝指教沪么。
17、怎么檢測一個(gè)線程是否持有對(duì)象監(jiān)視器
我也是在網(wǎng)上看到一道多線程面試題才知道有方法可以判斷某個(gè)線程是否持有對(duì)象監(jiān)視器:Thread類提供了一個(gè)holdsLock(Object obj)方法锌半,當(dāng)且僅當(dāng)對(duì)象obj的監(jiān)視器被某條線程持有的時(shí)候才會(huì)返回true禽车,注意這是一個(gè)static方法,這意味著"某條線程"指的是當(dāng)前線程刊殉。
18殉摔、synchronized和ReentrantLock的區(qū)別
synchronized是和if、else记焊、for钦勘、while一樣的關(guān)鍵字,ReentrantLock是類亚亲,這是二者的本質(zhì)區(qū)別彻采。既然ReentrantLock是類,那么它就提供了比synchronized更多更靈活的特性捌归,可以被繼承肛响、可以有方法、可以有各種各樣的類變量惜索,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,這點(diǎn)我不能確定角塑。
19蔫磨、ConcurrentHashMap的并發(fā)度是什么
ConcurrentHashMap的并發(fā)度就是segment的大小,默認(rèn)為16圃伶,這意味著最多同時(shí)可以有16條線程操作ConcurrentHashMap堤如,這也是ConcurrentHashMap對(duì)Hashtable的最大優(yōu)勢(shì),任何情況下窒朋,Hashtable能同時(shí)有兩條線程獲取Hashtable中的數(shù)據(jù)嗎搀罢?
20、ReadWriteLock是什么
首先明確一下侥猩,不是說ReentrantLock不好榔至,只是ReentrantLock某些時(shí)候有局限。如果使用ReentrantLock欺劳,可能本身是為了防止線程A在寫數(shù)據(jù)唧取、線程B在讀數(shù)據(jù)造成的數(shù)據(jù)不一致瓣俯,但這樣,如果線程C在讀數(shù)據(jù)兵怯、線程D也在讀數(shù)據(jù),讀數(shù)據(jù)是不會(huì)改變數(shù)據(jù)的腔剂,沒有必要加鎖媒区,但是還是加鎖了,降低了程序的性能掸犬。
因?yàn)檫@個(gè)袜漩,才誕生了讀寫鎖ReadWriteLock。ReadWriteLock是一個(gè)讀寫鎖接口湾碎,ReentrantReadWriteLock是ReadWriteLock接口的一個(gè)具體實(shí)現(xiàn)宙攻,實(shí)現(xiàn)了讀寫的分離,讀鎖是共享的介褥,寫鎖是獨(dú)占的座掘,讀和讀之間不會(huì)互斥,讀和寫柔滔、寫和讀溢陪、寫和寫之間才會(huì)互斥,提升了讀寫的性能睛廊。
21形真、FutureTask是什么
這個(gè)其實(shí)前面有提到過,F(xiàn)utureTask表示一個(gè)異步運(yùn)算的任務(wù)超全。FutureTask里面可以傳入一個(gè)Callable的具體實(shí)現(xiàn)類咆霜,可以對(duì)這個(gè)異步運(yùn)算的任務(wù)的結(jié)果進(jìn)行等待獲取、判斷是否已經(jīng)完成嘶朱、取消任務(wù)等操作蛾坯。當(dāng)然,由于FutureTask也是Runnable接口的實(shí)現(xiàn)類疏遏,所以FutureTask也可以放入線程池中偿衰。
22、Linux環(huán)境下如何查找哪個(gè)線程使用CPU最長
這是一個(gè)比較偏實(shí)踐的問題改览,這種問題我覺得挺有意義的下翎。可以這么做:
(1)獲取項(xiàng)目的pid宝当,jps或者ps -ef | grep java视事,這個(gè)前面有講過
(2)top -H -p pid,順序不能改變
這樣就可以打印出當(dāng)前的項(xiàng)目庆揩,每條線程占用CPU時(shí)間的百分比俐东。注意這里打出的是LWP跌穗,也就是操作系統(tǒng)原生線程的線程號(hào),我筆記本山?jīng)]有部署Linux環(huán)境下的Java工程虏辫,因此沒有辦法截圖演示蚌吸,網(wǎng)友朋友們?nèi)绻臼鞘褂肔inux環(huán)境部署項(xiàng)目的話,可以嘗試一下砌庄。
使用"top -H -p pid"+"jps pid"可以很容易地找到某條占用CPU高的線程的線程堆棧羹唠,從而定位占用CPU高的原因,一般是因?yàn)椴划?dāng)?shù)拇a操作導(dǎo)致了死循環(huán)娄昆。
最后提一點(diǎn)佩微,"top -H -p pid"打出來的LWP是十進(jìn)制的,"jps pid"打出來的本地線程號(hào)是十六進(jìn)制的萌焰,轉(zhuǎn)換一下哺眯,就能定位到占用CPU高的線程的當(dāng)前線程堆棧了。
23扒俯、Java編程寫一個(gè)會(huì)導(dǎo)致死鎖的程序
第一次看到這個(gè)題目奶卓,覺得這是一個(gè)非常好的問題。很多人都知道死鎖是怎么一回事兒:線程A和線程B相互等待對(duì)方持有的鎖導(dǎo)致程序無限死循環(huán)下去撼玄。當(dāng)然也僅限于此了寝杖,問一下怎么寫一個(gè)死鎖的程序就不知道了,這種情況說白了就是不懂什么是死鎖互纯,懂一個(gè)理論就完事兒了瑟幕,實(shí)踐中碰到死鎖的問題基本上是看不出來的。
真正理解什么是死鎖留潦,這個(gè)問題其實(shí)不難只盹,幾個(gè)步驟:
1)兩個(gè)線程里面分別持有兩個(gè)Object對(duì)象:lock1和lock2。這兩個(gè)lock作為同步代碼塊的鎖兔院;
2)線程1的run()方法中同步代碼塊先獲取lock1的對(duì)象鎖殖卑,Thread.sleep(xxx),時(shí)間不需要太多坊萝,50毫秒差不多了孵稽,然后接著獲取lock2的對(duì)象鎖。這么做主要是為了防止線程1啟動(dòng)一下子就連續(xù)獲得了lock1和lock2兩個(gè)對(duì)象的對(duì)象鎖
3)線程2的run)(方法中同步代碼塊先獲取lock2的對(duì)象鎖十偶,接著獲取lock1的對(duì)象鎖菩鲜,當(dāng)然這時(shí)lock1的對(duì)象鎖已經(jīng)被線程1鎖持有,線程2肯定是要等待線程1釋放lock1的對(duì)象鎖的
這樣惦积,線程1"睡覺"睡完接校,線程2已經(jīng)獲取了lock2的對(duì)象鎖了,線程1此時(shí)嘗試獲取lock2的對(duì)象鎖狮崩,便被阻塞蛛勉,此時(shí)一個(gè)死鎖就形成了鹿寻。代碼就不寫了,占的篇幅有點(diǎn)多诽凌,Java多線程7:死鎖這篇文章里面有毡熏,就是上面步驟的代碼實(shí)現(xiàn)。
點(diǎn)擊提供了一個(gè)死鎖的案例侣诵。
24痢法、怎么喚醒一個(gè)阻塞的線程
如果線程是因?yàn)檎{(diào)用了wait()、sleep()或者join()方法而導(dǎo)致的阻塞窝趣,可以中斷線程,并且通過拋出InterruptedException來喚醒它训柴;如果線程遇到了IO阻塞哑舒,無能為力,因?yàn)镮O是操作系統(tǒng)實(shí)現(xiàn)的幻馁,Java代碼并沒有辦法直接接觸到操作系統(tǒng)洗鸵。
25、不可變對(duì)象對(duì)多線程有什么幫助
前面有提到過的一個(gè)問題仗嗦,不可變對(duì)象保證了對(duì)象的內(nèi)存可見性膘滨,對(duì)不可變對(duì)象的讀取不需要進(jìn)行額外的同步手段,提升了代碼執(zhí)行效率稀拐。
26火邓、什么是多線程的上下文切換
多線程的上下文切換是指CPU控制權(quán)由一個(gè)已經(jīng)正在運(yùn)行的線程切換到另外一個(gè)就緒并等待獲取CPU執(zhí)行權(quán)的線程的過程。
27德撬、如果你提交任務(wù)時(shí)铲咨,線程池隊(duì)列已滿,這時(shí)會(huì)發(fā)生什么
這里區(qū)分一下:
1)如果使用的是無界隊(duì)列LinkedBlockingQueue蜓洪,也就是無界隊(duì)列的話纤勒,沒關(guān)系,繼續(xù)添加任務(wù)到阻塞隊(duì)列中等待執(zhí)行隆檀,因?yàn)長inkedBlockingQueue可以近乎認(rèn)為是一個(gè)無窮大的隊(duì)列摇天,可以無限存放任務(wù)
2)如果使用的是有界隊(duì)列比如ArrayBlockingQueue,任務(wù)首先會(huì)被添加到ArrayBlockingQueue中恐仑,ArrayBlockingQueue滿了泉坐,會(huì)根據(jù)maximumPoolSize的值增加線程數(shù)量,如果增加了線程數(shù)量還是處理不過來裳仆,ArrayBlockingQueue繼續(xù)滿坚冀,那么則會(huì)使用拒絕策略RejectedExecutionHandler處理滿了的任務(wù),默認(rèn)是AbortPolicy
28鉴逞、Java中用到的線程調(diào)度算法是什么
搶占式记某。一個(gè)線程用完CPU之后司训,操作系統(tǒng)會(huì)根據(jù)線程優(yōu)先級(jí)、線程饑餓情況等數(shù)據(jù)算出一個(gè)總的優(yōu)先級(jí)并分配下一個(gè)時(shí)間片給某個(gè)線程執(zhí)行液南。
29壳猜、Thread.sleep(0)的作用是什么
這個(gè)問題和上面那個(gè)問題是相關(guān)的,我就連在一起了滑凉。由于Java采用搶占式的線程調(diào)度算法统扳,因此可能會(huì)出現(xiàn)某條線程常常獲取到CPU控制權(quán)的情況,為了讓某些優(yōu)先級(jí)比較低的線程也能獲取到CPU控制權(quán)畅姊,可以使用Thread.sleep(0)手動(dòng)觸發(fā)一次操作系統(tǒng)分配時(shí)間片的操作咒钟,這也是平衡CPU控制權(quán)的一種操作。
30若未、什么是自旋
很多synchronized里面的代碼只是一些很簡單的代碼朱嘴,執(zhí)行時(shí)間非常快粗合,此時(shí)等待的線程都加鎖可能是一種不太值得的操作萍嬉,因?yàn)榫€程阻塞涉及到用戶態(tài)和內(nèi)核態(tài)切換的問題。既然synchronized里面的代碼執(zhí)行得非诚毒危快壤追,不妨讓等待鎖的線程不要被阻塞,而是在synchronized的邊界做忙循環(huán)供屉,這就是自旋行冰。如果做了多次忙循環(huán)發(fā)現(xiàn)還沒有獲得鎖,再阻塞伶丐,這樣可能是一種更好的策略资柔。
所謂技多不壓身,我們所讀過的每一本書撵割,所學(xué)過的每一門語言贿堰,在未來指不定都能給我們意想不到的回饋呢。其實(shí)做為一個(gè)開發(fā)者啡彬,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要這里我推薦一個(gè)Java學(xué)習(xí)交流群342016322羹与,不管你是小白還是大牛歡迎入駐,大家一起交流成長庶灿。