前言
本來(lái)想著給自己放松一下,刷刷博客,突然被幾道面試題難倒!什么是多線程中的上下文切換?什么是Daemon線程?它有什么意義?樂(lè)觀鎖和悲觀鎖的理解及如何實(shí)現(xiàn),有哪些實(shí)現(xiàn)方式?似乎有點(diǎn)模糊了,那就大概看一下面試題吧。好記性不如爛鍵盤
*** 12萬(wàn)字的java面試題整理 ***
*** java核心面試知識(shí)整理 ***
*** Java高頻面試講解視頻(知識(shí)涵蓋齊全) ***
什么是多線程中的上下文切換?
在上下文切換過(guò)程中,CPU會(huì)停止處理當(dāng)前運(yùn)行的程序,并保存當(dāng)前程序運(yùn)行的具體位置以便之后繼續(xù)運(yùn)行。從這個(gè)角度來(lái)看,上下文切換有點(diǎn)像我們同時(shí)閱讀幾本書,在來(lái)回切換書本的同時(shí)我們需要記住每本書當(dāng)前讀到的頁(yè)碼。
在程序中,上下文切換過(guò)程中的“頁(yè)碼”信息是保存在進(jìn)程控制塊(PCB)中的。PCB還經(jīng)常被稱作“切換楨”(switchframe)。“頁(yè)碼”信息會(huì)一直保存到CPU的內(nèi)存中,直到他們被再次使用。
上下文切換是存儲(chǔ)和恢復(fù)CPU狀態(tài)的過(guò)程,它使得線程執(zhí)行能夠從中斷點(diǎn)恢復(fù)執(zhí)行。上下文切換是多任務(wù)操作系統(tǒng)和多線程環(huán)境的基本特征。
什么是Daemon線程?它有什么意義?
所謂后臺(tái)(daemon)線程,也叫守護(hù)線程,是指在程序運(yùn)行的時(shí)候在后臺(tái)提供一種通用服務(wù)的線程,并且這個(gè)線程并不屬于程序中不可或缺的部分。
因此,當(dāng)所有的非后臺(tái)線程結(jié)束時(shí),程序也就終止了,同時(shí)會(huì)殺死進(jìn)程中的所有后臺(tái)線程。反過(guò)來(lái)說(shuō), 只要有任何非后臺(tái)線程還在運(yùn)行,程序就不會(huì)終止。
必須在線程啟動(dòng)之前調(diào)用setDaemon()方法,才能把它設(shè)置為后臺(tái)線程。注意:后臺(tái)進(jìn)程在不執(zhí)行finally子句的情況下就會(huì)終止其run()方法。
比如:JVM的垃圾回收線程就是Daemon線程,F(xiàn)inalizer也是守護(hù)線程。
樂(lè)觀鎖和悲觀鎖的理解及如何實(shí)現(xiàn),有哪些實(shí)現(xiàn)方式?
悲觀鎖:總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖。
傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。再比如Java里面的同步原語(yǔ)synchronized關(guān)鍵字的實(shí)現(xiàn)也是悲觀鎖。
樂(lè)觀鎖:顧名思義,就是很樂(lè)觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒(méi)有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)等機(jī)制。
樂(lè)觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫(kù)提供的類似于write_condition機(jī)制,其實(shí)都是提供的樂(lè)觀鎖。
在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂(lè)觀鎖的一種實(shí)現(xiàn)方式CAS實(shí)現(xiàn)的。
樂(lè)觀鎖的實(shí)現(xiàn)方式:
1、使用版本標(biāo)識(shí)來(lái)確定讀到的數(shù)據(jù)與提交時(shí)的數(shù)據(jù)是否一致。提交后修改版本標(biāo)識(shí),不一致時(shí)可以采取丟棄和再次嘗試的策略。
2、java中的Compare and Swap即CAS ,當(dāng)多個(gè)線程嘗試使用CAS同時(shí)更新同一個(gè)變量時(shí),只有其中一個(gè)線程能更新變量的值,而其它線程都失敗,失敗的線程并不會(huì)被掛起,而是被告知這次競(jìng)爭(zhēng)中失敗,并可以再次嘗試。
CAS 操作中包含三個(gè)操作數(shù) —— 需要讀寫的內(nèi)存位置(V)、進(jìn)行比較的預(yù)期原值(A)和擬寫入的新值(B)。如果內(nèi)存位置V的值與預(yù)期原值A(chǔ)相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值B。否則處理器不做任何操作。
CAS缺點(diǎn):
- ABA問(wèn)題:比如說(shuō)一個(gè)線程one從內(nèi)存位置V中取出A,這時(shí)候另一個(gè)線程two也從內(nèi)存中取出
A,并且two進(jìn)行了一些操作變成了B,然后two又將V位置的數(shù)據(jù)變成A,這時(shí)候線程one進(jìn)行
CAS操作發(fā)現(xiàn)內(nèi)存中仍然是A,然后one操作成功。盡管線程one的CAS操作成功,但可能存在
潛藏的問(wèn)題。從Java1.5開(kāi)始JDK的atomic包里提供了一個(gè)類AtomicStampedReference來(lái)解決
ABA問(wèn)題。 - 循環(huán)時(shí)間長(zhǎng)開(kāi)銷大:對(duì)于資源競(jìng)爭(zhēng)嚴(yán)重(線程沖突嚴(yán)重)的情況,CAS自旋的概率會(huì)比較大,
從而浪費(fèi)更多的CPU資源,效率低于synchronized。 - 只能保證一個(gè)共享變量的原子操作:當(dāng)對(duì)一個(gè)共享變量執(zhí)行操作時(shí),我們可以使用循環(huán)CAS的方式來(lái)保證原子操作,但是對(duì)多個(gè)共享變量操作時(shí),循環(huán)CAS就無(wú)法保證操作的原子性,這個(gè)時(shí)候就可以用鎖。