1. 程式人生 > >java並發編程實戰筆記

java並發編程實戰筆記

home ret 睡眠 帶來 hand 局限 線程編程 同步 single

1、復合操作

若一個類裏有多個屬性狀態,對每個屬性使用atomic類修飾,並且一個屬性更新,要在同一原子操作內更新其他所有屬性,這樣才是線程安全類。需要整體類的狀態操作是原子的。

要保持狀態的一致性,就需要在單個原子操作中更新所有相關的狀態變量。

判斷同步代碼塊的合理大小,要權衡安全性、簡單性和性能。

當執行時間較長的計算或可能無法快速完成的操作(如網絡IO、控制臺IO)一定不要持有鎖。

2、對象的共享

1)可見性

為了確保所有線程都能看到共享變量的最新值,所有執行讀操作或寫操作的線程都必須在同一個鎖上同步。

volatile,變量被volatile聲明後不會被重排,不會被緩存,都是返回最新值。

使用場景:僅當volatile變量能簡化代碼實現以及對同步策略的驗證時,才使用。

如:確保變量自身狀態是可見的;

確保他們所引用的對象的狀態是可見性的;

標識一些重要的程序生命周期事件:如初始化或關閉。

技術分享

Volatile通常被用做某個操作完成、發生中斷或狀態的標識。

使用volatile保證線程安全的應滿足的條件:

只有一個線程更新變量的值

該變量不與其他狀態變量一起納入不變性條件中

訪問該變量是不需要加鎖

2)對象發布

使對象能夠在當前作用域以外的代碼中使用。

技術分享

任何類和線程都能看到knownSecrets,即發布。

技術分享

私有變量statesgetStates()逸出(被發布到當前作用域外)。

使用封裝的意義:封裝能夠使得對程序的正確性進行分析變成可能(逸出變數太大),並使無意中破壞設計約束條件變得更難。

不可變對象的條件:

對象創建後其狀態就不可修改;

對象的所有域都是final類型;

對象是正確創建的:對象創建期間,this引用沒有逸出。

技術分享

Object的構造函數會在子類構造函數運行之前先將默認值寫入所有的域。因此,多線程時某個域的值可能為失效值。

安全發布一個正確構造對象的方式:

在靜態初始化函數中初始化一個對象的引用。

將對象的引用保存到volatile類型的域或者AtomicReferance對象中。

將對象的引用保存到某個正確構造對象的final類型域中。

將對象的引用保存到一個由鎖保護的域中。

3)線程安全的容器:

MapHashtablesynchronizedMapconcurrentMap

CollectionVectorCopyOnWriteArrayListCopyOnWriteArraySetSynchronizedListSynchronizedSet

隊列:BlockingQueueConcurrenLinkedQueue

對象的發布取決於它的可變性:

不可變對象可以通過任何機制來發布;

事實不可變對象必須通過安全的方式來發布;

可變對象必須通過安全的方式來發布,並且是線程安全的或者是由鎖保護起來的。

使用共享對象的策略:

線程封閉:線程封閉的對象只能一個線程擁有,對象被封閉在該線程中,並且只能由這個線程修改。

只讀共享:沒有額外同步的情況下,只讀共享對象任何線程都可以讀取,但不能修改。只讀共享對象包括不可變對象和事實不可變對象。

線程安全共享:線程安全的對象在其內部實現同步。

保護對象:保護對象只能通過持有特定的鎖來訪問。

3、對象的組合

1)設計線程安全類

設計線程安全類的三大基本要素:

找出構成對象狀態的所有變量;

找出約束狀態變量的不變性條件;

建立對象狀態的並發訪問管理策略;

2)線程安全委托

如果一個類是由多個獨立且線程安全的狀態變量組成,並且在所有的操作中都不包含無效狀態的轉換,那麽可以將線程安全委托給底層的狀態變量。如用volatileatomic類、安全容器包裝修飾。

在現有的安全類中加功能

擴展類:繼承安全類 或者 內部包裝一個安全類

內部包裝一個安全類:

技術分享

要確認同步的內容修改為同一個鎖:

技術分享

類中擁有指向底層List的唯一外部引用,就能確保線程安全:

技術分享

4、基礎構建模塊

同步容器:Vectorhashtable

並發容器:ConcurrentHashMapCopyOnWriteArrayListconcurrentMapCopyOnWriteArraySetConcurrentLinkedQueue

