Java理論與實踐: JDK 5.0中更靈活、更具延展性的鎖定機制

來源:互聯網
上載者:User

JDK 5.0為開發人員開發高效能的並發應用程式提供了一些很有效新選擇。例如, java.util.concurrent.lock 中的類 ReentrantLock 被作為 Java 語言中 synchronized 功能的替代,它具有相同的記憶體語義、相同的鎖定,但在競爭條件下卻有更好的效能,此外,它還有 synchronized 沒有提供的其他特性。這是否意味著我們應當忘記 synchronized ,轉而只用 ReentrantLock 呢?並發性專家 Brian Goetz 剛從他的夏季休假中返回,他將為我們提供答案。

多線程和並發性並不是什麼新內容,但是 Java 語言設計中的創新之一就是,它是第一個直接把跨平台執行緒模式和正規的記憶體模型整合到語言中的主流語言。核心類庫包含一個 Thread 類,可以用它來構建、啟動和操縱線程,Java 語言套件括了跨線程傳達並發性約束的構造 —— synchronized 和 volatile 。在簡化與平台無關的並發類的開發的同時,它決沒有使並發類的編寫工作變得更繁瑣,只是使它變得更容易了。

synchronized 快速回顧

把代碼塊聲明為 synchronized,有兩個重要後果,通常是指該代碼具有 原子性(atomicity)和 可見度(visibility)。原子性意味著一個線程一次只能執行由一個指定監控對象(lock)保護的代碼,從而防止多個線程在更新共用狀態時相互衝突。可見度則更為微妙;它要對付記憶體緩衝和編譯器最佳化的各種反常行為。一般來說,線程以某種不必讓其他線程立即可以看到的方式(不管這些線程在寄存器中、在處理器特定的緩衝中,還是通過指令重排或者其他編譯器最佳化),不受緩衝變數值的約束,但是如果開發人員使用了同步,如下面的代碼所示,那麼運行庫將確保某一線程對變數所做的更新先於對現有 synchronized 塊所進行的更新,當進入由同一監控器(lock)保護的另一個 synchronized 塊時,將立刻可以看到這些對變數所做的更新。類似的規則也存在於 volatile 變數上。(有關同步和 Java 記憶體模型的內容,請參閱 參考資料。)

synchronized (lockObject) {
 // update object state
}

所以,實現同步操作需要考慮安全更新多個共用變數所需的一切,不能有競爭條件,不能破壞資料(假設同步的邊界位置正確),而且要保證正確同步的其他線程可以看到這些變數的最新值。通過定義一個清晰的、跨平台的記憶體模型(該模型在 JDK 5.0 中做了修改,改正了原來定義中的某些錯誤),通過遵守下面這個簡單規則,構建“一次編寫,隨處運行”的並發類是有可能的:

不論什麼時候,只要您將編寫的變數接下來可能被另一個線程讀取,或者您將讀取的變數最後是被另一個線程寫入的,那麼您必須進行同步。

不過現在好了一點,在最近的 JVM 中,沒有爭用的同步(一個線程擁有鎖的時候,沒有其他線程企圖獲得鎖)的效能成本還是很低的。(也不總是這樣;早期 JVM 中的同步還沒有最佳化,所以讓很多人都這樣認為,但是現在這變成了一種誤解,人們認為不管是不是爭用,同步都有很高的效能成本。)

對 synchronized 的改進

如此看來同步相當好了,是嗎?那麼為什麼 JSR 166 小組花了這麼多時間來開發 java.util.concurrent.lock 架構呢?答案很簡單-同步是不錯,但它並不完美。它有一些功能性的限制 —— 它無法中斷一個正在等候獲得鎖的線程,也無法通過投票得到鎖,如果不想等下去,也就沒法得到鎖。同步還要求鎖的釋放只能在與獲得鎖所在的堆疊框架相同的堆疊框架中進行,多數情況下,這沒問題(而且與異常處理互動得很好),但是,確實存在一些非塊結構的鎖定更合適的情況。

ReentrantLock 類

java.util.concurrent.lock 中的 Lock 架構是鎖定的一個抽象,它允許把鎖定的實現作為 Java 類,而不是作為語言的特性來實現。這就為 Lock 的多種實現留下了空間,各種實現可能有不同的調度演算法、效能特性或者鎖定語義。 ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的並發性和記憶體語義,但是添加了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的效能。(換句話說,當許多線程都想訪問共用資源時,JVM 可以花更少的時候來調度線程,把更多時間用在執行線程上。)

reentrant 鎖意味著什麼呢?簡單來說,它有一個與鎖相關的擷取計數器,如果擁有鎖的某個線程再次得到鎖,那麼擷取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放。這模仿了 synchronized 的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者後續) synchronized 塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個 synchronized 塊時,才釋放鎖。

在查看清單 1 中的程式碼範例時,可以看到 Lock 和 synchronized 有一點明顯的區別 —— lock 必須在 finally 塊中釋放。否則,如果受保護的代碼將拋出異常,鎖就有可能永遠得不到釋放!這一點區別看起來可能沒什麼,但是實際上,它極為重要。忘記在 finally 塊中釋放鎖,可能會在程式中留下一個定時炸彈,當有一天炸彈爆炸時,您要花費很大力氣才有找到源頭在哪。而使用同步,JVM 將確保鎖會獲得自動釋放。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.