java+queue+se_「013期」JavaSE面试题(十三):多线程(3)
本文轉(zhuǎn)載自【微信公眾號:java進階架構(gòu)師,ID:java_jiagoushi】經(jīng)微信公眾號授權(quán)轉(zhuǎn)載,如需轉(zhuǎn)載與原文作者聯(lián)系
開篇介紹
大家好,我是Java面試題庫的提褲姐,今天這篇是JavaSE系列的第十一篇,主要總結(jié)了Java中的多線程問題,多線程分為三篇來講,這篇是第三篇,在后續(xù),會沿著第一篇開篇的知識線路一直總結(jié)下去,做到日更!如果我能做到百日百更,希望你也可以跟著百日百刷,一百天養(yǎng)成一個好習慣。
Q:
volatile關(guān)鍵字的作用?
對于可見性,Java提供了volatile關(guān)鍵字來保證可見性。當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內(nèi)存中讀取新值。主要的原理是使用了內(nèi)存指令。
LoadLoad重排序:一個處理器先執(zhí)行一個L1讀操作,再執(zhí)行一個L2讀操作;但是另外一個處理器看到的是先L2再L1StoreStore重排序:一個處理器先執(zhí)行一個W1寫操作,再執(zhí)行一個W2寫操作;但是另外一個處理器看到的是先W2再W1LoadStore重排序:一個處理器先執(zhí)行一個L1讀操作,再執(zhí)行一個W2寫操作;但是另外一個處理器看到的是先W2再L1StoreLoad重排序:一個處理器先執(zhí)行一個W1寫操作,再執(zhí)行一個L2讀操作;但是另外一個處理器看到的是先L2再W1
Q:
說一下volatile關(guān)鍵字對原子性、可見性以及有序性的保證?
在volatile變量寫操作的前面會加入一個
Release
屏障,然后在之后會加入一個
Store
屏障,這樣就可以保證volatile寫跟Release屏障之前的任何讀寫操作都不會指令重排,然后Store屏障保證了,寫完數(shù)據(jù)之后,立馬會執(zhí)行flush處理器緩存的操作。
在volatile變量讀操作的前面會加入一個
Load
屏障,這樣就可以保證對這個變量的讀取時,如果被別的處理器修改過了,必須得從其他 處理器的高速緩存(或者主內(nèi)存)中加載到自己本地高速緩存里,保證讀到的是最新數(shù)據(jù);在之后會加入一個
Acquire
屏障,禁止volatile讀操作之后的任何讀寫操作會跟volatile讀指令重排序。與volatie讀寫內(nèi)存屏障對比一下,是類似的意思。
Acquire屏障
其實就是
LoadLoad屏障 + LoadStore屏障
,
Release屏障
其實就是
StoreLoad屏障 + StoreStore屏障
Q:
什么是CAS?
CAS(compare and swap)的縮寫。Java利用CPU的CAS指令,同時借助JNI來完成Java的非阻塞算法,實現(xiàn)原子操作。其它原子操作都是利用類似的特性完成的。 CAS有3個操作數(shù):
內(nèi)存值V
,
舊的預期值A(chǔ)
,
要修改的新值B
。當且僅當預期值A(chǔ)和內(nèi)存值V相同時,將內(nèi)存值V修改為B,否則什么都不做。CAS的缺點:
CPU開銷過大在并發(fā)量比較高的情況下,如果許多線程反復嘗試更新某一個變量,卻又一直更新不成功,循環(huán)往復,會給CPU帶來很到的壓力。不能保證代碼塊的原子性CAS機制所保證的知識一個變量的原子性操作,而不能保證整個代碼塊的原子性。比如需要保證3個變量共同進行原子性的更新,就不得不使用synchronized了。ABA問題這是CAS機制最大的問題所在。
Q:
什么是AQS?
AQS,即AbstractQueuedSynchronizer,
隊列同步器
,它是Java并發(fā)用來構(gòu)建鎖和其他同步組件的基礎(chǔ)框架。
同步組件對AQS的使用: AQS是一個抽象類,主是是以繼承的方式使用。AQS本身是沒有實現(xiàn)任何同步接口的,它僅僅只是定義了同步狀態(tài)的獲取和釋放的方法來供自定義的同步組件的使用。查看源碼可知,在java的同步組件中,AQS的子類(Sync等)一般是同步組件的靜態(tài)內(nèi)部類,即通過組合的方式使用。 抽象的隊列式的同步器,AQS定義了一套多線程訪問共享資源的同步器框架,許多同步類實現(xiàn)都依賴于它,如常用的ReentrantLock/Semaphore/CountDownLatch 它維護了一個volatile int state(代表共享資源)和一個FIFO(雙向隊列)線程等待隊列(多線程爭用資源被阻塞時會進入此隊列)
1public class CountDownLatch {2 /** 3 * Synchronization control For CountDownLatch. 4 * Uses AQS state to represent count. 5 */ 6 private static final class Sync extends AbstractQueuedSynchronizer { 7 private static final long serialVersionUID = 4982264981922014374L; 8 9 Sync(int count) {10 setState(count);11 }1213 int getCount() {14 return getState();15 }1617 protected int tryAcquireShared(int acquires) {18 return (getState() == 0) ? 1 : -1;19 }2021 protected boolean tryReleaseShared(int releases) {22 // Decrement count; signal when transition to zero23 for (;;) {24 int c = getState();25 if (c == 0)26 return false;27 int nextc = c-1;28 if (compareAndSetState(c, nextc))29 return nextc == 0;30 }31 }32 }33}
Q:
Semaphore是什么?
Semaphore就是一個信號量,它的作用是限制某段代碼塊的并發(fā)數(shù)。 semaphore有一個構(gòu)造函數(shù),可以傳入一個int型整數(shù)n,表示某段代碼最多只有n個線程可以訪問,如果超出了n,那么請等待,等到某個線程執(zhí)行完畢這段代碼塊,下一個線程再進入。由此可以看出如果Semaphore構(gòu)造函數(shù)中傳入的int型整數(shù)n=1,相當于變成了一個synchronized了。
1public static void main(String[] args) { 2 int N = 8; //工人數(shù) 3 Semaphore semaphore = new Semaphore(5); //機器數(shù)目 4 for(int i=0;i
Q:
Synchronized的原理是什么?
Synchronized是由JVM實現(xiàn)的一種實現(xiàn)互斥同步的方式,查看被Synchronized修飾過的程序塊編譯后的字節(jié)碼,會發(fā)現(xiàn),被Synchronized修飾過的程序塊,在編譯前后被編譯器生成了
monitorenter
和
monitorexit
兩個字節(jié)碼指令。在虛擬機執(zhí)行到monitorenter指令時,首先要嘗試獲取對象的鎖:如果這個對象沒有鎖定,或者當前線程已經(jīng)擁有了這個對象的鎖,把鎖的計數(shù)器+1;當執(zhí)行monitorexit指令時,將鎖計數(shù)器-1;當計數(shù)器為0時,鎖就被釋放了。如果獲取對象失敗了,那當前線程就要阻塞等待,直到對象鎖被另外一個線程釋放為止。Java中Synchronize通過在對象頭設(shè)置標志,達到了獲取鎖和釋放鎖的目的。
Q:
為什么說Synchronized是非公平鎖?
非公平主要表現(xiàn)在獲取鎖的行為上,并非是按照申請鎖的時間前后給等待線程分配鎖的,每當鎖被釋放后,任何一個線程都有機會競爭到鎖,這樣做的目的是為了提高執(zhí)行性能,缺點是可能會產(chǎn)生線程饑餓現(xiàn)象。
Q:
JVM對java的原生鎖做了哪些優(yōu)化?
在Java6之前, Monitor的實現(xiàn)完全依賴底層操作系統(tǒng)的互斥鎖來實現(xiàn).
由于Java層面的線程與操作系統(tǒng)的原生線程有映射關(guān)系,如果要將一個線程進行阻塞或喚起都需要操作系統(tǒng)的協(xié)助,這就需要從用戶態(tài)切換到內(nèi)核態(tài)來執(zhí)行,這種切換代價十分昂貴,很耗處理器時間,現(xiàn)代JDK中做了大量的優(yōu)化。一種優(yōu)化是使用
自旋鎖
,即在把線程進行阻塞操作之前先讓線程自旋等待一段時間,可能在等待期間其他線程已經(jīng)解鎖,這時就無需再讓線程執(zhí)行阻塞操作,避免了用戶態(tài)到內(nèi)核態(tài)的切換。現(xiàn)代JDK中還提供了三種不同的 Monitor實現(xiàn),也就是三種不同的鎖:
偏向鎖(Biased Locking)輕量級鎖重量級鎖這三種鎖使得JDK得以優(yōu)化 Synchronized的運行,當JVM檢測到不同的競爭狀況時,會自動切換到適合的鎖實現(xiàn),這就是鎖的升級、降級。當沒有競爭出現(xiàn)時,默認會使用偏向鎖。JVM會利用CAS操作,在對象頭上的 Mark Word部分設(shè)置線程ID,以表示這個對象偏向于當前線程,所以并不涉及真正的互斥鎖,因為在很多應用場景中,大部分對象生命周期中最多會被一個線程鎖定,使用偏向鎖可以降低無競爭開銷。如果有另一線程試圖鎖定某個被偏向過的對象,JVM就撤銷偏向鎖,切換到輕量級鎖實現(xiàn)。輕量級鎖依賴CAS操作 Mark Word來試圖獲取鎖,如果重試成功,就使用普通的輕量級鎖否則,進一步升級為重量級鎖。
Q:
Synchronized和 ReentrantLock的異同?
synchronized:是java內(nèi)置的關(guān)鍵字,它提供了一種獨占的加鎖方式。synchronized的獲取和釋放鎖由JVM實現(xiàn),用戶不需要顯示的釋放鎖,非常方便。然而synchronized也有一些問題:當線程嘗試獲取鎖的時候,如果獲取不到鎖會一直阻塞。如果獲取鎖的線程進入休眠或者阻塞,除非當前線程異常,否則其他線程嘗試獲取鎖必須一直等待。
ReentrantLock:ReentrantLock是Lock的實現(xiàn)類,是一個互斥的同步鎖。ReentrantLock是JDK 1.5之后提供的API層面的互斥鎖,需要lock()和unlock()方法配合try/finally語句塊來完成。等待可中斷避免,出現(xiàn)死鎖的情況(如果別的線程正持有鎖,會等待參數(shù)給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false) 公平鎖與非公平鎖多個線程等待同一個鎖時,必須按照申請鎖的時間順序獲得鎖,Synchronized鎖非公平鎖,ReentrantLock默認的構(gòu)造函數(shù)是創(chuàng)建的非公平鎖,可以通過參數(shù)true設(shè)為公平鎖,但公平鎖表現(xiàn)的性能不是很好。
從功能角度:ReentrantLock比 Synchronized的同步操作更精細(因為可以像普通對象一樣使用),甚至實現(xiàn) Synchronized沒有的高級功能,如:
等待可中斷當持有鎖的線程長期不釋放鎖的時候,正在等待的線程可以選擇放棄等待,對處理執(zhí)行時間非常長的同步塊很有用。帶超時的獲取鎖嘗試在指定的時間范圍內(nèi)獲取鎖,如果時間到了仍然無法獲取則返回。可以判斷是否有線程在排隊等待獲取鎖。可以響應中斷請求與Synchronized不同,當獲取到鎖的線程被中斷時,能夠響應中斷,中斷異常將會被拋出,同時鎖會被釋放。可以實現(xiàn)公平鎖。從鎖釋放角度:Synchronized在JVM層面上實現(xiàn)的,不但可以通過一些監(jiān)控工具監(jiān)控 Synchronized的鎖定,而且在代碼執(zhí)行出現(xiàn)異常時,JVM會自動釋放鎖定,但是使用Lock則不行,Lock是通過代碼實現(xiàn)的,要保證鎖定一定會被釋放,就必須將unLock()放到finally{}中。
從性能角度:Synchronized早期實現(xiàn)比較低效,對比 ReentrantLock,大多數(shù)場景性能都相差較大。但是在Java6中對其進行了非常多的改進,在競爭不激烈時:Synchronized的性能要優(yōu)于 ReetrantLock;在高競爭情況下:Synchronized的性能會下降幾十倍,但是 ReetrantLock的性能能維持常態(tài)。
總結(jié)
以上是生活随笔為你收集整理的java+queue+se_「013期」JavaSE面试题(十三):多线程(3)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 协程框架_GitHub - y
- 下一篇: 信用卡逾期没能力一次性还清怎么办 可以