第三章
3.1 鎖概述
- 鎖的持有線程在其獲得鎖之后和釋放鎖之前這段時間內所執(zhí)行的代碼被稱為臨界區(qū)戈鲁。
- Java平臺中的鎖包括內部鎖和顯示鎖。內部鎖是通過synchronized關鍵字實現(xiàn)的嘹叫;顯示鎖是通過Lock接口的實現(xiàn)類實現(xiàn)的婆殿。
- 可見性的保障是通過寫線程沖刷處理器緩存和讀線程刷新處理器緩存這兩個動作實現(xiàn)的。鎖的獲得隱含著刷新處理器緩存這個動作待笑,這使得讀線程在執(zhí)行臨界區(qū)代碼前(獲得鎖之后)可以將寫線程對共享變量所做的更新同步到該線程執(zhí)行處理器的高速緩存中鸣皂;而鎖的釋放隱含著沖刷處理器緩存這個動作。
- 鎖對可見性暮蹂、原子性和有序性的保障是有條件的:
- 這些線程在訪問同一組共享數(shù)據的時候必須使用同一個鎖寞缝。
- 這些線程中的任意一個線程,即使其僅僅是讀取這組共享數(shù)據而沒有對其進行更新的話仰泻,也需要在讀取時持有相應的鎖荆陆。
- 一個線程在其持有一個鎖的時候能否再次(或者多次)申請該鎖。如果一個線程持有一個鎖的時候還能夠繼續(xù)成功申請該鎖集侯,那么我們就稱該鎖是可重入的被啼。
- Java平臺中鎖的調度策略也包括公平策略和非公平策略,相應的鎖就被稱為公平鎖和非公平鎖棠枉。
- 一個鎖實例所保護的共享數(shù)據的數(shù)量大小就被稱為該鎖的粒度浓体。
3.2 內部鎖:synchronized關鍵字
image.png
- synchronized 關鍵字修飾的代碼塊被稱為同步塊。
- 作為鎖句柄的變量通常采用final修飾辈讶,鎖不能改變命浴。通常會使用private修飾作為鎖句柄的變量。
- 同步靜態(tài)方法相當于以當前類對象為引導鎖的同步塊贱除。
- 公平鎖保障鎖調度的公平性往往是以增加了線程的暫停和喚醒的可能性生闲,即增加了上下文切換為代價的。因此月幌,公平鎖適合于鎖被持有的時間相對長或者線程申請鎖的平均間隔時間相對長的情形碍讯。
- 使用顯示鎖的時候必須注意將鎖的釋放操作放在finally塊中。
- Lock接口定義了一個tryLock扯躺。該方法的作用是嘗試申請相應Lock實例鎖標識的鎖捉兴。避免出現(xiàn)持有線程一直不釋放鎖這個鎖(例如代碼錯誤),同步在該鎖之上的所有線程就會一直被暫停而使其任務無法進展录语。
image.png
- 改進型鎖:讀寫鎖
- 讀寫鎖允許多個線程可以同時讀戎崾酢(只讀)共享變量,但是一次只允許一個線程對共享變量進行更新(包括讀取后再更新)钦无。
- 任何一個線程持有一個讀鎖的時候逗栽,其他任何線程都無法獲得相應鎖的寫鎖。
- 讀寫鎖適用于
- 只讀操作比寫(更新)操作要頻繁得多失暂。
- 讀線程持有鎖的時間比較長彼宠。
3.3 輕量級同步機制:volatile關鍵字
- volatile關鍵字用于修飾共享可變變量鳄虱,即沒有使用final關鍵字修飾的實例變量或靜態(tài)變量。
- volatile變量不會被編譯器分配到寄存器進行存儲凭峡,對volatile變量的讀寫操作都是內存訪問(訪問高速緩存相當于主內存)操作拙已。
- volatile關鍵字的作用包括:保障可見性、保障有序性和保障long/double型變量讀寫操作的原子性摧冀。
- 一般而言倍踪,對volatile變量的賦值操作,其右邊表達式中只要涉及共享變量(包括被賦值的volatiel變量本身)索昂,那么這個賦值操作就不是原子操作建车。
- 如果被修飾的變量是個數(shù)組,那么volatile關鍵字只能夠對數(shù)組引用本身的操作(讀取數(shù)組引用和更新數(shù)組引用)起作用椒惨,而無法對數(shù)組元素的操作(讀取缤至、更新數(shù)組元素)起作用。
- volatile應用場景
- 使用volatile變量作物狀態(tài)標志康谆。在該場景中领斥,應用程序的某個狀態(tài)由一個線程設置,其他線程會讀取該狀態(tài)并以該狀態(tài)作為其計算的依據(或者僅僅讀取并輸出這個狀態(tài)值)沃暗。此時使用volatile變量作為同步機制的好處是一個線程能夠“通知”另外一個線程某種事件(例如月洛,網絡連接斷連之后重新連上)的發(fā)生,而這些線程又無須因此而使用鎖孽锥。從而避免了鎖的開銷以及相關問題嚼黔。
- 使用volatile保障可見性。其中一個線程更新了該變量之后忱叭,其他線程無須加鎖的情況下也能夠看到更新隔崎。
- 使用volatile變量替代鎖今艺。利用volatile變量寫操作具有的原子性韵丑,可以把一組可變狀態(tài)變量封裝成一個對象,那么對這些狀態(tài)變量的更新操作就可以通過創(chuàng)建一個新的對象并將該對象引用賦值給相應的引用型變量來實現(xiàn)虚缎。volatile適合于多個線程共享一個狀態(tài)變量(對象)撵彻,而鎖更實用用于多個線程共享一組狀態(tài)變量。
-
使用volatile實現(xiàn)簡易版讀寫鎖实牡。
image.png
3.4 對象的發(fā)布與逸出
-
對象發(fā)布形式
- 將對象引用存儲到public變量中陌僵。
- 在非private方法(包括public、protected创坞、package方法)中返回一個對象碗短。
- 創(chuàng)建內部類,使得當前對象(this)能夠被這個內部類使用题涨。
- 通過方法調用將對象傳遞給外部方法偎谁。
-
對象初始化安全:final與static
2.1 static
- java中類的初始化實際上也采取了延遲加載的技術总滩,即一個類被java虛擬機加載之后,該類的所有靜態(tài)變量的值仍然是其默認值(引用型變量的默認值為null,boolean變量的默認值為false),直到有個線程初次訪問了該類的任意一個靜態(tài)變量才使這個類被初始化——類的靜態(tài)初始塊("static{}")被執(zhí)行巡雨,類的所有靜態(tài)變量被賦予初始化闰渔。
- static關鍵字僅僅保障讀線程能夠讀取到相應字段的初始值,而不是相對新值铐望。
2.2 final
- 當一個對象被發(fā)布到其他線程的時候冈涧,該對象的所有final字段(實例變量)都是初始化完畢的,即其他線程讀取這些字段的時候所讀取到的值都是相應字段的初始值(而不是默認值)正蛙。而非final字段沒有這種保障督弓,即這些線程讀取該對象的非final字段時所讀取到的值可能仍然是相應字段的默認值。
3.5 對象逸出
-
容易導致對象逸出的幾種形式
- 在構造器中將this賦值給一個共享變量跟畅。
- 在構造器中將this作為方法參數(shù)傳遞給其他方法咽筋。
- 在構造器中啟動基于匿名類的線程。(其他線程可能看到的是一個未初始化完成的對象)
-
一般地徊件,如果一個類需要創(chuàng)建自己的工作者線程奸攻,那么我們可以為該類定義一個init方法(可以是private的),相應的工作者線程可以在該方法或者該類的構造器創(chuàng)建虱痕,但是線程的啟動則是在init方法中執(zhí)行的睹耐。
image.png -
開銷大小
- 使用static關鍵字修飾引用該對象的變量。
- 使用final關鍵字修飾引用該對象的變量部翘。
- 使用volatile關鍵字修飾引用該對象的變量硝训。
- 使用AtomicReference來引用該對象。
- 對訪問該對象的代碼進行加鎖新思。