紀(jì)檢監(jiān)察工作 網(wǎng)站建設(shè)軟文寫作營銷
Java內(nèi)存模型(非JVM)
Java內(nèi)存模型(Java Memory Model簡稱JMM),是一種共享內(nèi)存模型,是多線程的東西,并不是JVM(Java Virtual Machine(Java虛擬機(jī))的縮寫),這是倆玩意兒!!!
共享內(nèi)存模型指的就是Java內(nèi)存模型(Java Memory Model簡稱JMM),JMM決定一個線程對共享變量的寫入時,能對另一個線程可見。從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存(main memory)中,每個線程都有一個私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個抽象概念,并不真實(shí)存在。它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化。

從上圖來看,線程A與線程B之間如要通信的話,必須要經(jīng)歷下面2個步驟:
1. 首先,線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去。
2. 然后,線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量。
總結(jié):什么是Java內(nèi)存模型:java內(nèi)存模型簡稱JMM,定義了一個線程對另一個線程可見。共享變量存放在主內(nèi)存中,每個線程都有自己的本地內(nèi)存,當(dāng)多個線程同時訪問一個數(shù)據(jù)的時候,可能本地內(nèi)存沒有及時刷新到主內(nèi)存,所以就會發(fā)生線程安全問題。
2. 什么是重排序
程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。
一般來說處理器為了提高程序運(yùn)行效率,可能會對輸入代碼進(jìn)行優(yōu)化,進(jìn)行重新排序(重排序),它不保證程序中各個語句的執(zhí)行先后順序同代碼中的順序一致,但是它會保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的。
int a = 5; //語句1
int r = 3; //語句2
a = a + 2; //語句3
r = a*a; //語句4
則因?yàn)橹嘏判?#xff0c;他還可能執(zhí)行順序?yàn)?#xff08;這里標(biāo)注的是語句的執(zhí)行順序) 2-1-3-4,1-3-2-4 但絕不
可能 2-1-4-3,因?yàn)檫@打破了依賴關(guān)系。顯然重排序?qū)尉€程運(yùn)行是不會有任何問題,但是多線程就不一定了,所以我們在多線程編程時就得考慮這個問題了。
簡單認(rèn)為,重排序就是處理器不按照你寫的代碼的順序來執(zhí)行,可能會給打亂,但是有依賴關(guān)系的不會打亂。
3. 重排序?qū)嶋H執(zhí)行的指令步驟
1. 編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。
2. 指令級并行的重排序。現(xiàn)代處理器采用了指令級并行技術(shù)(ILP)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對應(yīng)機(jī)器指令的執(zhí)行順序。
3. 內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲操作看上去可能是在亂序執(zhí)行。
這些重排序?qū)τ趩尉€程沒問題,但是多線程都可能會導(dǎo)致多線程程序出現(xiàn)內(nèi)存可見性問題。
4. 重排序遵守的規(guī)則
as-if-serial:
1. 不管怎么排序,結(jié)果不能改變
2. 不存在數(shù)據(jù)依賴的可以被編譯器和處理器重排序
3. 一個操作依賴兩個操作,這兩個操作如果不存在依賴可以重排序
4. 單線程根據(jù)此規(guī)則不會有問題,但是重排序后多線程會有問題
5. happen-before規(guī)則
雖然指令重排提高了并發(fā)的性能,但是Java虛擬機(jī)會對指令重排做出一些規(guī)則限制,并不能讓所有的指令都隨意的改變執(zhí)行位置,主要有以下幾點(diǎn):
單線程每個操作,happen-before于該線程中任意后續(xù)操作
volatile寫happen-before與后續(xù)對這個變量的讀
synchronized解鎖happen-before后續(xù)對這個鎖的加鎖
final變量的寫happen-before于final域?qū)ο蟮淖x,happen-before后續(xù)對final變量的讀
傳遞性規(guī)則,A先于B,B先于C,那么A一定先于C發(fā)生
6. as-if-serial規(guī)則和happens-before規(guī)則的區(qū)別
as-if-serial語義保證單線程內(nèi)程序的執(zhí)行結(jié)果不被改變,happens-before關(guān)系保證正確同步的多線程程序的執(zhí)行結(jié)果不被改變。
as-if-serial語義給編寫單線程程序的程序員創(chuàng)造了一個幻境:單線程程序是按程序的順序來執(zhí)行
的。happens-before關(guān)系給編寫正確同步的多線程程序的程序員創(chuàng)造了一個幻境:正確同步的多
線程程序是按happens-before指定的順序來執(zhí)行的。
as-if-serial語義和happens-before這么做的目的,都是為了在不改變程序執(zhí)行結(jié)果的前提下,盡
可能地提高程序執(zhí)行的并行度。
7. volatile 關(guān)鍵字的作用
對于可見性,Java 提供了 volatile 關(guān)鍵字來保證可見性和禁止指令重排。 volatile 提供 happens-before 的保證,確保一個線程的修改能對其他線程是可見的。當(dāng)一個共享變量被 volatile 修飾時,它會保證修改的值會立即被更新到主內(nèi)存中,當(dāng)有其他線程需要讀取時,它會去內(nèi)存中讀取新值。
volatile修飾之后會加入不同的內(nèi)存屏障來保證可見性的問題能正確執(zhí)行。這里寫的屏障基于書中提供的內(nèi)容,但是實(shí)際上由于CPU架構(gòu)不同,重排序的策略不同,提供的內(nèi)存屏障也不一樣,比如x86平臺上,只有StoreLoad一種內(nèi)存屏障。
StoreStore屏障,保證上面的普通寫不和volatile寫發(fā)生重排序
StoreLoad屏障,保證volatile寫與后面可能的volatile讀寫不發(fā)生重排序
LoadLoad屏障,禁止volatile讀與后面的普通讀重排序
LoadStore屏障,禁止volatile讀和后面的普通寫重排序
8. Java 中能創(chuàng)建 volatile 數(shù)組嗎?
能,Java 中可以創(chuàng)建 volatile 類型數(shù)組,不過只是一個指向數(shù)組的引用,而不是整個數(shù)組。意思是,如果改變引用指向的數(shù)組,將會受到 volatile 的保護(hù),但是如果多個線程同時改變數(shù)組的元素,volatile 標(biāo)示符就不能起到之前的保護(hù)作用了。
9. volatile 變量和 atomic 變量有什么不同?
volatile 變量可以確保先行關(guān)系,即寫操作會發(fā)生在后續(xù)的讀操作之前, 但它并不能保證原子性。
例如用 volatile 修飾 count 變量,那么 count++ 操作就不是原子性的。
而 AtomicInteger 類提供的 atomic 方法可以讓這種操作具有原子性如getAndIncrement()方法會原子性的進(jìn)行增量操作把當(dāng)前值加一,其它數(shù)據(jù)類型和引用變量也可以進(jìn)行相似操作。
volatile保證操作的先后順序,atomic保證操作的原子性。
10. 并發(fā)關(guān)鍵字 synchronized ?
在 Java 中,synchronized 關(guān)鍵字是用來控制線程同步的,就是在多線程的環(huán)境下,控制synchronized 代碼段不被多個線程同時執(zhí)行。synchronized 可以修飾類、方法、變量。
Java 6 之后 Java 官方對從 JVM 層面對synchronized 較大優(yōu)化,所以現(xiàn)在的 synchronized 鎖效率也優(yōu)化得很不錯了。JDK1.6對鎖的實(shí)現(xiàn)引入了大量的優(yōu)化,如自旋鎖、適應(yīng)性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術(shù)來減少鎖操作的開銷。
11. 說說自己是怎么使用 synchronized 關(guān)鍵字,在項(xiàng)目中用到了嗎
synchronized關(guān)鍵字最主要的三種使用方式:
修飾實(shí)例方法: 作用于當(dāng)前對象實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前對象實(shí)例的鎖
修飾靜態(tài)方法: 也就是給當(dāng)前類加鎖,會作用于類的所有對象實(shí)例,因?yàn)殪o態(tài)成員不屬于任何一個實(shí)例對象,是類成員( static 表明這是該類的一個靜態(tài)資源,不管new了多少個對象,只有一份)。所以如果一個線程A調(diào)用一個實(shí)例對象的非靜態(tài) synchronized 方法,而線程B需要調(diào)用這個實(shí)例對象所屬類的靜態(tài) synchronized 方法,是允許的,不會發(fā)生互斥現(xiàn)象,因?yàn)樵L問靜態(tài)synchronized 方法占用的鎖是當(dāng)前類的鎖,而訪問非靜態(tài) synchronized 方法占用的鎖是當(dāng)前實(shí)例對象鎖。
修飾代碼塊: 指定加鎖對象,對給定對象加鎖,進(jìn)入同步代碼庫前要獲得給定對象的鎖。
總結(jié): synchronized 關(guān)鍵字加到 static 靜態(tài)方法和 synchronized(class)代碼塊上都是是給 Class類上鎖。synchronized 關(guān)鍵字加到實(shí)例方法上是給對象實(shí)例上鎖。盡量不要使用 synchronized(String a) 因?yàn)镴VM中,字符串常量池具有緩存功能!
12. 說一下 synchronized 底層實(shí)現(xiàn)原理?
synchronized是java提供的原子性內(nèi)置鎖,這種內(nèi)置的并且使用者看不到的鎖也被稱為監(jiān)視器鎖,使用synchronized之后,會在編譯之后在同步的代碼塊前后加上monitorenter和monitorexit字節(jié)碼指令,他依賴操作系統(tǒng)底層互斥鎖實(shí)現(xiàn)。他的作用主要就是實(shí)現(xiàn)原子性操作和解決共享變量的內(nèi)存可見性問題。
執(zhí)行monitorenter指令時會嘗試獲取對象鎖,如果對象沒有被鎖定或者已經(jīng)獲得了鎖,鎖的計(jì)數(shù)器+1。此時其他競爭鎖的線程則會進(jìn)入等待隊(duì)列中。
執(zhí)行monitorexit指令時則會把計(jì)數(shù)器-1,當(dāng)計(jì)數(shù)器值為0時,則鎖釋放,處于等待隊(duì)列中的線程再繼續(xù)競爭鎖。
synchronized是排它鎖,當(dāng)一個線程獲得鎖之后,其他線程必須等待該線程釋放鎖后才能獲得鎖,而且由于Java中的線程和操作系統(tǒng)原生線程是一一對應(yīng)的,線程被阻塞或者喚醒時時會從用戶態(tài)切換到內(nèi)核態(tài),這種轉(zhuǎn)換非常消耗性能。
從內(nèi)存語義來說,加鎖的過程會清除工作內(nèi)存中的共享變量,再從主內(nèi)存讀取,而釋放鎖的過程則是將工作內(nèi)存中的共享變量寫回主內(nèi)存。
實(shí)際上大部分時候我認(rèn)為說到monitorenter就行了,但是為了更清楚的描述,還是再具體一點(diǎn)。
如果再深入到源碼來說,synchronized實(shí)際上有兩個隊(duì)列waitSet和entryList。
當(dāng)多個線程進(jìn)入同步代碼塊時,首先進(jìn)入entryList
有一個線程獲取到monitor鎖后,就賦值給當(dāng)前線程,并且計(jì)數(shù)器+1
如果線程調(diào)用wait方法,將釋放鎖,當(dāng)前線程置為null,計(jì)數(shù)器-1,同時進(jìn)入waitSet等待被喚醒,調(diào)用notify或者notifyAll之后又會進(jìn)入entryList競爭鎖
如果線程執(zhí)行完畢,同樣釋放鎖,計(jì)數(shù)器-1,當(dāng)前線程置為null