阻塞隊列:LinkedBlockingQueueArrayBlockingQueuePriorityBlockingQueue

在構建高可用的應用程序中,有界隊列是一種強大的管理工具:它們能抑制並防止產生過多的工作項,使應用程序在負荷過載的情況下更健壯。

線程中斷

恢復中斷:

技術分享

傳遞InterruptedException :不捕獲異常直接拋出,或者捕獲異常簡單處理後再拋出。

同步工具類

同步工具類可以是任何一個對象,只要它根據其自身的狀態來協調線程的控制流。

常見的同步工具類:阻塞隊列、信號量(Semaphore)、柵欄(Barrier)、閉鎖(Latch)。

CountDownLatchCyclicBarrier都能夠實現線程之間的等待,只不過它們側重點不同:

CountDownLatch一般用於某個線程A等待若幹個其他線程執行完任務之後,它才執行;

CyclicBarrier一般用於一組線程互相等待至某個狀態,然後這一組線程再同時執行;

    另外,CountDownLatch是不能夠重用的,而CyclicBarrier是可以重用的。

Semaphore其實和鎖有點類似,它一般用於控制對某組資源的訪問權限。

CountDownLatch一種靈活閉鎖的實現,它可以使一個或多個線程等待一事件發生後再執行。

閉鎖狀態包括一個計數器:countDown方法遞減計數器,await方法等待計數器到達零。

FutureTask也可以做閉鎖,它表示的計算是通過Callable來實現的,相當於一種可生產結果的Runnable,可以處於:等待運行、正在運行、運行完成這三種狀態。

FutureTask將計算結果從執行計算的線程傳遞到獲取這個結果的線程。

http://blog.csdn.net/liulipuo/article/details/39029643

技術分享

編寫多線程程序有三種方法,Thread,Runnable,Callable.

RunnableCallable的區別是,
(1)Callable規定的方法是call(),Runnable規定的方法是run().
(2)Callable的任務執行後可返回值,而Runnable的任務是不能返回值得
(3)call方法可以拋出異常,run方法不可以
(4)運行Callable任務可以拿到一個Future對象,表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,並檢索計算的結果。通過Future對象可以了解任務執行情況,可取消任務的執行,還可獲取執行結果。

Future就是對於具體的Runnable或者Callable任務的執行結果進行

取消、查詢是否完成、獲取結果、設置結果操作。get方法會阻塞,直到任務返回結果(Future簡介)

FutureTask則是一個RunnableFuture<V>,而RunnableFuture實現了Runnbale又實現了Futrue<V>這兩個接口另外它還可以包裝RunnableCallable<V>, 由構造函數註入依賴。Runnable註入會被Executors.callable()函數轉換為Callable類型,即FutureTask最終都是執行Callable類型的任務。

由於FutureTask實現了Runnable,因此它既可以通過Thread包裝來直接執行,也可以提交給ExecuteService來執行。

並且還可以直接通過get()函數獲取執行結果,該函數會阻塞,直到結果返回。因此FutureTask既是Future

Runnable,又是包裝了Callable( 如果是Runnable最終也會被轉換為Callable ), 它是這兩者的合體。

計數信號量(Counting Semaphore):用來控制同時訪問某個資源的操作數量,或同時執行某個指定操作的數量。

Semaphore可以控同時訪問的線程個數,通過 acquire() 獲取一個許可,如果沒有就等待,而 release() 釋放一個許可。

技術分享

柵欄:它能阻塞一組線程只到某個事件發生。所有線程必須同時到達柵欄的位置,才能繼續執行。

CyclicBarrier:可以使一定數量的參與方反復的在柵欄位置匯集。

技術分享

技術分享

構建高效且可伸縮的結果緩存:

技術分享

第一種用synchronized進行同步,確保線程安全。但是性能差,長時間計算時間長。

技術分享

第二種:用ConcurrentHashMap代替HashMap,多線程可以並發使用。

漏洞:當兩個線程同時調用compute方法時可能會計算出相同的結果,浪費一次計算。

技術分享

第三種並發容器中包含FutrueFutreTask表示一個計算過程,如果有結果get()方法立刻放回結果,否則一直阻塞到結果計算完成再返回。如果其他線程在計算結果,那麽新到的線程就等待這個結果被計算出來。

漏洞:if操作非原子,依然有計算相同值的概率。

技術分享

