第三章
3.1 鎖概述
- 鎖的持有線程在其獲得鎖之后和釋放鎖之前這段時(shí)間內(nèi)所執(zhí)行的代碼被稱為臨界區(qū)。
- Java平臺(tái)中的鎖包括內(nèi)部鎖和顯示鎖。內(nèi)部鎖是通過(guò)synchronized關(guān)鍵字實(shí)現(xiàn)的;顯示鎖是通過(guò)Lock接口的實(shí)現(xiàn)類實(shí)現(xiàn)的。
- 可見(jiàn)性的保障是通過(guò)寫(xiě)線程沖刷處理器緩存和讀線程刷新處理器緩存這兩個(gè)動(dòng)作實(shí)現(xiàn)的。鎖的獲得隱含著刷新處理器緩存這個(gè)動(dòng)作,這使得讀線程在執(zhí)行臨界區(qū)代碼前(獲得鎖之后)可以將寫(xiě)線程對(duì)共享變量所做的更新同步到該線程執(zhí)行處理器的高速緩存中;而鎖的釋放隱含著沖刷處理器緩存這個(gè)動(dòng)作。
- 鎖對(duì)可見(jiàn)性、原子性和有序性的保障是有條件的:
- 這些線程在訪問(wèn)同一組共享數(shù)據(jù)的時(shí)候必須使用同一個(gè)鎖。
- 這些線程中的任意一個(gè)線程,即使其僅僅是讀取這組共享數(shù)據(jù)而沒(méi)有對(duì)其進(jìn)行更新的話,也需要在讀取時(shí)持有相應(yīng)的鎖。
- 一個(gè)線程在其持有一個(gè)鎖的時(shí)候能否再次(或者多次)申請(qǐng)?jiān)撴i。如果一個(gè)線程持有一個(gè)鎖的時(shí)候還能夠繼續(xù)成功申請(qǐng)?jiān)撴i,那么我們就稱該鎖是可重入的。
- Java平臺(tái)中鎖的調(diào)度策略也包括公平策略和非公平策略,相應(yīng)的鎖就被稱為公平鎖和非公平鎖。
- 一個(gè)鎖實(shí)例所保護(hù)的共享數(shù)據(jù)的數(shù)量大小就被稱為該鎖的粒度。
3.2 內(nèi)部鎖:synchronized關(guān)鍵字

image.png
- synchronized 關(guān)鍵字修飾的代碼塊被稱為同步塊。
- 作為鎖句柄的變量通常采用final修飾,鎖不能改變。通常會(huì)使用private修飾作為鎖句柄的變量。
- 同步靜態(tài)方法相當(dāng)于以當(dāng)前類對(duì)象為引導(dǎo)鎖的同步塊。
- 公平鎖保障鎖調(diào)度的公平性往往是以增加了線程的暫停和喚醒的可能性,即增加了上下文切換為代價(jià)的。因此,公平鎖適合于鎖被持有的時(shí)間相對(duì)長(zhǎng)或者線程申請(qǐng)鎖的平均間隔時(shí)間相對(duì)長(zhǎng)的情形。
- 使用顯示鎖的時(shí)候必須注意將鎖的釋放操作放在finally塊中。
- Lock接口定義了一個(gè)tryLock。該方法的作用是嘗試申請(qǐng)相應(yīng)Lock實(shí)例鎖標(biāo)識(shí)的鎖。避免出現(xiàn)持有線程一直不釋放鎖這個(gè)鎖(例如代碼錯(cuò)誤),同步在該鎖之上的所有線程就會(huì)一直被暫停而使其任務(wù)無(wú)法進(jìn)展。

