Java synchronized 可重入鎖 基本概念

衣舞晨風發表於2017-02-07

Java 5以前的併發程式設計

Java的執行緒模型建立在搶佔式執行緒排程的基礎上,也就是說:

  • 所有執行緒可以很容易的共享同一程式中的物件。
  • 能夠引用這些物件的任何執行緒都可以修改這些物件。
  • 為了保護資料,物件可以被鎖住。

Java基於執行緒和鎖的併發過於底層,而且使用鎖很多時候都是很萬惡的,因為它相當於讓所有的併發都變成了排隊等待。

在Java 5以前,可以用synchronized關鍵字來實現鎖的功能,它可以用在程式碼塊和方法上,表示在執行整個程式碼塊或方法之前執行緒必須取得合適的鎖。對於類的非靜態方法(成員方法)而言,這意味這要取得物件例項的鎖,對於類的靜態方法(類方法)而言,要取得類的Class物件的鎖,對於同步程式碼塊,程式設計師可以指定要取得的是那個物件的鎖。

不管是同步程式碼塊還是同步方法,每次只有一個執行緒可以進入,如果其他執行緒試圖進入(不管是同一同步塊還是不同的同步塊),JVM會將它們掛起(放入到等鎖池中)。這種結構在併發理論中稱為臨界區(critical section)。這裡我們可以對Java中用synchronized實現同步和鎖的功能做一個總結:

  • 只能鎖定物件,不能鎖定基本資料型別
  • 被鎖定的物件陣列中的單個物件不會被鎖定
  • 同步方法可以視為包含整個方法的synchronized(this) { … }程式碼塊
  • 靜態同步方法會鎖定它的Class物件
  • 內部類的同步是獨立於外部類的
  • synchronized修飾符並不是方法簽名的組成部分,所以不能出現在介面的方法宣告中
  • 非同步的方法不關心鎖的狀態,它們在同步方法執行時仍然可以得以執行
  • synchronized實現的鎖是可重入的鎖

在JVM內部,為了提高效率,同時執行的每個執行緒都會有它正在處理的資料的快取副本,當我們使用synchronzied進行同步的時候,真正被同步的是在不同執行緒中表示被鎖定物件的記憶體塊(副本資料會保持和主記憶體的同步,現在知道為什麼要用同步這個詞彙了吧),簡單的說就是在同步塊或同步方法執行完後,對被鎖定的物件做的任何修改要在釋放鎖之前寫回到主記憶體中;在進入同步塊得到鎖之後,被鎖定物件的資料是從主記憶體中讀出來的,持有鎖的執行緒的資料副本一定和主記憶體中的資料檢視是同步的 。

基於synchronized關鍵字的鎖機制有以下問題:

  • 鎖只有一種型別,而且對所有同步操作都是一樣的作用
  • 鎖只能在程式碼塊或方法開始的地方獲得,在結束的地方釋放
  • 執行緒要麼得到鎖,要麼阻塞,沒有其他的可能性

Java 5的併發程式設計

Java 5對鎖機制進行了重構,提供了顯示的鎖,這樣可以在以下幾個方面提升鎖機制:

  • 可以新增不同型別的鎖,例如讀取鎖和寫入鎖
  • 可以在一個方法中加鎖,在另一個方法中解鎖
  • 可以使用tryLock方式嘗試獲得鎖,如果得不到鎖可以等待、回退或者乾點別的事情,當然也可以在超時之後放棄操作

顯示的鎖都實現了java.util.concurrent.Lock介面,主要有兩個實現類:

  • ReentrantLock – 比synchronized稍微靈活一些的重入鎖
  • ReentrantReadWriteLock – 在讀操作很多寫操作很少時效能更好的一種重入鎖

可重入鎖

  • 可重入鎖,也叫做遞迴鎖,指的是同一執行緒外層函式獲得鎖之後 ,內層遞迴函式仍然有獲取該鎖的程式碼,但不受影響。
  • 可重入鎖(Reentrant Lock),是指允許同一個執行緒多次對該鎖進行acquire動作。對於不可重入的鎖,當一個執行緒多次呼叫acquire後將造成死鎖。

Reentrant 鎖意味著什麼呢?

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

本文節選自:關於Java併發程式設計的總結和思考

作者:jiankunking 出處:http://blog.csdn.net/jiankunking

相關文章