第四種:ConcurrentMap的原子方法putIfAbsent(),避免第三種方法的漏洞。

5、任務執行

各自獨立的任務可以並行,要有清晰的任務邊界和明確的任務執行策略。

Executor

執行策略:

在什麽線程中執行任務

任務按照什麽順序執行(FIFO\LIFO\優先級)

有多少任務可以並發執行

在隊列中有多少任務等待執行

過載時拒絕策略:怎麽選擇拒絕的任務,怎麽通知程序有任務被拒絕。

執行任務前後的,應該做些什麽。

ExecutorService,completionService

Exec.invokeAll(tasks)

ExecutorServiceinvokeAll方法有兩種用法:

1.exec.invokeAll(tasks)

2.exec.invokeAll(tasks, timeout, unit)

其中tasks是任務集合,timeout是超時時間,unit是時間單位

當所有任務都完成時、調用線程被中斷時或者超過時限時,限時版本的invokeAll都會返回結果。超過時限後,任何尚未完成的任務都會被取消。

作為invokeAll的返回值,每個任務要麽正常地完成,要麽被取消。

技術分享

6、線程取消與關閉

1)任務取消

Java沒有提供取消線程的機制,但是提供了中斷,這是一種協作機制,能夠使一個線程終止另一個線程的當前工作。

中斷機制可以取消一個線程。通常中斷是實現取消最合理的方式。

調用interrupt並不意味著立即停止目標線程正在進行的工作,而只是傳遞了請求中斷的消息。(發出中斷請求後,線程會在下一個合適的時刻中斷自己)

使用靜態的interrupt會清除當前線程的中斷狀態,必須對它進行處理:拋出InterruptedException或者再次調用interrupt恢復中斷,否則中斷狀態將會被屏蔽。

每個線程都有自己的中斷策略,所以除非你知道中斷對該線程的含義,否則就不應該中斷這個線程。

只有實現了線程中斷策略的代碼才可以屏蔽中斷請求,在常規的任務和庫代碼中都不應該屏蔽中斷請求。

可以通過Future來實現取消(Future有個cancel方法)。

處理不可中斷的阻塞:可以使用類似中斷的手段停止線程。

同步的I/O------close socket

獲取鎖----------lockInterruptibly

2)停止基於線程的服務

關閉ExecutorService:

shutDown正常關閉,一直等到隊列中的任務都執行完後才關閉。

shutDownNow強行關閉,首先關閉當前正在執行的任務,然後返回所有尚未執行的任務清單。

shutDownNow不會返回正在執行的任務(這些任務首先被取消)。應該在線程運行時進行判斷,並提供返回的方法:

技術分享

技術分享

技術分享

3)處理非正常的線程終止

線程會由於發生一個未捕獲異常而終止,並發程序中這種線程終止不易發覺,會出現線程“遺漏”,會引起意外的後果。

導致線程提前死亡的最主要的原因是RuntimeException

Thread API 提供了UncaughtExceptionHandler,它能夠檢測出某個線程由於未捕獲的異常而終結的情況:

技術分享

在運行時間較長的應用程序中,通常會為所有線程的未捕獲異常指定同一個異常處理器,並且該處理器至少會將異常信息記錄到日誌中。

如果希望任務在發生異常而失敗時獲得通知,並執行一些特定於任務的恢復操作,可以將任務封裝在能捕獲異常的RunnableCallable中,或改寫ThreadPoolExecutorafterExecute方法。

7、線程池的使用

線程饑餓死鎖:在線程池中,一個任務以來其他任務的執行,就可能產生死鎖。在單線程的Executor中,一個任務將另一個任務提交到同一個Executor中,並且等待這個任務的結果,就會發生死鎖。

只有當任務是同類且相互獨立時,線程池的性能才最佳。運行時間長任務和運行時間短任務一起會造成“阻塞”,依賴型的任務會產生死鎖。

1)設置線程池大小

計算密集型任務:Ncpu +1

IO密集型任務:2Ncpu 需要計算等待時間和計算時間。

Ncpu=Runtime.getRuntime().availableProcessors();

影響線程池大小的資源:CPU周期、內存、文件句柄、套接字句柄、數據庫連接等。

計算每個任務對資源的需求量。然後該資源可用的總量除以每個任務的需求量,就是線程池大小的上限。

2)配置ThreadPoolExecutor

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue,threadFactory, handler);

Executor一些基本的實現:

1newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。

