什么是線程和進(jìn)程?
進(jìn)程是程序的一次執(zhí)行過程锨并,是系統(tǒng)運(yùn)行程序的基本單位。系統(tǒng)運(yùn)行程序是一個(gè)進(jìn)程從創(chuàng)建到消亡的過程帖努。在java中撰豺,當(dāng)我們啟動(dòng)main函數(shù),其實(shí)就是啟動(dòng)了一個(gè)jvm的進(jìn)程拼余。main函數(shù)所在的線程就是這個(gè)進(jìn)程中的一個(gè)線程污桦,這個(gè)線程也加主線程。
線程是一個(gè)比進(jìn)程更小的執(zhí)行單位匙监,一個(gè)進(jìn)程在其執(zhí)行的過程中可以產(chǎn)生多個(gè)線程凡橱。與進(jìn)程不同的是同類的多個(gè)線程共享進(jìn)程的堆和方法區(qū)資源小作,但每個(gè)線程有自己的程序計(jì)數(shù)器、虛擬機(jī)棧和本地方法棧稼钩,所以系統(tǒng)在產(chǎn)生一個(gè)線程顾稀,或是在各個(gè)線程之間作切換工作時(shí),負(fù)擔(dān)要比進(jìn)程小得多坝撑,也正因?yàn)槿绱司哺眩€程也被稱為輕量級(jí)進(jìn)程。
請(qǐng)簡(jiǎn)要描述線程與進(jìn)程的關(guān)系,區(qū)別及優(yōu)缺點(diǎn)巡李?
一個(gè)進(jìn)程中可以有多個(gè)線程抚笔,多個(gè)線程共享進(jìn)程的堆、方法區(qū)(jdk1.8之后的元空間)侨拦,但是每個(gè)線程有自己的程序計(jì)數(shù)器殊橙,虛擬機(jī)棧和本地方法棧。
線程是進(jìn)程劃分成的更小的運(yùn)行單位狱从。線程和進(jìn)程最大的不同在于基本上各進(jìn)程是獨(dú)立的膨蛮,而各線程則不一定,因?yàn)橥贿M(jìn)程中的線程極有可能會(huì)相互影響季研。線程執(zhí)行開銷小敞葛,但不利于資源的管理和保護(hù);而進(jìn)程正相反与涡。
程序計(jì)數(shù)器為什么是私有的?
字節(jié)碼解釋器通過改變程序計(jì)數(shù)器來依次讀取指令制肮,從而實(shí)現(xiàn)代碼的流程控制。
在多線程的情況下递沪,程序計(jì)數(shù)器用于記錄當(dāng)前線程執(zhí)行的位置,從而當(dāng)線程被切換回來的時(shí)候能夠知道該線程上次運(yùn)行到哪兒了综液。
如果執(zhí)行的是 native 方法款慨,那么程序計(jì)數(shù)器記錄的是 undefined 地址,只有執(zhí)行的是 Java 代碼時(shí)程序計(jì)數(shù)器記錄的才是下一條指令的地址谬莹。
程序計(jì)數(shù)器私有主要是為了線程切換后能恢復(fù)到正確的執(zhí)行位置檩奠。
虛擬機(jī)棧和本地方法棧為什么是私有的?
虛擬機(jī)棧: 每個(gè) Java 方法在執(zhí)行的同時(shí)會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表、操作數(shù)棧附帽、常量池引用等信息埠戳。從方法調(diào)用直至執(zhí)行完成的過程,就對(duì)應(yīng)著一個(gè)棧幀在 Java 虛擬機(jī)棧中入棧和出棧的過程蕉扮。
本地方法棧: 和虛擬機(jī)棧所發(fā)揮的作用非常相似整胃,區(qū)別是: 虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法 (也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的 Native 方法服務(wù)喳钟。 在 HotSpot 虛擬機(jī)中和 Java 虛擬機(jī)棧合二為一屁使。
一句話簡(jiǎn)單了解堆和方法區(qū)在岂?
堆和方法區(qū)是所有線程共享的資源,其中堆是進(jìn)程中最大的一塊內(nèi)存蛮寂,主要用于存放新創(chuàng)建的對(duì)象 (幾乎所有對(duì)象都在這里分配內(nèi)存)蔽午,方法區(qū)主要用于存放已被加載的類信息、常量酬蹋、靜態(tài)變量及老、以及編譯器編譯后的代碼等數(shù)據(jù)。
說說并發(fā)與并行的區(qū)別?
并發(fā):某一個(gè)時(shí)間段同時(shí)進(jìn)行多個(gè)事情范抓,但是同一時(shí)刻可能并沒有同時(shí)進(jìn)行 在計(jì)算機(jī)科學(xué)中骄恶,多個(gè)線程或者是多個(gè)進(jìn)程同時(shí)在單核或者多核cpu上執(zhí)行時(shí),執(zhí)行路徑具有不確定性的這種情形尉咕。
并行:同一時(shí)刻叠蝇,同時(shí)進(jìn)行多個(gè)事情。
為什么要使用多線程呢?
從計(jì)算機(jī)底層來說: 單核時(shí)代年缎,主要是為了提高單線程利用CPU和IO系統(tǒng)的效率悔捶,當(dāng)線程被IO阻塞時(shí),能充分利用CPU单芜。多核時(shí)代蜕该,主要是為了提高單線程對(duì)多核CPU能力的利用,例如洲鸠,假設(shè)在多核CPU上只有一個(gè)線程執(zhí)行堂淡,其他CPU資源就會(huì)被浪費(fèi)掉。
使用多線程可能帶來什么問題?
并發(fā)編程是為了提高程序的執(zhí)行效率和運(yùn)行速度(多核多線程)扒腕,但是多線程不一定都能提高運(yùn)行速度(單核多線程)绢淀。并且多線程可能會(huì)遇到內(nèi)存泄漏、死鎖瘾腰、線程不安全等問題皆的。
說說線程的生命周期和狀態(tài)?
什么是上下文切換?
線程在執(zhí)行過程中,會(huì)有自己的運(yùn)行條件和狀態(tài)蹋盆,例如程序計(jì)數(shù)器费薄,棧信息等。當(dāng)出現(xiàn)如下條件時(shí)栖雾,線程會(huì)從CPU占用狀態(tài)退出楞抡。
主動(dòng)退出CPU,比如調(diào)用了sleep析藕,wait等
時(shí)間片用完
調(diào)用了阻塞類系統(tǒng)中斷召廷,比如IO,線程被阻塞、被終止或結(jié)束運(yùn)行
前面三種情況會(huì)發(fā)生線程上下文切換
什么是線程死鎖?如何避免死鎖?
死鎖:兩個(gè)及以上的線程同時(shí)阻塞柱恤,并互相等待對(duì)方釋放對(duì)方占用的資源数初,并且會(huì)一直等待下去,導(dǎo)致程序無法正常結(jié)束梗顺。
-
死鎖產(chǎn)生必須具備以下四個(gè)條件:
互斥條件:該資源任意一個(gè)時(shí)刻只由一個(gè)線程占用
請(qǐng)求與保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí)泡孩,對(duì)已獲得的資源保持不放
不剝奪條件: 線程已獲得的資源在未使用完之前不能被其他線程強(qiáng)行剝奪,只有自己使用完畢后才釋放資源
循環(huán)等待條件: 若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系
-
如何預(yù)防死鎖寺谤?破壞死鎖的產(chǎn)生的必要條件即可:
破壞請(qǐng)求與保持條件(不釋放條件) :一次性申請(qǐng)所有的資源
破壞不剝奪條件 :占用部分資源的線程進(jìn)一步申請(qǐng)其他資源時(shí)仑鸥,如果申請(qǐng)不到,可以主動(dòng)釋放它占有的資源
破壞循環(huán)等待條件 :靠按序申請(qǐng)資源來預(yù)防变屁。按某一順序申請(qǐng)資源眼俊,釋放資源則反序釋放。例如要求按照A粟关、B疮胖、C的順序申請(qǐng)A、B闷板、C三個(gè)資源澎灸,則不會(huì)發(fā)生死鎖
-
如何避免死鎖?
避免死鎖就是在資源分配時(shí)遮晚,借助于算法(比如銀行家算法)對(duì)資源分配進(jìn)行計(jì)算評(píng)估性昭,使其進(jìn)入安全狀態(tài)。
安全狀態(tài): 指的是系統(tǒng)能夠按照某種進(jìn)程推進(jìn)順序(P1县遣、P2糜颠、P3.....Pn)來為每個(gè)進(jìn)程分配所需資源,直到滿足每個(gè)進(jìn)程對(duì)資源的最大需求萧求,使每個(gè)進(jìn)程都可順利完成其兴。稱<P1、P2夸政、P3.....Pn>序列為安全序列.
說說 sleep() 方法和 wait() 方法區(qū)別和共同點(diǎn)?
兩者最主要的區(qū)別在于:sleep() 方法沒有釋放鎖忌警,而 wait() 方法釋放了鎖 。
兩者都可以暫停線程的執(zhí)行秒梳。
wait() 通常被用于線程間交互/通信,sleep()通常被用于暫停執(zhí)行箕速。
wait() 方法被調(diào)用后酪碘,線程不會(huì)自動(dòng)蘇醒,需要?jiǎng)e的線程調(diào)用同一個(gè)對(duì)象上的 notify()或者 notifyAll() 方法盐茎。sleep()方法執(zhí)行完成后兴垦,線程會(huì)自動(dòng)蘇醒。或者可以使用 wait(long timeout) 超時(shí)后線程會(huì)自動(dòng)蘇醒探越。
為什么我們調(diào)用 start() 方法時(shí)會(huì)執(zhí)行 run() 方法狡赐,為什么我們不能直接調(diào)用 run() 方法?
new 一個(gè) Thread钦幔,線程進(jìn)入了新建狀態(tài)枕屉。調(diào)用 start()方法,會(huì)啟動(dòng)一個(gè)線程并使線程進(jìn)入了就緒狀態(tài)鲤氢,當(dāng)分配到時(shí)間片后就可以開始運(yùn)行了搀擂。 start() 會(huì)執(zhí)行線程的相應(yīng)準(zhǔn)備工作,然后自動(dòng)執(zhí)行 run() 方法的內(nèi)容卷玉,這是真正的多線程工作哨颂。 但是,直接執(zhí)行 run() 方法相种,會(huì)把 run() 方法當(dāng)成一個(gè) main 線程下的普通方法去執(zhí)行威恼,并不會(huì)在某個(gè)線程中執(zhí)行它,所以這并不是多線程工作寝并。
總結(jié): 調(diào)用 start() 方法方可啟動(dòng)線程并使線程進(jìn)入就緒狀態(tài)箫措,直接執(zhí)行 run() 方法的話不會(huì)以多線程的方式執(zhí)行。
說一說自己對(duì)于 synchronized 關(guān)鍵字的了解食茎?
synchronized 關(guān)鍵字解決的是多個(gè)線程之間訪問資源的同步性蒂破,synchronized關(guān)鍵字可以保證被它修飾的方法或者代碼塊在任意時(shí)刻只能有一個(gè)線程執(zhí)行。
-
在 Java 早期版本中别渔,synchronized 屬于 重量級(jí)鎖附迷,效率低下:
因?yàn)楸O(jiān)視器鎖(monitor)是依賴于底層的操作系統(tǒng)的 Mutex Lock(互斥鎖) 來實(shí)現(xiàn)的,Java 的線程是映射到操作系統(tǒng)的原生線程之上的哎媚,而線程切換是需要從用戶態(tài)切換到內(nèi)核態(tài)喇伯,這個(gè)狀態(tài)轉(zhuǎn)換的時(shí)間成本比較高。
DK1.6 對(duì)鎖的實(shí)現(xiàn)引入了大量的優(yōu)化拨与,如自旋鎖稻据、自適應(yīng)鎖、鎖消除买喧、鎖粗化捻悯、偏向鎖、輕量級(jí)鎖等技術(shù)來減少鎖操作的開銷淤毛。
所以今缚,目前不論是各種開源框架還是 JDK 源碼都大量使用了 synchronized 關(guān)鍵字。
說說自己是怎么使用 synchronized 關(guān)鍵字低淡?
修飾實(shí)例方法: 作用于當(dāng)前對(duì)象實(shí)例加鎖姓言,進(jìn)入同步代碼前要獲得 當(dāng)前對(duì)象實(shí)例的鎖
修飾靜態(tài)方法: 也就是給當(dāng)前類加鎖瞬项,會(huì)作用于類的所有對(duì)象實(shí)例 ,進(jìn)入同步代碼前要獲得 當(dāng)前 class 的鎖何荚,靜態(tài)方法鎖和實(shí)例方法鎖可以同時(shí)調(diào)用囱淋,一個(gè)鎖類,一個(gè)鎖實(shí)例餐塘,不會(huì)形成互斥
修飾代碼塊 :指定加鎖對(duì)象妥衣,對(duì)給定對(duì)象/類加鎖
盡量不要使用 synchronized(String a) 因?yàn)?JVM 中,字符串常量池具有緩存功能唠倦!
雙重校驗(yàn)鎖實(shí)現(xiàn)對(duì)象單例称鳞,為什么需要用volatile關(guān)鍵字修飾成員變量?
-
new對(duì)象分為三步完成:
為新對(duì)象分配地址空間
初始化新對(duì)象
將分配的地址空間指向新對(duì)象
new對(duì)象的過程存在指令重拍稠鼻,如果順序?yàn)?->3->2可能導(dǎo)致其他線程獲取到?jīng)]有正常初始化的對(duì)象冈止。
使用 volatile 可以禁止 JVM 的指令重排,保證在多線程環(huán)境下也能正常運(yùn)行候齿。
構(gòu)造方法可以使用 synchronized 關(guān)鍵字修飾么熙暴?
- 構(gòu)造方法不能使用 synchronized 關(guān)鍵字修飾。
講一下 synchronized 關(guān)鍵字的底層原理慌盯?
-
synchronized 同步代碼塊的情況:
synchronized 同步語句塊的實(shí)現(xiàn)使用的是 monitorenter 和 monitorexit 指令周霉,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結(jié)束位置
當(dāng)執(zhí)行 monitorenter 指令時(shí)亚皂,線程試圖獲取鎖也就是獲取 對(duì)象監(jiān)視器 monitor 的持有權(quán)
wait/notify等方法也依賴于monitor對(duì)象俱箱,這就是為什么只有在同步的塊或者方法中才能調(diào)用wait/notify等方法,否則會(huì)拋出java.lang.IllegalMonitorStateException的異常的原因
在執(zhí)行monitorenter時(shí)灭必,會(huì)嘗試獲取對(duì)象的鎖狞谱,如果鎖的計(jì)數(shù)器為 0 則表示鎖可以被獲取,獲取后將鎖計(jì)數(shù)器設(shè)為 1 也就是加 1
在執(zhí)行 monitorexit 指令后禁漓,將鎖計(jì)數(shù)器設(shè)為 0跟衅,表明鎖被釋放。如果獲取對(duì)象鎖失敗播歼,那當(dāng)前線程就要阻塞等待伶跷,直到鎖被另外一個(gè)線程釋放為止。
-
synchronized 修飾方法的的情況:
- synchronized 修飾的方法并沒有 monitorenter 指令和 monitorexit 指令秘狞,取得代之的確實(shí)是 ACC_SYNCHRONIZED 標(biāo)識(shí)叭莫,該標(biāo)識(shí)指明了該方法是一個(gè)同步方法。JVM 通過該 ACC_SYNCHRONIZED 訪問標(biāo)志來辨別一個(gè)方法是否聲明為同步方法烁试,從而執(zhí)行相應(yīng)的同步調(diào)用雇初。
不過兩者的本質(zhì)都是對(duì)對(duì)象監(jiān)視器 monitor 的獲取。
說說 JDK1.6 之后的 synchronized 關(guān)鍵字底層做了哪些優(yōu)化廓潜,可以詳細(xì)介紹一下這些優(yōu)化嗎?
JDK1.6 對(duì)鎖的實(shí)現(xiàn)引入了大量的優(yōu)化,如偏向鎖、輕量級(jí)鎖砾肺、自旋鎖碟贾、自適應(yīng)性鎖、鎖消除悼院、鎖粗化等技術(shù)來減少鎖操作的開銷伤为。
鎖主要存在四種狀態(tài),依次是:無鎖狀態(tài)据途、偏向鎖狀態(tài)绞愚、輕量級(jí)鎖狀態(tài)、重量級(jí)鎖狀態(tài)颖医,他們會(huì)隨著競(jìng)爭(zhēng)的激烈而逐漸升級(jí)位衩。注意鎖可以升級(jí)不可降級(jí),這種策略是為了提高獲得鎖和釋放鎖的效率熔萧。
鎖升級(jí)過程糖驴?
無鎖 -> 偏向鎖 -> 輕量級(jí)鎖 -> 重量級(jí)鎖。
-
偏向鎖:
線程獲取到鎖對(duì)象佛致,如果此時(shí)沒有其他線程占用鎖對(duì)象贮缕,就將鎖對(duì)象頭中的標(biāo)志位置為01,并將自己的線程ID記錄在鎖對(duì)象頭的Mark Work的偏向鎖線程ID中俺榆,同時(shí)將偏向鎖狀態(tài)置為1感昼。
當(dāng)前當(dāng)前線程再次加鎖,直接鎖加1罐脊,可重入定嗓。如果其他線程競(jìng)爭(zhēng),判斷當(dāng)前線程是否還需要鎖爹殊,需要?jiǎng)t釋放偏向鎖蜕乡,升級(jí)為輕量級(jí)鎖。如果不需要鎖梗夸,則釋放鎖层玲,其他線程競(jìng)爭(zhēng),繼續(xù)偏向鎖反症。
-
輕量級(jí)鎖:
- 當(dāng)有另外一個(gè)線程競(jìng)爭(zhēng)獲取這個(gè)鎖時(shí)辛块,由于該鎖已經(jīng)是偏向鎖,當(dāng)發(fā)現(xiàn)對(duì)象頭 Mark Word 中的線程 ID 不是自己的線程 ID铅碍,就會(huì)進(jìn)行 CAS 操作獲取鎖润绵,如果獲取成功,直接替換 Mark Word 中的線程 ID 為自己的 ID胞谈,該鎖會(huì)保持偏向鎖狀態(tài)尘盼;如果獲取鎖失敗憨愉,代表當(dāng)前鎖有一定的競(jìng)爭(zhēng),偏向鎖將升級(jí)為輕量級(jí)鎖卿捎。
-
重量級(jí)鎖:
- 當(dāng)前鎖處于輕量級(jí)鎖時(shí)配紫,如果其他線程來競(jìng)爭(zhēng)鎖,此時(shí)會(huì)進(jìn)行自旋午阵。自旋鎖重試之后如果搶鎖依然失敗躺孝,輕量級(jí)鎖就會(huì)升級(jí)至重量級(jí)鎖,鎖標(biāo)志位改為 10底桂。在這個(gè)狀態(tài)下植袍,未搶到鎖的線程都會(huì)進(jìn)入 Monitor,之后會(huì)被阻塞在 _WaitSet 隊(duì)列中籽懦。
-
偏向鎖:適用于單線程適用鎖的情況
輕量級(jí)鎖:適用于競(jìng)爭(zhēng)較不激烈的情況(這和樂觀鎖的使用范圍類似)
重量級(jí)鎖:適用于競(jìng)爭(zhēng)激烈的情況
談?wù)?synchronized 和 ReentrantLock 的區(qū)別于个?
兩者都是可重入鎖,是指線程可以再次獲取自己已經(jīng)獲取的鎖猫十。比如一個(gè)線程已經(jīng)獲取到了A對(duì)象上的鎖览濒,此時(shí)這個(gè)線程還能再次獲取A對(duì)象上的鎖,只是鎖計(jì)數(shù)加1拖云,釋放鎖時(shí)贷笛,依次釋放直到鎖計(jì)數(shù)為0,才算釋放鎖宙项。如果不支持重入的話乏苦,會(huì)造成死鎖。
synchronized 依賴于 JVM 而 ReentrantLock 依賴于 API尤筐,synchronized的監(jiān)視器鎖(monitor)是依賴于底層的操作系統(tǒng)的 Mutex Lock(互斥鎖) 來實(shí)現(xiàn)的汇荐,Java1.6對(duì)synchronized的優(yōu)化都是在jvm層面實(shí)現(xiàn)的;ReentrantLock 是 JDK 層面實(shí)現(xiàn)的(也就是 API 層面盆繁,需要 lock() 和 unlock() 方法配合 try/finally 語句塊來完成)掀淘。
-
ReentrantLock 比 synchronized 增加了一些高級(jí)功能,可以實(shí)現(xiàn)更精細(xì)的鎖控制:
等待可中斷: ReentrantLock提供了一種能夠中斷等待鎖的線程的機(jī)制油昂,通過 lock.lockInterruptibly() 來實(shí)現(xiàn)這個(gè)機(jī)制革娄。也就是說正在等待的線程可以選擇放棄等待,改為處理其他事情
可實(shí)現(xiàn)公平鎖:ReentrantLock可以指定是公平鎖還是非公平鎖冕碟。而synchronized只能是非公平鎖拦惋。所謂的公平鎖就是先等待的線程先獲得鎖。ReentrantLock默認(rèn)情況是非公平的安寺,可以通過 ReentrantLock類的ReentrantLock(boolean fair)構(gòu)造方法來制定是否是公平的
可實(shí)現(xiàn)選擇性通知(鎖可以綁定多個(gè)條件): synchronized關(guān)鍵字與wait()和notify()/notifyAll()方法相結(jié)合可以實(shí)現(xiàn)等待/通知機(jī)制厕妖。ReentrantLock類當(dāng)然也可以實(shí)現(xiàn),但是需要借助于Condition接口與newCondition()方法挑庶。notify()/notifyAll()具體喚醒的線程依賴于操作系統(tǒng)的線程調(diào)度言秸。而Condition實(shí)例的signalAll()方法 只會(huì)喚醒注冊(cè)在該Condition實(shí)例中的所有等待線程
volatile 關(guān)鍵字的作用软能?
禁止指令重排
保證共享變量在多線程環(huán)境下的可見性
為什么要使用CPU緩存?
CPU緩存在寄存器和主存直接設(shè)置了一層CPU緩存举畸,寄存器每次都先從寄存器中獲取數(shù)據(jù)埋嵌,如果獲取不到才到主存中讀取數(shù)據(jù)。
CPU緩存為了解決CPU處理速度和內(nèi)存處理速度不匹配的問題俱恶,內(nèi)存緩存是用于解決內(nèi)存訪問太慢的問題。
談一談JMM范舀?
JMM是java內(nèi)存模型合是,模型是說java的線程都有自己的本地內(nèi)存,可以將數(shù)據(jù)緩存到本地內(nèi)存中锭环,在線程執(zhí)行過程中聪全,可以先去本地內(nèi)存中讀取數(shù)據(jù),而不是直接去主存中讀取辅辩。在多線程的環(huán)境下难礼,如果一個(gè)線程修改了主存中的數(shù)據(jù),而另一個(gè)線程從它本地內(nèi)存中讀取到的是沒有修改過的緩存數(shù)據(jù)玫锋,從而導(dǎo)致數(shù)據(jù)不一致蛾茉。
并發(fā)編程的三個(gè)重要特征?
原子性: 一個(gè)的操作或者多次操作撩鹿,要么所有的操作全部都得到執(zhí)行并且不會(huì)受到任何因素的干擾而中斷谦炬,要么所有的操作都執(zhí)行,要么都不執(zhí)行节沦。synchronized 可以保證代碼片段的原子性键思。
可見性:當(dāng)一個(gè)線程對(duì)共享變量進(jìn)行了修改,那么另外的線程都是立即可以看到修改后的最新值甫贯。volatile 關(guān)鍵字可以保證共享變量的可見性吼鳞。
有序性:代碼在執(zhí)行的過程中的先后順序,Java 在編譯器以及運(yùn)行期間的優(yōu)化叫搁,代碼的執(zhí)行順序未必就是編寫代碼時(shí)候的順序赔桌。volatile 關(guān)鍵字可以禁止指令進(jìn)行重排序優(yōu)化。
說說 synchronized 關(guān)鍵字和 volatile 關(guān)鍵字的區(qū)別常熙?
synchronized 關(guān)鍵字和 volatile 關(guān)鍵字是兩個(gè)互補(bǔ)的存在纬乍,而不是對(duì)立的存在。
volatile 關(guān)鍵字是線程同步的輕量級(jí)實(shí)現(xiàn)裸卫,所以 volatile性能肯定比synchronized關(guān)鍵字要好 仿贬。但是 volatile 關(guān)鍵字只能用于變量而 synchronized 關(guān)鍵字可以修飾方法以及代碼塊 。
volatile 關(guān)鍵字能保證數(shù)據(jù)的可見性墓贿,但不能保證原子性茧泪。synchronized 關(guān)鍵字兩者都能保證蜓氨。
volatile關(guān)鍵字主要用于解決變量在多個(gè)線程之間的可見性,而 synchronized 關(guān)鍵字解決的是多個(gè)線程之間訪問資源的同步性队伟。
ThreadLocal穴吹?
ThreadLocal是線程保存線程事由變量的對(duì)象,在當(dāng)前線程中對(duì)當(dāng)前線程ThreadLocal中的變量進(jìn)行修改嗜侮,只會(huì)影響到當(dāng)前線程的ThreadLocal中的變量港令,不會(huì)影響到其他線程的ThreadLocal變量。
ThreadLocal內(nèi)部持有一個(gè)ThreadLocalMap靜態(tài)內(nèi)部類锈颗,這個(gè)類類似java map的實(shí)現(xiàn)顷霹,保存的變量的K值是當(dāng)前的線程的ThreadLocal<?>對(duì)象實(shí)例,value是要保存的值對(duì)象击吱,一個(gè)對(duì)象可以持有多個(gè)ThreadLocal對(duì)象淋淀。
ThreadLocal 內(nèi)存泄露問題?
ThreadLocalMap 中使用的 key 為 ThreadLocal<?>對(duì)象實(shí)例的弱引用,而 value 是強(qiáng)引用覆醇。所以朵纷,如果弱引用對(duì)象實(shí)例沒有被外部強(qiáng)引用的情況下,在垃圾回收的時(shí)候永脓,key 會(huì)被清理掉袍辞,而 value 不會(huì)被清理掉。這樣一來常摧,ThreadLocalMap 中就會(huì)出現(xiàn) key 為 null 的 Entry革屠。假如我們不做任何措施的話,value 永遠(yuǎn)無法被 GC 回收排宰,這個(gè)時(shí)候就可能會(huì)產(chǎn)生內(nèi)存泄露似芝。ThreadLocalMap 實(shí)現(xiàn)中已經(jīng)考慮了這種情況,在調(diào)用 set()板甘、get()党瓮、remove() 方法的時(shí)候,會(huì)清理掉 key 為 null 的記錄盐类。使用完 ThreadLocal方法后 最好手動(dòng)調(diào)用remove()方法寞奸。
ThreadlocalMap的key設(shè)置成弱引用的原因?
在一個(gè)Thread的生命周期中在跳,如果Thread中的某個(gè)對(duì)象持有一個(gè)ThreadLocal對(duì)象枪萄,那么這個(gè)線程就有一個(gè)線程私有的變量ThreadLocal。
如果在線程生命周期中猫妙,持有ThreadLocal的對(duì)象被回收了瓷翻,那么ThreadLocal也應(yīng)該被回收掉,但是ThreadLocal是存放在Thread的ThreadLocalMap中的,如果ThreadLocalMap的key不是弱引用齐帚,則應(yīng)該被回收掉的ThreadLocal會(huì)一直被Thread的ThreadLocalMap持有妒牙,則在線程生命周期能訪問到一個(gè)應(yīng)該被回收的對(duì)象,是不應(yīng)該的对妄。
一個(gè)ThreadLocal對(duì)象被設(shè)置到線程中時(shí)湘今,至少有兩個(gè)引用,一個(gè)是ThreadLocalMap中的key對(duì)它有弱引用剪菱,一個(gè)是持有ThreadLocal的對(duì)象對(duì)它有強(qiáng)引用摩瞎。所以ThreadLocalMap的key被設(shè)置成弱引用,當(dāng)引用ThreadLocal的對(duì)象被回收后孝常,即強(qiáng)引用消失后愉豺,ThreadLocal對(duì)象只剩弱引用,會(huì)被回收掉茫因。ThreadLocalMap沒有對(duì)外提供操作key、value的api也是為了保證數(shù)據(jù)被回收的數(shù)據(jù)不能再被操作杖剪。
如何讓線程持有一個(gè)貫穿線程生命周期的ThreadLocal冻押?
將ThreadLocal設(shè)置成靜態(tài)變量(static修飾),這樣ThreadLocal對(duì)象會(huì)被類的Class對(duì)象持有盛嘿,會(huì)貫穿線程生命周期中洛巢。此時(shí)多個(gè)線程持有一個(gè)相同的ThreadLocal,每個(gè)ThreadLocal在各自的Thread中ThreadLocalMap中對(duì)應(yīng)的值是不一樣的次兆,所以線程私有變量的實(shí)現(xiàn)是依賴線程的ThreadLocalMap來實(shí)現(xiàn)的稿茉。
設(shè)計(jì)為非static的,長(zhǎng)對(duì)象(比如被spring管理的對(duì)象)的內(nèi)部芥炭,也不會(huì)被回收漓库。
為什么要用線程池?
線程池园蝠、數(shù)據(jù)庫連接池渺蒿、Http 連接池等等都是池化技術(shù)的應(yīng)用。池化技術(shù)主要是為了減少每次獲取資源的消耗彪薛,提高對(duì)資源的利用率茂装。例如數(shù)據(jù)庫連接,如果每次連接都去創(chuàng)建善延,開銷非常大少态。
線程池提供了對(duì)線程資源的限制和管理。 每個(gè)線程池還維護(hù)一些基本統(tǒng)計(jì)信息易遣,例如已完成任務(wù)的數(shù)量彼妻。
使用線程池的好處?
降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗澳骤。
提高響應(yīng)速度歧强。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行为肮。
提高線程的可管理性摊册。線程是稀缺資源,如果無限制的創(chuàng)建颊艳,不僅會(huì)消耗系統(tǒng)資源茅特,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配棋枕,調(diào)優(yōu)和監(jiān)控白修。
實(shí)現(xiàn) Runnable 接口和 Callable 接口的區(qū)別?
Runnable 接口 不會(huì)返回結(jié)果或拋出檢查異常。
Callable 接口 可以回結(jié)果或拋出檢查異常重斑。
Executors 可以實(shí)現(xiàn)將 Runnable 對(duì)象轉(zhuǎn)換成 Callable 對(duì)象 (Executors.callable(Runnable task) 或 Executors.callable(Runnable task, Object result).
執(zhí)行 execute()方法和 submit()方法的區(qū)別是什么呢兵睛?
execute()方法用于提交不需要返回值的任務(wù),所以無法判斷任務(wù)是否被線程池執(zhí)行成功與否窥浪。
submit()方法用于提交需要返回值的任務(wù)祖很。線程池會(huì)返回一個(gè) Future 類型的對(duì)象,通過這個(gè) Future 對(duì)象可以判斷任務(wù)是否執(zhí)行成功漾脂,并且可以通過 Future 的 get()方法來獲取返回值假颇,get()方法會(huì)阻塞當(dāng)前線程直到任務(wù)完成,而使用 get(long timeout骨稿,TimeUnit unit)方法則會(huì)阻塞當(dāng)前線程一段時(shí)間后立即返回笨鸡,這時(shí)候有可能任務(wù)沒有執(zhí)行完。
如何創(chuàng)建線程池?
-
通過ThreadPoolExecutor構(gòu)造方法實(shí)現(xiàn):
- ThreadPoolExecutor一共有三個(gè)構(gòu)造方法坦冠,一般使用參數(shù)最長(zhǎng)的構(gòu)造方法形耗,自己指定各種參數(shù)來創(chuàng)建線程池。
-
通過 Executor 框架的工具類 Executors 來實(shí)現(xiàn):
FixedThreadPool : 該方法返回一個(gè)固定線程數(shù)量的線程池辙浑。該線程池中的線程數(shù)量始終不變趟脂。當(dāng)有一個(gè)新的任務(wù)提交時(shí),線程池中若有空閑線程例衍,則立即執(zhí)行昔期。若沒有,則新的任務(wù)會(huì)被暫存在一個(gè)任務(wù)隊(duì)列中佛玄,待有線程空閑時(shí)硼一,便處理在任務(wù)隊(duì)列中的任務(wù)。
SingleThreadExecutor: 方法返回一個(gè)只有一個(gè)線程的線程池梦抢。若多余一個(gè)任務(wù)被提交到該線程池般贼,任務(wù)會(huì)被保存在一個(gè)任務(wù)隊(duì)列中,待線程空閑,按先入先出的順序執(zhí)行隊(duì)列中的任務(wù)哼蛆。
CachedThreadPool: 該方法返回一個(gè)可根據(jù)實(shí)際情況調(diào)整線程數(shù)量的線程池蕊梧。線程池的線程數(shù)量不確定,但若有空閑線程可以復(fù)用腮介,則會(huì)優(yōu)先使用可復(fù)用的線程肥矢。若所有線程均在工作,又有新的任務(wù)提交叠洗,則會(huì)創(chuàng)建新的線程處理任務(wù)甘改。所有線程在當(dāng)前任務(wù)執(zhí)行完畢后,將返回線程池進(jìn)行復(fù)用灭抑。
-
《阿里巴巴 Java 開發(fā)手冊(cè)》中強(qiáng)制線程池不允許使用 Executors 去創(chuàng)建十艾,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則腾节,規(guī)避資源耗盡的風(fēng)險(xiǎn):
FixedThreadPool 和 SingleThreadExecutor :允許請(qǐng)求的隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE 忘嫉,可能堆積大量的請(qǐng)求,從而導(dǎo)致 OOM案腺。
CachedThreadPool 和 ScheduledThreadPool : 允許創(chuàng)建的線程數(shù)量為 Integer.MAX_VALUE 庆冕,可能會(huì)創(chuàng)建大量線程,從而導(dǎo)致 OOM救湖。
ThreadPoolExecutor 最多參數(shù)構(gòu)造方法分析?
corePoolSize:核心線程數(shù)
maximumPoolSize:最大線程數(shù)
workQueue:緩存隊(duì)列
keepAliveTime:當(dāng)線程池中的線程數(shù)大于corePoolSize核心線程數(shù)時(shí)涎才,線程不會(huì)立即銷毀鞋既,而是等超過keepAliveTime時(shí)間后才會(huì)銷毀
unit:keepAliveTime的時(shí)間單位
threadFactory:線程工廠executor 創(chuàng)建新線程的時(shí)候會(huì)用到
handler:飽和策略
ThreadPoolExecutor 飽和策略定義?
- 當(dāng)同時(shí)運(yùn)行的線程數(shù)達(dá)到maximumPoolSize最大線程數(shù),且workQueue也已經(jīng)滿了時(shí)耍铜,再進(jìn)來新的任務(wù)時(shí)邑闺,ThreadPoolTaskExecutor定義的一些拒絕策略。
飽和策略棕兼?
AbortPolicy:拋出RejectedExecutionException來拒絕新任務(wù)的處理陡舅,這是默認(rèn)策略。
CallerRunsPolicy:直接在調(diào)用execute方法的線程中運(yùn)行被拒絕的任務(wù)伴挚,這種策略會(huì)降低對(duì)于新任務(wù)的提交速度靶衍,影響整體性能。如果程序能夠承受延遲茎芋,并且要求每個(gè)任務(wù)都被執(zhí)行颅眶,可以使用這種策略。
DiscardPolicy:不處理新任務(wù)田弥,直接丟棄掉涛酗。
DiscardOldestPolicy:拋棄最早的,未處理的任務(wù)。
線程池執(zhí)行過程分析商叹?
提交任務(wù)燕刻。
判斷corePoolSize是否已經(jīng)滿了,沒有滿剖笙,創(chuàng)建新線程執(zhí)行卵洗,滿了交給workQueue。
判斷workQueue是否已經(jīng)滿了枯途,沒有滿忌怎,添加到workQueue中,滿了交給maximumPoolSize酪夷。
判斷maximumPoolSize是否已經(jīng)滿了榴啸,沒有滿,創(chuàng)建新線程執(zhí)行晚岭,滿了交給handler鸥印。
判斷handler使用的哪種拒絕策略,按照拒絕策略進(jìn)行拒絕坦报。
介紹一下 Atomic 原子類库说?
- 原子類就是提供了一系列不可被中斷的操作的類。
java.util.concurrent片择,JUC 包中的原子類是哪 4 類?
-
基本類型:
AtomicInteger:整形原子類
AtomicLong:長(zhǎng)整型原子類
AtomicBoolean:布爾型原子類
-
數(shù)組類型:
AtomicIntegerArray:整形數(shù)組原子類
AtomicLongArray:長(zhǎng)整形數(shù)組原子類
AtomicReferenceArray:引用類型數(shù)組原子類
-
引用類型:
AtomicReference:引用類型原子類
AtomicStampedReference:原子更新帶有版本號(hào)的引用類型潜的。該類將整數(shù)值與引用關(guān)聯(lián)起來,可用于解決原子的更新數(shù)據(jù)和數(shù)據(jù)的版本號(hào)字管,可以解決使用 CAS 進(jìn)行原子更新時(shí)可能出現(xiàn)的 ABA 問題啰挪。
AtomicMarkableReference :原子更新帶有標(biāo)記位的引用類型
-
對(duì)象的屬性修改類型:
AtomicIntegerFieldUpdater:原子更新整形字段的更新器
AtomicLongFieldUpdater:原子更新長(zhǎng)整形字段的更新器
-
AtomicReferenceFieldUpdater:原子更新引用類型字段的更新器
講講 AtomicInteger 的使用?
public final int get()
//獲取當(dāng)前的值public final int getAndSet(int newValue)
//獲取當(dāng)前的值,并設(shè)置新的值public final int getAndIncrement()
//獲取當(dāng)前的值嘲叔,并自增public final int getAndDecrement()
//獲取當(dāng)前的值亡呵,并自減public final int getAndAdd(int delta)
//獲取當(dāng)前的值,并加上預(yù)期的值boolean compareAndSet(int expect, int update)
//如果輸入的數(shù)值等于預(yù)期值硫戈,則以原子方式將該值設(shè)置為輸入值(update)public final void lazySet(int newValue)
//最終設(shè)置為newValue,使用 lazySet 設(shè)置之后可能導(dǎo)致其他線程在之后的一小段時(shí)間內(nèi)還是可以讀到舊的值锰什。
AtomicInteger 線程安全原理簡(jiǎn)單分析?
- AtomicInteger主要使用了CAS + volatile + native方法來保證原子操作的丁逝,從而避免 synchronized 的高開銷汁胆,執(zhí)行效率大為提升。
- CAS 的原理是拿期望的值和原本的一個(gè)值作比較霜幼,如果相同則更新成新的值沦泌。
- UnSafe 類的 objectFieldOffset() 方法是一個(gè)本地方法,這個(gè)方法是用來拿到“原來的值”的內(nèi)存地址辛掠,返回值是 valueOffset谢谦。
- 另外 value 是一個(gè) volatile 變量释牺,在內(nèi)存中可見,因此 JVM 可以保證任何時(shí)刻任何線程總能拿到該變量的最新值回挽。
為什么會(huì)設(shè)計(jì)AQS同步器類没咙?
- AQS(AbstractQueuedSynchronizer)同步器的作者在論文中闡明了,幾乎任意一個(gè)同步器都可以基于它去實(shí)現(xiàn)其他類型的同步器千劈。例如我們可以基于ReentrantLock去實(shí)現(xiàn)Semaphore祭刚,反過來也可以實(shí)現(xiàn)。但是這種設(shè)計(jì)會(huì)帶來很大的復(fù)雜性和不靈活性墙牌。所以需要構(gòu)建一個(gè)各種同步器的基礎(chǔ)框架涡驮,即基于這個(gè)基礎(chǔ)框架,可以實(shí)現(xiàn)各種同步器喜滨。
- 由于java內(nèi)置的同步鎖synchronized關(guān)鍵字捉捅,存在潛在的饑餓問題,AQS的主要性能指標(biāo)之一就是要解決饑餓問題虽风,同時(shí)AQS也支持非公平的同步器棒口。
介紹一下AQS?
- AQS 的全稱為(AbstractQueuedSynchronizer)辜膝,這個(gè)類在java.util.concurrent.locks包下面
- AQS 是一個(gè)用來構(gòu)建鎖和同步器的基礎(chǔ)框架无牵,使用 AQS 能簡(jiǎn)單且高效地構(gòu)造出大量應(yīng)用廣泛的同步器,比如我們提到的 ReentrantLock厂抖,Semaphore茎毁,其他的諸如 ReentrantReadWriteLock,SynchronousQueue忱辅,F(xiàn)utureTask 等等都是基于 AQS 實(shí)現(xiàn)的七蜘。
AQS是基于CLH實(shí)現(xiàn)的,介紹一下CLH耕蝉?
CLH是排隊(duì)式自旋鎖論文三個(gè)作者的首字母崔梗,排隊(duì)時(shí)式自旋鎖要解決的問題是如何高效的訪問多核CPU的共享資源夜只。無論是一致性內(nèi)存訪問架構(gòu)(CPU -> 高速緩存 -> 總線 -> 內(nèi)存)垒在,還是非一致性內(nèi)存訪問架構(gòu)(內(nèi)存 -> CPU -> 總線)多線程之間的共享資源訪問在高并發(fā)的情況下,都是系統(tǒng)瓶頸扔亥。CLH就是為了解決這個(gè)問題而設(shè)計(jì)的场躯。
AQS原理介紹?
AQS使用一個(gè)int型的成員變量state來表示同步狀態(tài)旅挤,且這個(gè)狀態(tài)是volatile修飾的踢关,保證線程可見的。所有要獲取鎖的線程都會(huì)被封裝成一個(gè)PNode添加到一個(gè)FIFO的CLH隊(duì)列中粘茄,后一個(gè)節(jié)點(diǎn)總是自旋監(jiān)視前一個(gè)節(jié)點(diǎn)的狀態(tài)签舞,如果前一個(gè)節(jié)點(diǎn)的狀態(tài)是釋放鎖秕脓,則后一個(gè)節(jié)點(diǎn)可以嘗試去獲取鎖。
AQS支持的兩種資源共享方式儒搭?
-
獨(dú)占(Exclusive):只有一個(gè)線程執(zhí)行吠架,又可以分為公平鎖和非公平鎖
- 公平鎖:按照線程在隊(duì)列中的排隊(duì)順序,先到者先拿到鎖
- 非公平鎖:當(dāng)線程要獲取鎖時(shí)搂鲫,無視隊(duì)列順序直接去搶鎖傍药,誰搶到就是誰的
共享(Share):多個(gè)線程可以同時(shí)執(zhí)行,例如CountDownLatch魂仍、Semaphore拐辽、 CyclicBarrier、ReadWriteLock
ReentrantReadWriteLock可以看做是組合式擦酌,因?yàn)樽x鎖是多線程的俱诸,寫鎖是單線程的
AQS 組件總結(jié)?
- 信號(hào)量(Semaphore)-允許多個(gè)線程同時(shí)訪問: synchronized 和 ReentrantLock 都是一次只允許一個(gè)線程訪問某個(gè)資源仑氛,Semaphore(信號(hào)量)可以指定多個(gè)線程同時(shí)訪問某個(gè)資源
- 倒計(jì)時(shí)器(CountDownLatch): CountDownLatch 是一個(gè)同步工具類乙埃,用來協(xié)調(diào)多個(gè)線程之間的同步。這個(gè)工具通常用來控制線程等待锯岖,它可以讓某一個(gè)線程等待直到倒計(jì)時(shí)結(jié)束介袜,再開始執(zhí)行
- 循環(huán)柵欄(CyclicBarrier): CyclicBarrier 和 CountDownLatch 非常類似,它也可以實(shí)現(xiàn)線程間的技術(shù)等待出吹,但是它的功能比 CountDownLatch 更加復(fù)雜和強(qiáng)大遇伞。主要應(yīng)用場(chǎng)景和 CountDownLatch 類似
用過 CountDownLatch 么?什么場(chǎng)景下用的捶牢?
CountDownLatch 的作用就是 允許 count 個(gè)線程阻塞在一個(gè)地方鸠珠,直至所有線程的任務(wù)都執(zhí)行完畢。之前在項(xiàng)目中秋麸,有一個(gè)使用多線程讀取多個(gè)文件處理的場(chǎng)景渐排,我用到了 CountDownLatch 。具體場(chǎng)景是下面這樣的: 我們要讀取處理 6 個(gè)文件灸蟆,這 6 個(gè)任務(wù)都是沒有執(zhí)行順序依賴的任務(wù)驯耻,但是我們需要返回給用戶的時(shí)候?qū)⑦@幾個(gè)文件的處理的結(jié)果進(jìn)行統(tǒng)計(jì)整理。 為此我們定義了一個(gè)線程池和 count 為 6 的CountDownLatch對(duì)象 炒考。使用線程池處理讀取任務(wù)可缚,每一個(gè)線程處理完之后就將 count-1,調(diào)用CountDownLatch對(duì)象的 await()方法斋枢,直到所有文件讀取完之后帘靡,才會(huì)接著執(zhí)行后面的邏輯。
shutdown() 與 shutdownNow() 對(duì)比瓤帚?
- shutdown() :關(guān)閉線程池描姚,線程池的狀態(tài)變?yōu)?SHUTDOWN涩赢。線程池不再接受新任務(wù)了,但是隊(duì)列里的任務(wù)得執(zhí)行完畢
- shutdownNow() :關(guān)閉線程池轩勘,線程的狀態(tài)變?yōu)?STOP谒主。線程池會(huì)終止當(dāng)前正在運(yùn)行的任務(wù),并停止處理排隊(duì)的任務(wù)并返回正在等待執(zhí)行的 List
isTerminated() 與 isShutdown() 對(duì)比赃阀?
- isShutDown 當(dāng)調(diào)用 shutdown() 方法后返回為 true霎肯。
- isTerminated 當(dāng)調(diào)用 shutdown() 方法后,并且所有提交的任務(wù)完成后返回為 true榛斯。
ScheduledThreadPoolExecutor 和 Timer 的比較观游?
- Timer 對(duì)系統(tǒng)時(shí)鐘的變化敏感,ScheduledThreadPoolExecutor不是驮俗。
- Timer 只有一個(gè)執(zhí)行線程懂缕,因此長(zhǎng)時(shí)間運(yùn)行的任務(wù)可以延遲其他任務(wù)。 ScheduledThreadPoolExecutor 可以配置任意數(shù)量的線程王凑。 此外搪柑,如果你想(通過提供 ThreadFactory),你可以完全控制創(chuàng)建的線程索烹。
- 在TimerTask 中拋出的運(yùn)行時(shí)異常會(huì)殺死一個(gè)線程工碾,從而導(dǎo)致 Timer 死機(jī)( 即計(jì)劃任務(wù)將不再運(yùn)行),ScheduledThreadPoolExecutor 不僅捕獲運(yùn)行時(shí)異常,還允許在需要時(shí)處理它們百姓。拋出異常的任務(wù)將被取消渊额,但其他任務(wù)將繼續(xù)正常運(yùn)行。
線程池大小確定垒拢?
- 如果我們?cè)O(shè)置的線程池?cái)?shù)量太小的話旬迹,如果同一時(shí)間有大量任務(wù)/請(qǐng)求需要處理,可能會(huì)導(dǎo)致大量的請(qǐng)求/任務(wù)在任務(wù)隊(duì)列中排隊(duì)等待執(zhí)行求类,甚至?xí)霈F(xiàn)任務(wù)隊(duì)列滿了之后任務(wù)/請(qǐng)求無法處理的情況唬血,或者大量任務(wù)堆積在任務(wù)隊(duì)列導(dǎo)致 OOM痹屹。這樣很明顯是有問題的厅须! CPU 根本沒有得到充分利用硼被。
- 如果我們?cè)O(shè)置線程數(shù)量太大,大量線程可能會(huì)同時(shí)在爭(zhēng)取 CPU 資源仓技,這樣會(huì)導(dǎo)致大量的上下文切換鸵贬,從而增加線程的執(zhí)行時(shí)間俗他,影響了整體執(zhí)行效率脖捻。
- CPU 密集型任務(wù)(N+1): 這種任務(wù)消耗的主要是 CPU 資源,可以將線程數(shù)設(shè)置為 N(CPU 核心數(shù))+1兆衅,比 CPU 核心數(shù)多出來的一個(gè)線程是為了防止線程偶發(fā)的缺頁中斷地沮,或者其它原因?qū)е碌娜蝿?wù)暫停而帶來的影響嗜浮。一旦任務(wù)暫停,CPU 就會(huì)處于空閑狀態(tài)摩疑,而在這種情況下多出來的一個(gè)線程就可以充分利用 CPU 的空閑時(shí)間危融。
- I/O 密集型任務(wù)(2N): 這種任務(wù)應(yīng)用起來,系統(tǒng)會(huì)用大部分的時(shí)間來處理 I/O 交互雷袋,而線程在處理 I/O 的時(shí)間段內(nèi)不會(huì)占用 CPU 來處理吉殃,這時(shí)就可以將 CPU 交出給其它線程使用。因此在 I/O 密集型任務(wù)的應(yīng)用中楷怒,我們可以多配置一些線程蛋勺,具體的計(jì)算方法是 2N
如何判斷是 CPU 密集任務(wù)還是 IO 密集任務(wù)?
CPU 密集型簡(jiǎn)單理解就是利用 CPU 計(jì)算能力的任務(wù)比如你在內(nèi)存中對(duì)大量數(shù)據(jù)進(jìn)行排序鸠删。但凡涉及到網(wǎng)絡(luò)讀取抱完,文件讀取這類都是 IO 密集型,這類任務(wù)的特點(diǎn)是 CPU 計(jì)算耗費(fèi)時(shí)間相比于等待 IO 操作完成的時(shí)間來說很少刃泡,大部分時(shí)間都花在了等待 IO 操作完成上
JDK 提供的并發(fā)容器總結(jié)巧娱?
- JDK 提供的這些容器大部分在 java.util.concurrent 包中。
- ConcurrentHashMap : 線程安全的 HashMap
- CopyOnWriteArrayList : 線程安全的 List烘贴,在讀多寫少的場(chǎng)合性能非常好禁添,遠(yuǎn)遠(yuǎn)好于 Vector
- ConcurrentLinkedQueue : 高效的并發(fā)隊(duì)列,使用鏈表實(shí)現(xiàn)桨踪∩系矗可以看做一個(gè)線程安全的 LinkedList,這是一個(gè)非阻塞隊(duì)列馒闷。
- BlockingQueue : 這是一個(gè)接口酪捡,JDK 內(nèi)部通過鏈表、數(shù)組等方式實(shí)現(xiàn)了這個(gè)接口纳账。表示阻塞隊(duì)列逛薇,非常適合用于作為數(shù)據(jù)共享的通道
- ConcurrentSkipListMap : 跳表的實(shí)現(xiàn)。這是一個(gè) Map疏虫,使用跳表的數(shù)據(jù)結(jié)構(gòu)進(jìn)行快速查找永罚。
ConcurrentHashMap?
- HashMap 不是線程安全的卧秘。
- 使用 Collections.synchronizedMap() 方法來包裝我們的 HashMap呢袱。但這是通過使用一個(gè)全局的鎖來同步不同線程間的并發(fā)訪問,因此會(huì)帶來不可忽視的性能問題翅敌。
- 在 ConcurrentHashMap 中羞福,無論是讀操作還是寫操作都能保證很高的性能:在進(jìn)行讀操作時(shí)(幾乎)不需要加鎖,而在寫操作時(shí)通過鎖分段技術(shù)只對(duì)所操作的段加鎖而不影響客戶端對(duì)其它段的訪問蚯涮。
- 讀操作時(shí)治专,只有是作為紅黑樹旋轉(zhuǎn)時(shí)卖陵,才會(huì)加鎖。其他時(shí)候都不用加鎖张峰,且value是volatile類型變量泪蔫,是線程可見的。
- 寫操作通過分段加鎖喘批,沒有hash沖突時(shí)撩荣,使用cas樂觀鎖,段出現(xiàn)hash沖突升級(jí)成synchronized悲觀鎖饶深。
CopyOnWriteArrayList婿滓?
- 寫時(shí)復(fù)制技術(shù)
- ReentrantReadWriteLock讀寫鎖,解決了讀讀互斥的問題粥喜,但是存在讀寫互斥
- CopyOnWriteArrayList讓所有的寫操作互斥凸主,且寫時(shí),先將原來的數(shù)組復(fù)制一份(只能有一個(gè)寫操作额湘,只能復(fù)制一份副本)卿吐,將數(shù)據(jù)添加進(jìn)去,然后用新數(shù)據(jù)替換原數(shù)組锋华,從而達(dá)到讀讀不互斥嗡官,讀寫不互斥,寫寫互斥毯焕。
- 源碼分析衍腥,讀不做操作,寫全部加上同步一把重入鎖
ConcurrentLinkedQueue纳猫?
- Java 提供的線程安全的 Queue 可以分為阻塞隊(duì)列和非阻塞隊(duì)列婆咸,其中阻塞隊(duì)列的典型例子是 BlockingQueue,非阻塞隊(duì)列的典型例子是 ConcurrentLinkedQueue芜辕。阻塞隊(duì)列可以通過加鎖來實(shí)現(xiàn)尚骄,非阻塞隊(duì)列可以通過 CAS 操作實(shí)現(xiàn)。
- ConcurrentLinkedQueue 主要使用 CAS 非阻塞算法來實(shí)現(xiàn)線程安全侵续。
BlockingQueue倔丈?
- 阻塞隊(duì)列(BlockingQueue)被廣泛使用在“生產(chǎn)者-消費(fèi)者”問題中。
- BlockingQueue 提供了可阻塞的插入和移除的方法状蜗。
- 當(dāng)隊(duì)列容器已滿需五,生產(chǎn)者線程會(huì)被阻塞,直到隊(duì)列未滿轧坎。
- 當(dāng)隊(duì)列容器為空時(shí)宏邮,消費(fèi)者線程會(huì)被阻塞,直至隊(duì)列非空時(shí)為止。
三個(gè)常見的 BlockingQueue 的實(shí)現(xiàn)類蜀铲?
-
ArrayBlockingQueue:
- ArrayBlockingQueue 是 BlockingQueue 接口的有界隊(duì)列實(shí)現(xiàn)類,底層采用數(shù)組來實(shí)現(xiàn)
- ArrayBlockingQueue 一旦創(chuàng)建属百,容量不能改變记劝。其并發(fā)控制采用可重入鎖 ReentrantLock ,不管是插入操作還是讀取操作族扰,都需要獲取到鎖才能進(jìn)行操作厌丑。當(dāng)隊(duì)列容量滿時(shí),嘗試將元素放入隊(duì)列將導(dǎo)致操作阻塞;嘗試從一個(gè)空隊(duì)列中取一個(gè)元素也會(huì)同樣阻塞
- ArrayBlockingQueue 默認(rèn)情況下不能保證線程訪問隊(duì)列的公平性渔呵,即線程訪問不準(zhǔn)尋FIFO怒竿。因此可能存在,當(dāng) ArrayBlockingQueue 可以被訪問時(shí)扩氢,長(zhǎng)時(shí)間阻塞的線程依然無法訪問到 ArrayBlockingQueue耕驰。如果保證公平性,通常會(huì)降低吞吐量录豺。如果需要獲得公平性的 ArrayBlockingQueue可以在new的時(shí)候朦肘,通過參數(shù)指出
-
LinkedBlockingQueue:
- 底層基于單向鏈表實(shí)現(xiàn)的阻塞隊(duì)列,可以當(dāng)做無界隊(duì)列也可以當(dāng)做有界隊(duì)列來使用双饥,同樣滿足 FIFO 的特性媒抠,與 ArrayBlockingQueue 相比起來具有更高的吞吐量
- 為了防止 LinkedBlockingQueue 容量迅速增,損耗大量?jī)?nèi)存咏花。通常在創(chuàng)建 LinkedBlockingQueue 對(duì)象時(shí)趴生,會(huì)指定其大小,如果未指定昏翰,容量等于 Integer.MAX_VALUE
-
PriorityBlockingQueue:
- 是一個(gè)支持優(yōu)先級(jí)的無界阻塞隊(duì)列苍匆。默認(rèn)情況下元素采用自然順序進(jìn)行排序,也可以通過自定義類實(shí)現(xiàn) compareTo() 方法來指定元素排序規(guī)則棚菊,或者初始化時(shí)通過構(gòu)造器參數(shù) Comparator 來指定排序規(guī)則
- PriorityBlockingQueue 并發(fā)控制采用的是可重入鎖 ReentrantLock锉桑,隊(duì)列為無界隊(duì)列,后面插入元素的時(shí)候窍株,如果空間不夠的話會(huì)自動(dòng)擴(kuò)容
- 簡(jiǎn)單地說民轴,它就是 PriorityQueue 的線程安全版本。不可以插入 null 值球订,同時(shí)后裸,插入隊(duì)列的對(duì)象必須是可比較大小的(comparable),否則報(bào) ClassCastException 異常冒滩。它的插入操作 put 方法不會(huì) block微驶,因?yàn)樗菬o界隊(duì)列(take 方法在隊(duì)列為空的時(shí)候會(huì)阻塞)
ConcurrentSkipListMap?
- 跳表又叫跳躍表,是一種可以進(jìn)行二分查找的有序鏈表因苹。跳表是在原來的有序鏈表上添加了多層有序鏈表實(shí)現(xiàn)的苟耻。如果嚴(yán)格按照二分查找構(gòu)建跳表,每次添加數(shù)據(jù)后都必須重建i>1層的所有鏈表扶檐,會(huì)讓插入和刪除的時(shí)間復(fù)雜度退化成O(n)凶杖,所以一般采用插入數(shù)據(jù)后,隨機(jī)數(shù)據(jù)所處在層數(shù)款筑,即第1到隨機(jī)層數(shù)都有這個(gè)值(只需維護(hù)每層這個(gè)值的索引關(guān)系)智蝠,從而達(dá)到簡(jiǎn)化跳表插入和刪除。
- 由于滿足二分查找奈梳,所以跳表的查找復(fù)雜度是O(logn)
- 跳表是一種以時(shí)間換空間的算法
線程池最佳實(shí)踐
為什么要使用線程池杈湾?
- 線程池、數(shù)據(jù)庫連接池攘须、Http 連接池等等漆撞,主要是為了減少每次獲取資源的消耗,提高對(duì)資源的利用率于宙。
- 線程池提供了對(duì)線程使用的限制和管理叫挟。 同時(shí)每個(gè)線程池還維護(hù)一些基本統(tǒng)計(jì)信息,例如已完成任務(wù)的數(shù)量
- 使用線程池的好處:
- 降低資源消耗限煞。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗抹恳。
- 提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí)署驻,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行奋献。
- 提高線程的可管理性。線程是稀缺資源旺上,如果無限制的創(chuàng)建瓶蚂,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性宣吱,使用線程池可以進(jìn)行統(tǒng)一的分配窃这,調(diào)優(yōu)和監(jiān)控。
線程池在實(shí)際項(xiàng)目的使用場(chǎng)景征候?
線程池一般用于執(zhí)行多個(gè)不相關(guān)聯(lián)的耗時(shí)任務(wù)杭攻,沒有多線程的情況下,任務(wù)順序執(zhí)行疤坝,使用了線程池的話可讓多個(gè)不相關(guān)聯(lián)的任務(wù)同時(shí)執(zhí)行兆解。例如:批量發(fā)送郵件。
如何使用線程池跑揉?
一般是通過 ThreadPoolExecutor 的構(gòu)造函數(shù)來創(chuàng)建線程池锅睛,然后提交任務(wù)給線程池執(zhí)行就可以了埠巨。創(chuàng)建線程池時(shí)需要注意核心線程數(shù),最大線程數(shù)现拒,隊(duì)列長(zhǎng)度辣垒,拒絕策略。
線程池最佳實(shí)踐印蔬?
- 使用 ThreadPoolExecutor 的構(gòu)造函數(shù)聲明線程池勋桶。
- 避免使用Executors 類:
- Executors 類的FixedThreadPool 和 SingleThreadExecutor : 允許請(qǐng)求的隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE,可能堆積大量的請(qǐng)求,從而導(dǎo)致 OOM扛点。
- CachedThreadPool 和 ScheduledThreadPool : 允許創(chuàng)建的線程數(shù)量為 Integer.MAX_VALUE 哥遮,可能會(huì)創(chuàng)建大量線程岂丘,從而導(dǎo)致 OOM陵究。
- 實(shí)際使用中需要根據(jù)自己機(jī)器的性能、業(yè)務(wù)場(chǎng)景來手動(dòng)配置線程池的參數(shù)比如核心線程數(shù)奥帘、使用的任務(wù)隊(duì)列铜邮、飽和策略等等。
- 我們應(yīng)該顯示地給我們的線程池命名寨蹋,這樣有助于我們定位問題松蒜。
- 總結(jié):使用有界隊(duì)列,控制線程創(chuàng)建數(shù)量已旧。
監(jiān)測(cè)線程池運(yùn)行狀態(tài)秸苗?
可以利用 ThreadPoolExecutor 的相關(guān) API做一個(gè)的監(jiān)控。打印線程池當(dāng)前的線程數(shù)和活躍線程數(shù)运褪、已經(jīng)執(zhí)行完成的任務(wù)數(shù)惊楼、正在排隊(duì)中的任務(wù)數(shù)等等。
建議不同類別的業(yè)務(wù)用不同的線程池秸讹?
一般建議是不同的業(yè)務(wù)使用不同的線程池檀咙,配置線程池的時(shí)候根據(jù)當(dāng)前業(yè)務(wù)的情況對(duì)當(dāng)前線程池進(jìn)行配置,因?yàn)椴煌臉I(yè)務(wù)的并發(fā)以及對(duì)資源的使用情況都不同璃诀,重心優(yōu)化系統(tǒng)性能瓶頸相關(guān)的業(yè)務(wù)弧可。
別忘記給線程池命名?
- 初始化線程池的時(shí)候需要顯示命名(設(shè)置線程池名稱前綴)劣欢,有利于定位問題
- 自己實(shí)現(xiàn) ThreadFactor棕诵,對(duì)線程命名
正確配置線程池參數(shù)?
CPU 密集型任務(wù)(N+1):這種任務(wù)消耗的主要是 CPU 資源凿将,可以將線程數(shù)設(shè)置為 N(CPU 核心數(shù))+1年鸳,比 CPU 核心數(shù)多出來的一個(gè)線程是為了防止線程偶發(fā)的缺頁中斷,或者其它原因?qū)е碌娜蝿?wù)暫停而帶來的影響丸相。一旦任務(wù)暫停搔确,CPU 就會(huì)處于空閑狀態(tài),而在這種情況下多出來的一個(gè)線程就可以充分利用 CPU 的空閑時(shí)間。 I/O 密集型任務(wù)(2N):這種任務(wù)應(yīng)用起來膳算,系統(tǒng)會(huì)用大部分的時(shí)間來處理 I/O 交互座硕,而線程在處理 I/O 的時(shí)間段內(nèi)不會(huì)占用 CPU 來處理,這時(shí)就可以將 CPU 交出給其它線程使用涕蜂。因此在 I/O 密集型任務(wù)的應(yīng)用中华匾,我們可以多配置一些線程,具體的計(jì)算方法是 2N机隙。
如何判斷是 CPU 密集任務(wù)還是 IO 密集任務(wù)蜘拉?
CPU 密集型簡(jiǎn)單理解就是利用 CPU 計(jì)算能力的任務(wù)比如你在內(nèi)存中對(duì)大量數(shù)據(jù)進(jìn)行排序。但凡涉及到網(wǎng)絡(luò)讀取有鹿,文件讀取這類都是 IO 密集型旭旭,這類任務(wù)的特點(diǎn)是 CPU 計(jì)算耗費(fèi)時(shí)間相比于等待 IO 操作完成的時(shí)間來說很少,大部分時(shí)間都花在了等待 IO 操作完成上葱跋。
AQS 簡(jiǎn)單介紹持寄?
- AQS 的全稱為 AbstractQueuedSynchronizer ,翻譯過來的意思就是抽象隊(duì)列同步器娱俺。這個(gè)類在 java.util.concurrent.locks 包下面稍味。
- AQS 就是一個(gè)抽象類,主要用來構(gòu)建鎖和同步器荠卷。
- AQS 為構(gòu)建鎖和同步器提供了一些通用功能的是實(shí)現(xiàn)模庐,因此,使用 AQS 能簡(jiǎn)單且高效地構(gòu)造出應(yīng)用廣泛的大量的同步器油宜,比如我們提到的 ReentrantLock掂碱,Semaphore,其他的諸如 ReentrantReadWriteLock验庙,SynchronousQueue顶吮,F(xiàn)utureTask(jdk1.7) 等等皆是基于 AQS 的。
AQS 原理概覽粪薛?
- AQS 核心思想是悴了,如果被請(qǐng)求的共享資源空閑,則將當(dāng)前請(qǐng)求資源的線程設(shè)置為有效的工作線程违寿,并且將共享資源設(shè)置為鎖定狀態(tài)湃交。如果被請(qǐng)求的共享資源被占用,那么就需要一套線程阻塞等待以及被喚醒時(shí)鎖分配的機(jī)制藤巢,這個(gè)機(jī)制 AQS 是用 CLH 隊(duì)列鎖實(shí)現(xiàn)的搞莺,即將暫時(shí)獲取不到鎖的線程加入到隊(duì)列中。
- AQS 使用一個(gè) int 成員變量來表示同步狀態(tài)掂咒,通過內(nèi)置的 FIFO 隊(duì)列來完成獲取資源線程的排隊(duì)工作才沧。AQS 使用 CAS 對(duì)該同步狀態(tài)進(jìn)行原子操作實(shí)現(xiàn)對(duì)其值的修改迈喉。
AQS 對(duì)資源的共享方式?
-
Exclusive(獨(dú)占):只有一個(gè)線程能執(zhí)行温圆,如 ReentrantLock挨摸。又可分為公平鎖和非公平鎖
- 公平鎖 :按照線程在隊(duì)列中的排隊(duì)順序,先到者先拿到鎖
- 非公平鎖 :當(dāng)線程要獲取鎖時(shí)岁歉,先通過兩次 CAS 操作去搶鎖得运,如果沒搶到,當(dāng)前線程再加入到隊(duì)列中等待喚醒
- 公平鎖和非公平鎖只有兩處不同:
- 非公平鎖在調(diào)用 lock 后锅移,首先就會(huì)調(diào)用 CAS 進(jìn)行一次搶鎖熔掺,如果這個(gè)時(shí)候恰巧鎖沒有被占用,那么直接就獲取到鎖返回了
- 非公平鎖在 CAS 失敗后非剃,和公平鎖一樣都會(huì)進(jìn)入到 tryAcquire 方法置逻,在 tryAcquire 方法中,如果發(fā)現(xiàn)鎖這個(gè)時(shí)候被釋放了(state == 0)努潘,非公平鎖會(huì)直接 CAS 搶鎖诽偷,但是公平鎖會(huì)判斷等待隊(duì)列是否有線程處于等待狀態(tài)坤学,如果有則不去搶鎖疯坤,乖乖排到后面
- 公平鎖和非公平鎖就這兩點(diǎn)區(qū)別,如果這兩次 CAS 都不成功深浮,那么后面非公平鎖和公平鎖是一樣的压怠,都要進(jìn)入到阻塞隊(duì)列等待喚醒
- 相對(duì)來說,非公平鎖會(huì)有更好的性能飞苇,因?yàn)樗耐掏铝勘容^大菌瘫。當(dāng)然,非公平鎖讓獲取鎖的時(shí)間變得更加不確定布卡,可能會(huì)導(dǎo)致在阻塞隊(duì)列中的線程長(zhǎng)期處于饑餓狀態(tài)
-
Share(共享):
多個(gè)線程可同時(shí)執(zhí)行雨让,如Semaphore、CountDownLatCh忿等、 CyclicBarrier栖忠、ReadWriteLock
Semaphore(信號(hào)量)?
- synchronized 和 ReentrantLock 都是一次只允許一個(gè)線程訪問某個(gè)資源,Semaphore(信號(hào)量)可以指定多個(gè)線程同時(shí)訪問某個(gè)資源贸街。
- Semaphore 有兩種模式庵寞,公平模式和非公平模式:
- 公平模式: 調(diào)用 acquire() 方法的順序就是獲取許可證的順序,遵循 FIFO
- 非公平模式: 搶占式的
CountDownLatch(倒計(jì)時(shí)器)薛匪?
CountDownLatch 允許 count 個(gè)線程阻塞在一個(gè)地方捐川,直至所有線程的任務(wù)都執(zhí)行完畢,或者允許count線程同時(shí)運(yùn)行逸尖。
CountDownLatch 的兩種典型用法古沥?
- 某一線程在開始運(yùn)行前等待 n 個(gè)線程執(zhí)行完畢
- 例子:
- 主線程提交線程后瘸右,立即CountDownLatch.await()
- 多個(gè)線程執(zhí)行,每執(zhí)行完成一個(gè)后岩齿,countdownlatch.countDown()
- 當(dāng)count個(gè)線程執(zhí)行完成后尊浓,主線程會(huì)被喚醒,然后繼續(xù)執(zhí)行
- 例子:
- 實(shí)現(xiàn)多個(gè)線程開始執(zhí)行任務(wù)的最大并行性
- 例子:
- 需要一個(gè)startCountDownLatch和一個(gè)endCountDownLatch纯衍,startCountDownLatch用于控制所有線程同時(shí)執(zhí)行栋齿,endCountDownLatch用于控制允許多少個(gè)線程同時(shí)執(zhí)行
- startCountDownLatch的count為1,每加入一個(gè)線程后襟诸,都startCountDownLatch.await()瓦堵,同時(shí)endCountDownLatch.countDown()
- 當(dāng)所有線程加入后,在主線程上startCountDownLatch.countDown()
CountDownLatch 的不足?
CountDownLatch 是一次性的歌亲,計(jì)數(shù)器的值只能在構(gòu)造方法中初始化一次菇用,之后沒有任何機(jī)制再次對(duì)其設(shè)置值,當(dāng) CountDownLatch 使用完畢后陷揪,它不能再次被使用
CyclicBarrier(循環(huán)柵欄)惋鸥?
- CyclicBarrier 和 CountDownLatch 非常類似,它也可以實(shí)現(xiàn)線程間的技術(shù)等待悍缠,但是它的功能比 CountDownLatch 更加復(fù)雜和強(qiáng)大卦绣。主要應(yīng)用場(chǎng)景和 CountDownLatch 類似。
- CountDownLatch 的實(shí)現(xiàn)是基于 AQS 的飞蚓,而 CycliBarrier 是基于 ReentrantLock(ReentrantLock 也屬于 AQS 同步器)和 Condition 的滤港。
- 讓一組線程到達(dá)一個(gè)屏障(也可以叫同步點(diǎn))時(shí)被阻塞,直到最后一個(gè)線程到達(dá)屏障時(shí)趴拧,屏障才會(huì)開門溅漾,所有被屏障攔截的線程才會(huì)繼續(xù)干活
CyclicBarrier 的應(yīng)用場(chǎng)景?
CyclicBarrier 可以用于多線程計(jì)算數(shù)據(jù)著榴,最后合并計(jì)算結(jié)果的應(yīng)用場(chǎng)景添履。比如我們用一個(gè) Excel 保存了用戶所有銀行流水,每個(gè) Sheet 保存一個(gè)帳戶近一年的每筆銀行流水脑又,現(xiàn)在需要統(tǒng)計(jì)用戶的日均銀行流水暮胧,先用多線程處理每個(gè) sheet 里的銀行流水,都執(zhí)行完之后挂谍,得到每個(gè) sheet 的日均銀行流水叔壤,最后,再用 barrierAction 用這些線程的計(jì)算結(jié)果口叙,計(jì)算出整個(gè) Excel 的日均銀行流水炼绘。
CyclicBarrier 和 CountDownLatch 的區(qū)別?
- CountDownLatch 是計(jì)數(shù)器,只能使用一次妄田,而 CyclicBarrier 的計(jì)數(shù)器提供 reset 功能俺亮,可以多次使用驮捍。
- CountDownLatch 是計(jì)數(shù)器,線程完成一個(gè)記錄一個(gè)脚曾,只不過計(jì)數(shù)不是遞增而是遞減东且,而 CyclicBarrier 更像是一個(gè)閥門,需要所有線程都到達(dá)本讥,閥門才能打開珊泳,然后繼續(xù)執(zhí)行。
ReentrantLock 與 Synchronized 對(duì)比拷沸?
鎖的實(shí)現(xiàn)機(jī)制 | AQS | 監(jiān)視器模式 |
---|---|---|
靈活性 | 支持響應(yīng)中斷色查、超時(shí)、嘗試獲取鎖 | 不靈活 |
釋放鎖的形式 | 必須顯示的調(diào)用unlock釋放鎖 | 自動(dòng)釋放監(jiān)視器 |
鎖類型 | 公平鎖撞芍、非公平鎖 | 非公平鎖 |
條件隊(duì)列 | 可以關(guān)聯(lián)多個(gè)條件隊(duì)列 | 關(guān)聯(lián)一個(gè)條件隊(duì)列 |
可重入性 | 可重入 | 可重入 |