史上最全的并發(fā)編程面試題小結(jié)

1.什么是活鎖、饑餓、無鎖、死鎖?
死鎖、活鎖、饑餓是關(guān)于多線程是否活躍出現(xiàn)的運(yùn)行阻塞障礙問題,如果線程出現(xiàn) 了這三種情況,即線程不再活躍,不能再正常地執(zhí)行下去了。
死鎖
死鎖是多線程中最差的一種情況,多個(gè)線程相互占用對(duì)方的資源的鎖,而又相互等 對(duì)方釋放鎖,此時(shí)若無外力干預(yù),這些線程則一直處理阻塞的假死狀態(tài),形成死鎖。
舉個(gè)例子,A 同學(xué)搶了 B 同學(xué)的鋼筆,B 同學(xué)搶了 A 同學(xué)的書,兩個(gè)人都相互占 用對(duì)方的東西,都在讓對(duì)方先還給自己自己再還,這樣一直爭(zhēng)執(zhí)下去等待對(duì)方還而 又得不到解決,
老師知道此事后就讓他們相互還給對(duì)方,這樣在外力的干預(yù)下他們 才解決,當(dāng)然這只是個(gè)例子沒有老師他們也能很好解決,計(jì)算機(jī)不像人如果發(fā)現(xiàn)這 種情況沒有外力干預(yù)還是會(huì)一直阻塞下去的。
活鎖
活鎖這個(gè)概念大家應(yīng)該很少有人聽說或理解它的概念,而在多線程中這確實(shí)存在。
活鎖恰恰與死鎖相反,死鎖是大家都拿不到資源都占用著對(duì)方的資源,而活鎖是拿 到資源卻又相互釋放不執(zhí)行。
當(dāng)多線程中出現(xiàn)了相互謙讓,都主動(dòng)將資源釋放給別 的線程使用,這樣這個(gè)資源在多個(gè)線程之間跳動(dòng)而又得不到執(zhí)行,這就是活鎖。
饑餓
我們知道多線程執(zhí)行中有線程優(yōu)先級(jí)這個(gè)東西,優(yōu)先級(jí)高的線程能夠插隊(duì)并優(yōu)先執(zhí) 行,這樣如果優(yōu)先級(jí)高的線程一直搶占優(yōu)先級(jí)低線程的資源,導(dǎo)致低優(yōu)先級(jí)線程無 法得到執(zhí)行,這就是饑餓。
當(dāng)然還有一種饑餓的情況,一個(gè)線程一直占著一個(gè)資源 不放而導(dǎo)致其他線程得不到執(zhí)行,與死鎖不同的是饑餓在以后一段時(shí)間內(nèi)還是能夠 得到執(zhí)行的,如那個(gè)占用資源的線程結(jié)束了并釋放了資源。
無鎖
無鎖,即沒有對(duì)資源進(jìn)行鎖定,即所有的線程都能訪問并修改同一個(gè)資源,但同時(shí) 只有一個(gè)線程能修改成功。
無鎖典型的特點(diǎn)就是一個(gè)修改操作在一個(gè)循環(huán)內(nèi)進(jìn)行, 線程會(huì)不斷的嘗試修改共享資源,如果沒有沖突就修改成功并退出否則就會(huì)繼續(xù)下 一次循環(huán)嘗試。
所以,如果有多個(gè)線程修改同一個(gè)值必定會(huì)有一個(gè)線程能修改成功, 而其他修改失敗的線程會(huì)不斷重試直到修改成功。之前的文章我介紹過 JDK 的 CAS 原理及應(yīng)用即是無鎖的實(shí)現(xiàn)。
可以看出,無鎖是一種非常良好的設(shè)計(jì),它不會(huì)出現(xiàn)線程出現(xiàn)的跳躍性問題,鎖使 用不當(dāng)肯定會(huì)出現(xiàn)系統(tǒng)性能問題,雖然無鎖無法全面代替有鎖,但無鎖在某些場(chǎng)合 下是非常高效的。
2.線程和進(jìn)程的區(qū)別是什么?
進(jìn)程和線程的主要差別在于它們是不同的操作系統(tǒng)資源管理方式。進(jìn)程有獨(dú)立的地 址空間,一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其它進(jìn)程產(chǎn)生影響,而線程只是一 個(gè)進(jìn)程中的不同執(zhí)行路徑。
線程有自己的堆棧和局部變量,但線程之間沒有單獨(dú)的 地址空間,一個(gè)線程死掉就等于整個(gè)進(jìn)程死掉,所以多進(jìn)程的程序要比多線程的程序健壯,但在進(jìn)程切換時(shí),耗費(fèi)資源較大,效率要差一些。
但對(duì)于一些要求同時(shí)進(jìn)行并且又要共享某些變量的并發(fā)操作,只能用線程,不能用進(jìn)程。
3.Java 實(shí)現(xiàn)線程有哪幾種方式?
(1)繼承 Thread 類實(shí)現(xiàn)多線程
(2)實(shí)現(xiàn) Runnable 接口方式實(shí)現(xiàn)多線程
(3)使用 ExecutorService、Callable、Future 實(shí)現(xiàn)有返回結(jié)果的多線程
(4)通過線程池創(chuàng)建線程
4.啟動(dòng)線程方法 start()和 run()有什么區(qū)別?
只有調(diào)用了 start()方法,才會(huì)表現(xiàn)出多線程的特性,不同線程的 run()方法里面的代碼交替執(zhí)行。如果只是調(diào)用 run()方法,那么代碼還是同步執(zhí)行的,必須等待一個(gè)線程的 run()方法里面的代碼全部執(zhí)行完畢之后,另外一個(gè)線程才可以執(zhí)行其 run() 方法里面的代碼。
5.怎么終止一個(gè)線程?如何優(yōu)雅地終止線程?
stop 終止,不推薦。
6.一個(gè)線程的生命周期有哪幾種狀態(tài)?它們之間如何流轉(zhuǎn)的?
NEW:毫無疑問表示的是剛創(chuàng)建的線程,還沒有開始啟動(dòng)。
RUNNABLE: 表示線程已經(jīng)觸發(fā) start()方式調(diào)用,線程正式啟動(dòng),線程處于運(yùn)行中 狀態(tài)。
BLOCKED:表示線程阻塞,等待獲取鎖,如碰到 synchronized、lock 等關(guān)鍵字等占用臨界區(qū)的情況,一旦獲取到鎖就進(jìn)行 RUNNABLE 狀態(tài)繼續(xù)運(yùn)行。
WAITING:表示線程處于無限制等待狀態(tài),等待一個(gè)特殊的事件來重新喚醒,如 通過wait()方法進(jìn)行等待的線程等待一個(gè) notify()或者 notifyAll()方法,通過 join()方 法進(jìn)行等待的線程等待目標(biāo)線程運(yùn)行結(jié)束而喚醒,一旦通過相關(guān)事件喚醒線程,線 程就進(jìn)入了 RUNNABLE 狀態(tài)繼續(xù)運(yùn)行。
TIMED_WAITING:表示線程進(jìn)入了一個(gè)有時(shí)限的等待,如 sleep(3000),等待 3 秒 后線程重新進(jìn)行 RUNNABLE 狀態(tài)繼續(xù)運(yùn)行。
TERMINATED:表示線程執(zhí)行完畢后,進(jìn)行終止?fàn)顟B(tài)。需要注意的是,一旦線程通過start 方法啟動(dòng)后就再也不能回到初始 NEW 狀態(tài),線程終止后也不能再回到 RUNNABLE 狀態(tài) 。
7.線程中的 wait()和 sleep()方法有什么區(qū)別?
這個(gè)問題常問,sleep 方法和 wait 方法都可以用來放棄 CPU 一定的時(shí)間,不同點(diǎn)在于如果線程持有某個(gè)對(duì)象的監(jiān)視器,sleep 方法不會(huì)放棄這個(gè)對(duì)象的監(jiān)視器,wait 方法會(huì)放棄這個(gè)對(duì)象的監(jiān)視器。
8.多線程同步有哪幾種方法?
Synchronized 關(guān)鍵字,Lock 鎖實(shí)現(xiàn),分布式鎖等。
9.多線程有什么用?
1)發(fā)揮多核CPU的優(yōu)勢(shì)
隨著工業(yè)的進(jìn)步,現(xiàn)在的筆記本、臺(tái)式機(jī)乃至商用的應(yīng)用服務(wù)器至少也都是雙核的 ,4 核、8 核甚至 16 核的也都不少見,如果是單線程的程序,那么在雙核 CPU 上 就浪費(fèi)了 50%, 在 4 核 CPU 上就浪費(fèi)了 75%。
單核 CPU 上所謂的"多線程"那是 假的多線程,同一時(shí)間處理器只會(huì)處理一段邏輯,只不過線程之間切換得比較快, 看著像多個(gè)線程"同時(shí)"運(yùn)行罷了。
多核 CPU 上的多線程才是真正的多線程,它能 讓你的多段邏輯同時(shí)工作,多線程,可以真正發(fā)揮出多核CPU 的優(yōu)勢(shì)來,達(dá)到充 分利用CPU 的目的。
2)防止阻塞
從程序運(yùn)行效率的角度來看,單核 CPU 不但不會(huì)發(fā)揮出多線程的優(yōu)勢(shì),反而會(huì)因 為在單核CPU 上運(yùn)行多線程導(dǎo)致線程上下文的切換,而降低程序整體的效率。
但 是單核 CPU 我們還是要應(yīng)用多線程,就是為了防止阻塞。試想,如果單核 CPU 使 用單線程,那么只要這個(gè)線程阻塞了,比方說遠(yuǎn)程讀取某個(gè)數(shù)據(jù)吧,對(duì)端遲遲未返 回又沒有設(shè)置超時(shí)時(shí)間,那么你的整個(gè)程序在數(shù)據(jù)返回回來之前就停止運(yùn)行了。
多線程可以防止這個(gè)問題,多條線程同時(shí)運(yùn)行,哪怕一條線程的代碼執(zhí)行讀取數(shù)據(jù)阻 塞,也不會(huì)影響其它任務(wù)的執(zhí)行。
3)便于建模
這是另外一個(gè)沒有這么明顯的優(yōu)點(diǎn)了。假設(shè)有一個(gè)大的任務(wù) A,單線程編程,那么 就要考慮很多,建立整個(gè)程序模型比較麻煩。但是如果把這個(gè)大的任務(wù) A 分解成 幾個(gè)小任務(wù),任務(wù)B、任務(wù) C、任務(wù) D,分別建立程序模型,并通過多線程分別運(yùn) 行這幾個(gè)任務(wù),那就簡(jiǎn)單很多了。
10.多線程之間如何進(jìn)行通信?
wait/notify
11、線程怎樣拿到返回結(jié)果?
實(shí)現(xiàn)Callable 接口。
12、violatile 關(guān)鍵字的作用?
一個(gè)非常重要的問題,是每個(gè)學(xué)習(xí)、應(yīng)用多線程的 Java 程序員都必須掌握的。理 解 volatile關(guān)鍵字的作用的前提是要理解 Java 內(nèi)存模型,這里就不講 Java 內(nèi)存模型 了,可以參見第31 點(diǎn),volatile 關(guān)鍵字的作用主要有兩個(gè):
1)多線程主要圍繞可見性和原子性兩個(gè)特性而展開,使用 volatile 關(guān)鍵字修飾的變 量,保證了其在多線程之間的可見性,即每次讀取到 volatile 變量,一定是最新的數(shù)據(jù)。
2)代碼底層執(zhí)行不像我們看到的高級(jí)語言----Java 程序這么簡(jiǎn)單,它的執(zhí)行是 Java 代碼–>字節(jié)碼–>根據(jù)字節(jié)碼執(zhí)行對(duì)應(yīng)的 C/C++代碼–>C/C++代碼被編譯成匯編語 言–>和硬件電路交互,現(xiàn)實(shí)中,為了獲取更好的性能 JVM 可能會(huì)對(duì)指令進(jìn)行重排序,多線程下可能會(huì)出現(xiàn)一些意想不到的問題。
使用 volatile 則會(huì)對(duì)禁止語義重排 序,當(dāng)然這也一定程度上降低了代碼執(zhí)行效率從實(shí)踐角度而言,volatile 的一個(gè)重 要 作 用 就 是 和 CAS 結(jié) 合。
13、新建 T1、T2、T3 三個(gè)線程,如何保證它們按順序執(zhí)行?
用 join 方法。
14、怎么控制同一時(shí)間只有 3 個(gè)線程運(yùn)行?
用 Semaphore。
15、為什么要使用線程池?
我們知道不用線程池的話,每個(gè)線程都要通過 new Thread(xxRunnable).start()的方 式來創(chuàng)建并運(yùn)行一個(gè)線程,線程少的話這不會(huì)是問題。
而真實(shí)環(huán)境可能會(huì)開啟多個(gè)線程讓系統(tǒng)和程序達(dá)到最佳效率,當(dāng)線程數(shù)達(dá)到一定數(shù)量就會(huì)耗盡系統(tǒng)的 CPU 和 內(nèi)存資源,也會(huì)造成 GC頻繁收集和停頓,因?yàn)槊看蝿?chuàng)建和銷毀一個(gè)線程都是要消 耗系統(tǒng)資源的。
如果為每個(gè)任務(wù)都創(chuàng)建線程這無疑是一個(gè)很大的性能瓶頸。所以, 線程池中的線程復(fù)用極大節(jié)省了系統(tǒng)資源,當(dāng)線程一段時(shí)間不再有任務(wù)處理時(shí)它也 會(huì)自動(dòng)銷毀,而不會(huì)長(zhǎng)駐內(nèi)存。
到此這篇關(guān)于史上最全的并發(fā)編程面試題小結(jié)的文章就介紹到這了,更多相關(guān)并發(fā)編程面試題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持腳本之家!
相關(guān)文章
最全BAT架構(gòu)師130面試題(微服務(wù)、高并發(fā)、大數(shù)據(jù)、緩存等中間件)
這篇文章主要介紹了最全BAT架構(gòu)師130面試題(微服務(wù)、高并發(fā)、大數(shù)據(jù)、緩存等中間件),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2020-08-186個(gè)常見的高并發(fā)緩存問題,你知道幾個(gè)
這篇文章主要介紹了6個(gè)常見的高并發(fā)緩存問題,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2020-09-08