構造一個緩沖功能的線程池,配置corePoolSize=0maximumPoolSize=Integer.MAX_VALUEkeepAliveTime=60s,以及一個無容量的阻塞隊列 SynchronousQueue,因此任務提交之後,將會創建新的線程執行;線程空閑超過60s將會銷毀


2newFixedThreadPool 創建一個定長線程池,可控制線程最大並發數,超出的線程會在隊列中等待。

構造一個固定線程數目的線程池,配置的corePoolSizemaximumPoolSize大小相同,同時使用了一個無界LinkedBlockingQueue存放阻塞任務,因此多余的任務將存在再阻塞隊列,不會由RejectedExecutionHandler處理


3newScheduledThreadPool 創建一個定長線程池,支持定時及周期性任務執行。

構造有定時功能的線程池,配置corePoolSize,無界延遲阻塞隊列DelayedWorkQueue;有意思的是:maximumPoolSize=Integer.MAX_VALUE,由於DelayedWorkQueue是無界隊列,所以這個值是沒有意義的


4newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

構造一個只支持一個線程的線程池,配置corePoolSize=maximumPoolSize=1,無界阻塞隊列LinkedBlockingQueue;保證任務由一個線程串行執行

自定義線程池內部包裝一個ThreadPoolExecutor,自定義init(),destory(),threadFactory implements ThreadFactory rejectedExecutionHandler implements RejectedExecutionHandler

3)擴展ThreadPoolExecutor

ThreadPoolExecutor子類中重寫的方法:

beforeExecute

afterExecute

Terminated

可以在beforeExecuteafterExecute中添加日誌、計時、監視、統計信息收集等功能。

無論任務是從run中正常結束還是拋出異常,都會執行afterExecute,但是如果是error就不會。

如果beforeExecute拋出RuntimeException,則runafterExecute都不執行。

Terminated在線程池關閉時調用。可以用來釋放資源、發送通知、記錄日誌、收集finalize統計信息等:

技術分享

技術分享

4)遞歸算法並行化

若循環中任務都是獨立的,可以用Executor把串行循環變成並行循環:

技術分享

每次叠代執行任務的工作量大於管理一個新任務的工作量大,那麽適合並行化。

技術分享

閉鎖:只用運行setValue才能運行getValue

8、圖形用戶界面應用程序

為什麽GUI是單線程的。

GUI對象通過線程封閉機制來保證一致性。所有組件和數據模型對象都封閉在事件線程中。

Swing的單線程規則:Swing中的組件以及模型只能在這個事件分發線程中進行創建、修改以及查詢。

1)用線程接力處理長時間任務

技術分享

9、避免活躍性危險

1)死鎖

如果所有線程以固定的順序來獲得鎖,那麽在程序中就不會出現鎖順序死鎖問題。

如果在持有鎖時調用某個外部方法,那麽將出現活躍性問題。在這個外部方法中可能會獲取其他鎖(可能會產生死鎖),或阻塞時間過長,導致其他線程無法及時獲得當前被持有的鎖。

解決方法是:使用開放調用,以同步代碼塊替代同步方法(丟失一些原子性)來使用開放調用。

2)死鎖避免和診斷

支持定時鎖:LOCK.tryLock(time,tiemunit);等方法使用。

按順序獲取鎖。

診斷:通過Thread Dump來識別死鎖。定期觸發線程轉儲,來觀察程序的加鎖行為。

在生成線程轉儲之前,JVM將在等待關系圖中通過搜索循環來找出死鎖。

3)其他活躍性

饑餓:線程無法獲取所需要的資源而不能繼續進行,就會發生饑餓。

(如:線程的優先級使用不當,低優先級的線程容易饑餓)

要避免使用線程優先級,因為這會增加平臺依賴型,並可能導致活躍性問題。在大多數並發應用程序中,都可以使用默認的優先級。

Thread API 定義的優先級是10,當每個平臺的操作系統的優先級不一定是10個。線程調度最終取決於操作系統。

丟失信號:

活鎖:當多個相互協作的線程都對彼此進行響應從而修改各自的的狀態,並使得任何一個線程都無法繼續執行時,就會發生活鎖。

解決方法:在程序的重試機制中引入隨機性。通過等待隨機長度的時間和回退可以有效避免活鎖的發生。

10 性能與可伸縮性

1)性能的思考