image.png
- 改進(jìn)型鎖:讀寫(xiě)鎖
- 讀寫(xiě)鎖允許多個(gè)線程可以同時(shí)讀?。ㄖ蛔x)共享變量,但是一次只允許一個(gè)線程對(duì)共享變量進(jìn)行更新(包括讀取后再更新)。
- 任何一個(gè)線程持有一個(gè)讀鎖的時(shí)候,其他任何線程都無(wú)法獲得相應(yīng)鎖的寫(xiě)鎖。
- 讀寫(xiě)鎖適用于
- 只讀操作比寫(xiě)(更新)操作要頻繁得多。
- 讀線程持有鎖的時(shí)間比較長(zhǎng)。
3.3 輕量級(jí)同步機(jī)制:volatile關(guān)鍵字
- volatile關(guān)鍵字用于修飾共享可變變量,即沒(méi)有使用final關(guān)鍵字修飾的實(shí)例變量或靜態(tài)變量。
- volatile變量不會(huì)被編譯器分配到寄存器進(jìn)行存儲(chǔ),對(duì)volatile變量的讀寫(xiě)操作都是內(nèi)存訪問(wèn)(訪問(wèn)高速緩存相當(dāng)于主內(nèi)存)操作。
- volatile關(guān)鍵字的作用包括:保障可見(jiàn)性、保障有序性和保障long/double型變量讀寫(xiě)操作的原子性。
- 一般而言,對(duì)volatile變量的賦值操作,其右邊表達(dá)式中只要涉及共享變量(包括被賦值的volatiel變量本身),那么這個(gè)賦值操作就不是原子操作。
- 如果被修飾的變量是個(gè)數(shù)組,那么volatile關(guān)鍵字只能夠?qū)?shù)組引用本身的操作(讀取數(shù)組引用和更新數(shù)組引用)起作用,而無(wú)法對(duì)數(shù)組元素的操作(讀取、更新數(shù)組元素)起作用。
- volatile應(yīng)用場(chǎng)景
- 使用volatile變量作物狀態(tài)標(biāo)志。在該場(chǎng)景中,應(yīng)用程序的某個(gè)狀態(tài)由一個(gè)線程設(shè)置,其他線程會(huì)讀取該狀態(tài)并以該狀態(tài)作為其計(jì)算的依據(jù)(或者僅僅讀取并輸出這個(gè)狀態(tài)值)。此時(shí)使用volatile變量作為同步機(jī)制的好處是一個(gè)線程能夠“通知”另外一個(gè)線程某種事件(例如,網(wǎng)絡(luò)連接斷連之后重新連上)的發(fā)生,而這些線程又無(wú)須因此而使用鎖。從而避免了鎖的開(kāi)銷以及相關(guān)問(wèn)題。
- 使用volatile保障可見(jiàn)性。其中一個(gè)線程更新了該變量之后,其他線程無(wú)須加鎖的情況下也能夠看到更新。
- 使用volatile變量替代鎖。利用volatile變量寫(xiě)操作具有的原子性,可以把一組可變狀態(tài)變量封裝成一個(gè)對(duì)象,那么對(duì)這些狀態(tài)變量的更新操作就可以通過(guò)創(chuàng)建一個(gè)新的對(duì)象并將該對(duì)象引用賦值給相應(yīng)的引用型變量來(lái)實(shí)現(xiàn)。volatile適合于多個(gè)線程共享一個(gè)狀態(tài)變量(對(duì)象),而鎖更實(shí)用用于多個(gè)線程共享一組狀態(tài)變量。
-
使用volatile實(shí)現(xiàn)簡(jiǎn)易版讀寫(xiě)鎖。
image.png
3.4 對(duì)象的發(fā)布與逸出
-
對(duì)象發(fā)布形式
- 將對(duì)象引用存儲(chǔ)到public變量中。
- 在非private方法(包括public、protected、package方法)中返回一個(gè)對(duì)象。
- 創(chuàng)建內(nèi)部類,使得當(dāng)前對(duì)象(this)能夠被這個(gè)內(nèi)部類使用。
- 通過(guò)方法調(diào)用將對(duì)象傳遞給外部方法。
-
對(duì)象初始化安全:final與static
2.1 static
- java中類的初始化實(shí)際上也采取了延遲加載的技術(shù),即一個(gè)類被java虛擬機(jī)加載之后,該類的所有靜態(tài)變量的值仍然是其默認(rèn)值(引用型變量的默認(rèn)值為null,boolean變量的默認(rèn)值為false),直到有個(gè)線程初次訪問(wèn)了該類的任意一個(gè)靜態(tài)變量才使這個(gè)類被初始化——類的靜態(tài)初始?jí)K("static{}")被執(zhí)行,類的所有靜態(tài)變量被賦予初始化。
- static關(guān)鍵字僅僅保障讀線程能夠讀取到相應(yīng)字段的初始值,而不是相對(duì)新值。
2.2 final
- 當(dāng)一個(gè)對(duì)象被發(fā)布到其他線程的時(shí)候,該對(duì)象的所有final字段(實(shí)例變量)都是初始化完畢的,即其他線程讀取這些字段的時(shí)候所讀取到的值都是相應(yīng)字段的初始值(而不是默認(rèn)值)。而非final字段沒(méi)有這種保障,即這些線程讀取該對(duì)象的非final字段時(shí)所讀取到的值可能仍然是相應(yīng)字段的默認(rèn)值。
3.5 對(duì)象逸出
-
容易導(dǎo)致對(duì)象逸出的幾種形式
- 在構(gòu)造器中將this賦值給一個(gè)共享變量。
- 在構(gòu)造器中將this作為方法參數(shù)傳遞給其他方法。
- 在構(gòu)造器中啟動(dòng)基于匿名類的線程。(其他線程可能看到的是一個(gè)未初始化完成的對(duì)象)
-
一般地,如果一個(gè)類需要?jiǎng)?chuàng)建自己的工作者線程,那么我們可以為該類定義一個(gè)init方法(可以是private的),相應(yīng)的工作者線程可以在該方法或者該類的構(gòu)造器創(chuàng)建,但是線程的啟動(dòng)則是在init方法中執(zhí)行的。
image.png -
開(kāi)銷大小
- 使用static關(guān)鍵字修飾引用該對(duì)象的變量。
- 使用final關(guān)鍵字修飾引用該對(duì)象的變量。
- 使用volatile關(guān)鍵字修飾引用該對(duì)象的變量。
- 使用AtomicReference來(lái)引用該對(duì)象。
- 對(duì)訪問(wèn)該對(duì)象的代碼進(jìn)行加鎖。