13. synchronized可重入的原理
重入鎖是指一個線程獲取到該鎖之后,該線程可以繼續(xù)獲得該鎖。底層原理維護(hù)一個計(jì)數(shù)器,當(dāng)線程獲取該鎖時,計(jì)數(shù)器加一,再次獲得該鎖時繼續(xù)加一,釋放鎖時,計(jì)數(shù)器減一,當(dāng)計(jì)數(shù)器值為0時,表明該鎖未被任何線程所持有,其它線程可以競爭獲取鎖。
14. 什么是自旋
很多 synchronized 里面的代碼只是一些很簡單的代碼,執(zhí)行時間非常快,此時等待的線程都加鎖可能是一種不太值得的操作,因?yàn)榫€程阻塞涉及到用戶態(tài)和內(nèi)核態(tài)切換的問題。既然synchronized 里面的代碼執(zhí)行得非常快,不妨讓等待鎖的線程不要被阻塞,而是在 synchronized的邊界做忙循環(huán),這就是自旋。如果做了多次循環(huán)發(fā)現(xiàn)還沒有獲得鎖,再阻塞,這樣可能是一種更好的策略。
忙循環(huán):就是程序員用循環(huán)讓一個線程等待,不像傳統(tǒng)方法wait(), sleep() 或 yield() 它們都放棄了CPU控制,而忙循環(huán)不會放棄CPU,它就是在運(yùn)行一個空循環(huán)。這么做的目的是為了保留CPU緩存,在多核系統(tǒng)中,一個等待線程醒來的時候可能會在另一個內(nèi)核運(yùn)行,這樣會重建緩存。為了避免重建緩存和減少等待重建的時間就可以使用它了。
(自旋就是你要去上公共廁所,發(fā)現(xiàn)都滿了,你就在門口來回踱步,等有人出來了再去上廁所,因?yàn)槟阌X得很快就會有人出來了,很快就會輪到我了。)
15. 多線程中 synchronized 鎖升級的原理是什么?
(嫌復(fù)雜可以只看紅字)
從JDK1.6版本之后,synchronized本身也在不斷優(yōu)化鎖的機(jī)制,有些情況下他并不會是一個很重量級的鎖了。優(yōu)化機(jī)制包括自適應(yīng)鎖、自旋鎖、鎖消除、鎖粗化、輕量級鎖和偏向鎖。
鎖的狀態(tài)從低到高依次為無鎖->偏向鎖->輕量級鎖->重量級鎖,升級的過程就是從低到高,降級在一定條件也是有可能發(fā)生的。
自旋鎖:由于大部分時候,鎖被占用的時間很短,共享變量的鎖定時間也很短,所有沒有必要掛起線程,用戶態(tài)和內(nèi)核態(tài)的來回上下文切換嚴(yán)重影響性能。自旋的概念就是讓線程執(zhí)行一個忙循環(huán),可以理解為就是啥也不干,防止從用戶態(tài)轉(zhuǎn)入內(nèi)核態(tài),自旋鎖可以通過設(shè)置-XX:+UseSpining來開啟,自旋的默認(rèn)次數(shù)是10次,可以使用-XX:PreBlockSpin設(shè)置。
自適應(yīng)鎖:自適應(yīng)鎖就是自適應(yīng)的自旋鎖,自旋的時間不是固定時間,而是由前一次在同一個鎖上的自旋時間和鎖的持有者狀態(tài)來決定。
鎖消除:鎖消除指的是JVM檢測到一些同步的代碼塊,完全不存在數(shù)據(jù)競爭的場景,也就是不需要加鎖,就會進(jìn)行鎖消除。
鎖粗化:鎖粗化指的是有很多操作都是對同一個對象進(jìn)行加鎖,就會把鎖的同步范圍擴(kuò)展到整個操作序列之外。
偏向鎖:當(dāng)線程訪問同步塊獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲偏向鎖的線程ID,之后這個線程再次進(jìn)入同步塊時都不需要CAS來加鎖和解鎖了,偏向鎖會永遠(yuǎn)偏向第一個獲得鎖的線程,如果后續(xù)沒有其他線程獲得過這個鎖,持有鎖的線程就永遠(yuǎn)不需要進(jìn)行同步,反之,當(dāng)有其他線程競爭偏向鎖時,持有偏向鎖的線程就會釋放偏向鎖??梢杂眠^設(shè)置-XX:+UseBiasedLocking開啟偏向鎖。
輕量級鎖:JVM的對象的對象頭中包含有一些鎖的標(biāo)志位,代碼進(jìn)入同步塊的時候,JVM將會使用CAS方式來嘗試獲取鎖,如果更新成功則會把對象頭中的狀態(tài)位標(biāo)記為輕量級鎖,如果更新失敗,當(dāng)前線程就嘗試自旋來獲得鎖。
整個鎖升級的過程非常復(fù)雜,我盡力去除一些無用的環(huán)節(jié),簡單來描述整個升級的機(jī)制。
簡單點(diǎn)說,偏向鎖就是通過對象頭的偏向線程ID來對比,甚至都不需要CAS了,而輕量級鎖主要就是通過CAS修改對象頭鎖記錄和自旋來實(shí)現(xiàn),重量級鎖則是除了擁有鎖的線程其他全部阻塞。