資源:CPU時鐘周期、內存、網絡寬帶、I/O寬帶、數據庫請求、磁盤空間等。

多線程的額外開銷:

線程之間的協調(如加鎖、觸發信號、內存同步等);

增加上下文的切換;

線程的創建與銷毀;

線程調度等。

通過並發獲取更好的性能,需要:更有效的利用現有的資源,和在出現新處理資源時程序盡可能的利用這些新資源。

可伸縮性指:當憎加計算資源時(如CPU、內存、I/O寬帶、存儲容量),程序的吞吐量和或處理能力會相應的增加。

伸縮性調優的目的:設法將問題的計算並行化,從而能利用更多的計算資源來完成更多的工作。

對性能進行調優時,一定要有明確的性能需求(這樣才知道什麽時候應該調優,什麽時候應該停止)。還需要測試程序和真實的配置、負載環境等。

性能應以測試為基準,不能猜測。

2)Amdahl定律

Amdahl定律:在增加計算資源的情況下,程序在理論上能夠實現最高加速比,這個值取決於程序中可並行組件和串行組件所占的比重。

技術分享

F:必須被串行的部分;

N:處理器個數。

找出程序中串行部分,減小它。

3)線程的引入開銷

上下文切換:

技術分享

內存同步:

阻塞:

自螺旋等待:循環不斷地嘗試獲取鎖,直到獲取鎖;適合短時間能獲取的鎖。

掛起:掛起被阻塞的線程。適合需要長時間才能獲取的鎖。

4)減少鎖競爭

在並發程序中,對伸縮性的最主要威脅是獨占方式的鎖資源。

影響鎖競爭的因素:鎖的請求頻率和每次在持有該鎖的時間。

減少鎖競爭方法:

1)減少鎖持有時間(縮小鎖範圍);

2)降低鎖請求頻率(減小鎖的粒度:鎖分解和鎖分段技術);

3)使用帶有協調機制的獨占鎖,這些機制允許更高的並發率。

4)檢測CPU利用率:UNIX系統上的vmstatmpstatWINDOWS系統上的perfmon工具都可以檢測。

通常CPU沒有充分利用的原因:

負載不足------可能客戶端系統負載能力。增加負載。

I/O密集型------檢測網絡通信流量,或使用工具檢測;

外部限制------如數據庫連接或web服務。分析依賴的外部服務;

鎖競爭-----分析線程轉儲,檢測鎖競爭情況。

5)不使用對象池技術:通常,對象分配操作的開銷比同步的開銷更低。

11、並發程序的測試

1)正確的測試

基本的單元測試;

對阻塞操作的測試;

安全性測試;

資源管理的測試;

使用回調;

產生更多交替操作。

2)性能測試

吞吐量;

響應時間;

伸縮性。

3)避免性能測試的陷阱

垃圾回收:運行時序無法預測,很可能影響最終測試的每次叠代時間。

動態編譯:JVM會選擇在應用程序線程或後臺線程中執行編譯過程,不同的選擇會對計時結果產生影響。

對代碼的路徑不真實采樣:編譯器可能對已編譯的代碼優化,重排。不同程序中使用相同相同的方法時性能有差異。

無用代碼的消除:

技術分享

4)其他測試方法

代碼審查;

靜態分析工具:

不同工具的分析對象及應用技術對比

Java 靜態分析工具

分析對象

應用技術

Checkstyle

Java 源文件

缺陷模式匹配

FindBugs

字節碼

缺陷模式匹配;數據流分析

PMD

Java 源代碼

缺陷模式匹配

Jtest

Java 源代碼

缺陷模式匹配;數據流分析

內置編程規範

Checkstyle:

  • Javadoc 註釋:檢查類及方法的 Javadoc 註釋
  • 命名約定:檢查命名是否符合命名規範
  • 標題:檢查文件是否以某些行開頭
  • Import 語句:檢查 Import 語句是否符合定義規範
  • 代碼塊大小,即檢查類、方法等代碼塊的行數
  • 空白:檢查空白符,如 tab,回車符等
  • 修飾符:修飾符號的檢查,如修飾符的定義順序
  • 塊:檢查是否有空塊或無效塊
  • 代碼問題:檢查重復代碼,條件判斷,魔數等問題
  • 類設計:檢查類的定義是否符合規範,如構造函數的定義等問題

