Lock 和 Condition 併發編程大師重複造輪子?

Photo By Instagram sooyaaa

昨天的問題

Java 在語法層面已經有了 synchronized 來實現管程,爲什麼還要在 JDK 中提供了 Lock 和 Condition 工具類來做這樣的事情,這屬於重複造輪子嗎?

我的答案

首先你可能會想到的是 synchronized 性能問題,但是我想告訴你的是 synchronized 在高版本的 JDK 中性能已經得到了大幅的提升,很多開發者開始提倡使用 synchronized,性能問題可以不斷優化提升,它並不是重載輪子的原因。

大家都知道管程幫助我們解決了多線程資源共享問題,但同時也帶來了死鎖的風險。Coffman 等人在 1971 年總結出來產生死鎖的四個必要條件:

1. 互斥條件

一個資源在同一時刻只能被一個線程操作。

2. 佔有且等待

線程因爲請求資源而阻塞時不會釋放已經獲取到的資源。

3. 不可強行佔有

線程已經獲取到的資源,在未釋放前不允許被其他線程強行剝奪。

4. 循環等待

線程存在循環等待資源的關係(線程 T1 依次佔有資源 A,B;線程 T2 依次佔有資源 B,A;這就構成了循環等待資源關係)。

當發生死鎖的時候必然上面四個條件都會滿足,那麼只要我們破壞其中的任何一個條件,我們即可解決死鎖問題。首先條件 1 無法破解,因爲共享資源必須是互斥的,如果可以多個線程同時操作也沒必要加鎖了。那我們試圖使用 synchronized 來破解剩餘的三個條件:

1. 佔有且等待

synchronized 獲取資源時候,只要資源獲取不到,線程立即進入阻塞狀態,並且不會釋放已經獲取的資源。那麼我們可以調整一下獲取共享資源的方式,我們通過一個鎖中介,通過中介一次性獲取線程需要的所有資源,如果存在單個資源不滿足情況,直接阻塞,而不是獲取部分資源,這樣我們即可解決這個問題,破解該條件。

2. 不可強行佔有

synchronized 獲取不到資源時候,線程直接阻塞,無法被中斷釋放資源,因此這個條件 synchronized 無法破解。

3. 循環等待

循環等待是因爲線程在競爭 2 個互斥資源時候會以不同的順序去獲取資源,如果我們將線程獲取資源的順序固定下來即可破解這個條件。

綜上我們可以知道 synchronized 不能破解“不可強行佔有”條件,這就是 JDK 同時提供 Lock 這種管程的實現方式的原因。當然啦,Lock 使用起來也更加的靈活。例如我們有多個共享資源,鎖是嵌套方式獲取的,如線程需要先獲取 A 鎖,然後獲取 B 鎖,然後釋放 A 鎖,獲取 C 鎖,接着釋放 B 鎖,獲取 D 鎖 等等。這種嵌套獲取鎖的方式 synchronized 是無法實現的,但是 Lock 卻可以幫助我們來解決這個問題。既然我們知道了 JDK 重造管程的原因,那我們來一起看一下 Lock 爲我們提供的四種進入獲取鎖的方式:

1. void lock();

這種方式獲取不到鎖時候線程會進入阻塞狀態,和 synchronized 類似。

2. void lockInterruptibly() throws InterruptedException;

這種方式獲取不到鎖線程同樣會進入阻塞,但是它可以接收中斷信號,退出阻塞。

3. boolean tryLock();

這種方式不管是否能獲取到鎖都不會阻塞而是立刻返回獲取結果,成功返回 true,失敗返回 false。

4. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

這種方式獲取不到鎖的線程會進入阻塞狀態,但是在指定的時間內如果仍未獲得鎖,則返回 false,否則返回 true。

以上即爲昨天的問題的答案,小夥伴們對這個答案是否滿意呢?歡迎留言和我討論。

又要到年末了,你是不是又悄咪咪的開始看機會啦。爲了廣大小夥伴能充足電量,能順利通過 BAT 的面試官無情三連炮,我特意推出大型刷題節目。每天一道題目,第二天給答案,前一天給小夥伴們獨立思考的機會。

點下“在看”,鼓勵一下?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章