16. 線程 B 怎么知道線程 A 修改了變量
(1)volatile 修飾變量
(2)synchronized 修飾修改變量的方法
(3)wait/notify
(4)while 輪詢
17. 當(dāng)一個線程進(jìn)入一個對象的 synchronized 方法 A 之后,其它線程是否可進(jìn)入此對象的 synchronized 方法 B?
不能。其它線程只能訪問該對象的非同步方法,同步方法則不能進(jìn)入。因?yàn)榉庆o態(tài)方法上synchronized 修飾符要求執(zhí)行方法時要獲得對象的鎖,如果已經(jīng)進(jìn)入A 方法說明對象鎖已經(jīng)被取走,那么試圖進(jìn)入 B 方法的線程就只能在等鎖池(注意不是等待池哦)中等待對象的鎖。
18. 什么是 CAS
CAS叫做CompareAndSwap,比較并交換,主要是通過處理器的指令來保證操作的原子性,它包含三個操作數(shù):
變量內(nèi)存地址,V表示
舊的預(yù)期值,A表示
準(zhǔn)備設(shè)置的新值,B表示
當(dāng)執(zhí)行CAS指令時,只有當(dāng)V等于A時,才會用B去更新V的值,否則就不會執(zhí)行更新操作。
19. CAS 的會產(chǎn)生什么問題?
1、ABA 問題:
比如說一個線程 one 從內(nèi)存位置 V 中取出 A,這時候另一個線程 two 也從內(nèi)存中取出 A,并且 two 進(jìn)行了一些操作變成了 B,然后 two 又將 V 位置的數(shù)據(jù)變成 A,這時候線程 one 進(jìn)行 CAS 操作發(fā)現(xiàn)內(nèi)存
中仍然是 A,然后 one 操作成功。盡管線程 one 的 CAS 操作成功,但可能存在潛藏的問題。Java1.5 開始 JDK 的 atomic包里提供了一個類 AtomicStampedReference 來解決 ABA 問題。
2、循環(huán)時間長開銷大:
對于資源競爭嚴(yán)重(線程沖突嚴(yán)重)的情況,CAS 自旋的概率會比較大,從而浪費(fèi)更多的 CPU 資源,
效率低于 synchronized。
3、只能保證一個共享變量的原子操作:
當(dāng)對一個共享變量執(zhí)行操作時,我們可以使用循環(huán) CAS 的方式來保證原子操作,但是對多個共享變量
操作時,循環(huán) CAS 就無法保證操作的原子性,這個時候就可以用鎖
20. synchronized、volatile、CAS 比較
(1)synchronized 是悲觀鎖,屬于搶占式,會引起其他線程阻塞。
(2)volatile 提供多線程共享變量可見性和禁止指令重排序優(yōu)化。
(3)CAS 是基于沖突檢測的樂觀鎖(非阻塞)
21. synchronized 和 Lock 有什么區(qū)別?
首先synchronized是Java內(nèi)置關(guān)鍵字,在JVM層面,Lock是個Java類;
synchronized 可以給類、方法、代碼塊加鎖;而 lock 只能給代碼塊加鎖。
synchronized 不需要手動獲取鎖和釋放鎖,使用簡單,發(fā)生異常會自動釋放鎖,不會造成死鎖;
而 lock 需要自己加鎖和釋放鎖,如果使用不當(dāng)沒有 unLock()去釋放鎖就會造成死鎖。
通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到。
22. synchronized 和 ReentrantLock 區(qū)別是什么?
synchronized 是和 if、else、for、while 一樣的關(guān)鍵字,ReentrantLock 是類,這是二者的本質(zhì)區(qū)別。既然 ReentrantLock 是類,那么它就提供了比synchronized 更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量
synchronized 早期的實(shí)現(xiàn)比較低效,對比 ReentrantLock,大多數(shù)場景性能都相差較大,但是在
Java 6 中對 synchronized 進(jìn)行了非常多的改進(jìn)。
相同點(diǎn):兩者都是可重入鎖(加鎖后再次加鎖)
兩者都是可重入鎖?!翱芍厝腈i”概念是:自己可以再次獲取自己的內(nèi)部鎖。比如一個線程獲得了某個對象的鎖,此時這個對象鎖還沒有釋放,當(dāng)其再次想要獲取這個對象的鎖的時候還是可以獲取的,如果不可鎖重入的話,就會造成死鎖。同一個線程每次獲取鎖,鎖的計(jì)數(shù)器都自增1,所以要等到鎖的計(jì)數(shù)器下降為0時才能釋放鎖。
主要區(qū)別如下:
ReentrantLock 使用起來比較靈活,但是必須有釋放鎖的配合動作;
ReentrantLock 必須手動獲取與釋放鎖,而 synchronized 不需要手動釋放和開啟鎖;
ReentrantLock 只適用于代碼塊鎖,而 synchronized 可以修飾類、方法、變量等。
二者的鎖機(jī)制其實(shí)也是不一樣的。ReentrantLock 底層調(diào)用的是 Unsafe 的park 方法加鎖,
synchronized 操作的應(yīng)該是對象頭中 mark word
23. synchronized 和 volatile 的區(qū)別是什么?
synchronized 表示只有一個線程可以獲取作用對象的鎖,執(zhí)行代碼,阻塞其他線程。
volatile 表示變量在 CPU 的寄存器中是不確定的,必須從主存中讀取。保證多線程環(huán)境下變量的可見性;禁止指令重排序。
區(qū)別
volatile 是變量修飾符;synchronized 可以修飾類、方法、變量。
volatile 僅能實(shí)現(xiàn)變量的修改可見性,不能保證原子性;而 synchronized 則可以保證變量的修改可見性和原子性。
volatile 不會造成線程的阻塞;synchronized 可能會造成線程的阻塞。
volatile標(biāo)記的變量不會被編譯器優(yōu)化;synchronized標(biāo)記的變量可以被編譯器優(yōu)化。
volatile關(guān)鍵字是線程同步的輕量級實(shí)現(xiàn),所以volatile性能肯定比synchronized關(guān)鍵字要好。但是
volatile關(guān)鍵字只能用于變量而synchronized關(guān)鍵字可以修飾方法以及代碼塊。synchronized關(guān)鍵
字在JavaSE1.6之后進(jìn)行了主要包括為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕
量級鎖以及其它各種優(yōu)化之后執(zhí)行效率有了顯著提升,實(shí)際開發(fā)中使用 synchronized 關(guān)鍵字的場
景還是更多一些。
24. 樂觀鎖和悲觀鎖的理解及如何實(shí)現(xiàn),有哪些實(shí)現(xiàn)方式?
悲觀鎖:總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時候都認(rèn)為別人會修改,所以每次在拿數(shù)據(jù)的時候都會上鎖,這樣別人想拿這個數(shù)據(jù)就會阻塞直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。再比如 Java 里面的同步原語 synchronized 關(guān)鍵字的實(shí)現(xiàn)也是悲觀鎖。
樂觀鎖:顧名思義,就是很樂觀,每次去拿數(shù)據(jù)的時候都認(rèn)為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數(shù)據(jù),可以使用版本號等機(jī)制。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫提供的類似于 write_condition 機(jī)制,其實(shí)都是提供的樂觀鎖。在 Java中 java.util.concurrent.atomic 包下面的原子變量類就是使用了樂觀鎖的一種實(shí)現(xiàn)方式 CAS 實(shí)現(xiàn)的。
25. 死鎖與活鎖的區(qū)別,死鎖與饑餓的區(qū)別?
死鎖:是指兩個或兩個以上的進(jìn)程(或線程)在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。
活鎖:任務(wù)或者執(zhí)行者沒有被阻塞,由于某些條件沒有滿足,導(dǎo)致一直重復(fù)嘗試,失敗,嘗試,失敗。
活鎖和死鎖的區(qū)別在于,處于活鎖的實(shí)體是在不斷的改變狀態(tài),這就是所謂的“活”, 而處于死鎖的實(shí)體表現(xiàn)為等待;活鎖有可能自行解開,死鎖則不能。(活扣能解開,死扣解不開)
饑餓:一個或者多個線程因?yàn)榉N種原因無法獲得所需要的資源,導(dǎo)致一直無法執(zhí)行的狀態(tài)。
Java 中導(dǎo)致饑餓的原因:
1、高優(yōu)先級線程吞噬所有的低優(yōu)先級線程的 CPU 時間。(排隊(duì)打飯時領(lǐng)導(dǎo)說今天有參觀團(tuán),飯都留給人家觀光團(tuán)吃了)
2、線程被永久堵塞在一個等待進(jìn)入同步塊的狀態(tài),因?yàn)槠渌€程總是能在它之前持續(xù)地對該同步塊進(jìn)行訪問。(打飯時一直有領(lǐng)導(dǎo)插隊(duì),一直沒打到飯)
3、線程在等待一個本身也處于永久等待完成的對象(比如調(diào)用這個對象的 wait 方法),因?yàn)槠渌€程總是被持續(xù)地獲得喚醒(打飯時你在等打飯師傅來,結(jié)果打飯師傅在家睡覺)
26. final不可變對象,它對寫并發(fā)應(yīng)用有什么幫助?
不可變對象(Immutable Objects)即對象一旦被創(chuàng)建它的狀態(tài)(對象的數(shù)據(jù),也即對象屬性值)就不能改變,反之即為可變對象(Mutable Objects)。
不可變對象的類即為不可變類(Immutable Class)。Java 平臺類庫中包含許多不可變類,如String、基本類型的包裝類、BigInteger 和 BigDecimal 等。
只有滿足如下狀態(tài),一個對象才是不可變的;
它的狀態(tài)不能在創(chuàng)建后再被修改;
所有域都是 final 類型;并且,它被正確創(chuàng)建(創(chuàng)建期間沒有發(fā)生 this 引用的逸出)。
不可變對象保證了對象的內(nèi)存可見性,對不可變對象的讀取不需要進(jìn)行額外的同步手段,提升了代碼執(zhí)行效率。