FindBugs:

  • Bad practice 壞的實踐:常見代碼錯誤,用於靜態代碼檢查時進行缺陷模式匹配
  • Correctness 可能導致錯誤的代碼,如空指針引用等
  • 國際化相關問題:如錯誤的字符串轉換
  • 可能受到的惡意攻擊,如訪問權限修飾符的定義等
  • 多線程的正確性:如多線程編程時常見的同步,線程調度問題。
  • 運行時性能問題:如由變量定義,方法調用導致的代碼低效問題。

PMD:

  • 可能的 Bugs:檢查潛在代碼錯誤,如空 try/catch/finally/switch 語句
  • 未使用代碼(Dead code):檢查未使用的變量,參數,方法
  • 復雜的表達式:檢查不必要的 if 語句,可被 while 替代的 for 循環
  • 重復的代碼:檢查重復的代碼
  • 循環體創建新對象:檢查在循環體內實例化新對象
  • 資源關閉:檢查 Connect,Result,Statement 等資源使用之後是否被關閉掉

Jtest

  • 可能的錯誤:如內存破壞、內存泄露、指針錯誤、庫錯誤、邏輯錯誤和算法錯誤等
  • 未使用代碼:檢查未使用的變量,參數,方法
  • 初始化錯誤:內存分配錯誤、變量初始化錯誤、變量定義沖突
  • 命名約定:檢查命名是否符合命名規範
  • Javadoc 註釋:檢查類及方法的 Javadoc 註釋
  • 線程和同步:檢驗多線程編程時常見的同步,線程調度問題
  • 國際化問題:
  • 垃圾回收:檢查變量及 JDBC 資源是否存在內存泄露隱患

面向方面的測試技術;

分析與監測工具。

12、顯示鎖

Java5之前對協調共享對象的訪問機制只有synchronizedvolatile.

Java5增加了ReentrantLockReentrantLock不是替代內置鎖的方法,而是當內置加鎖機制不適用時,另一種可選擇的高級功能。

1)Lock ReentrantLock

Lock:提供了一種無條件、可輪詢的、定時的以及可中斷的鎖獲取操作。

lock.lockInterruptibly()可以使得線程在等待鎖是支持響應中斷;

即:嘗試獲取鎖。如果當前有別的線程獲取了鎖,則睡眠。當該函數返回時,有兩種可能:a.已經獲取了鎖
b.獲取鎖不成功,但是別的線程打斷了它。則該線程會拋出IterruptedException異常而返回,同時該線程的中斷標誌會被清除。

Lock()lockInterruptibly()區別:

線程嘗試獲取鎖操作失敗後,在等待過程中,如果該線程被其他線程中斷了,它是如何響應中斷請求的。lock方法會忽略中斷請求,繼續獲取鎖直到成功;

lockInterruptibly則直接拋出中斷異常來立即響應中斷,由上層調用者處理中斷。

如果要求被中斷線程不能參與鎖的競爭操作,則此時應該使用lockInterruptibly方法,一旦檢測到中斷請求,立即返回不再參與鎖的競爭並且取消鎖獲取操作

lock.tryLock()可以使得線程在等待一段時間過後如果還未獲得鎖就停止等待而非一直等待。

內置鎖的局限性:

無法中斷一個正在等待獲取鎖的線程。

內置鎖在進入同步塊時,采取的是無限等待的策略,一旦開始等待,就既不能中斷也不能取消,容易產生饑餓與死鎖的問題

在線程調用notify方法時,會隨機選擇相應對象的等待隊列的一個線程將其喚醒,而不是按照FIFO的方式,如果有強烈的公平性要求,比如FIFO就無法滿足

lock.lockInterruptiblylock.tryLock()可以更好的制定獲得鎖的重試機制,而非盲目一直等待,可以更好的避免饑餓和死鎖問題

ReentrantLock可以成為公平鎖(非默認的),所謂公平鎖就是鎖的等待隊列的FIFO(線程將按照它們發出請求的順序來獲取鎖),不過公平鎖會帶來性能消耗,如果不是必須的不建議使用。

非塊結構的鎖:

內置鎖的鎖獲得和釋放都是基於代碼塊。

Lock的鎖可以不局限於代碼塊中,如分段技術基於在散列的容器中實現了不同的散列鏈,以便使用不同的鎖。可以用Lock給每個鏈表結點使用一個獨立的鎖,使不同的線程能獨立的對鏈表的不同部分進行操作。

連鎖式加鎖

java並發編程實戰筆記