Java多線程:synchronized的可重入性

線程安全

線程安全函數的概念比較直觀,衆所周知,同一進程的不一樣線程會共享同一主內存,線程的私有棧中只包括PC棧,操做數棧,局部變量數組和動態連接。對共享內存進行讀寫時,若要保證線程安全,則必須經過加鎖的方式。數組

可重入定義

若一個程序或子程序能夠「在任意時刻被中斷而後操做系統調度執行另一段代碼,這段代碼又調用了該子程序不會出錯」,則稱其爲可重入(reentrant或re-entrant)的。即當該子程序正在運行時,執行線程能夠再次進入並執行它,仍然得到符合設計時預期的結果。與多線程併發執行的線程安全不一樣,可重入強調對單個線程執行時從新進入同一個子程序仍然是安全的。安全

可重入的條件

  • 不在函數內使用靜態或全局數據。
  • 不返回靜態或全局數據,全部數據都由函數的調用者提供。
  • 使用本地數據(工做內存),或者經過製做全局數據的本地拷貝來保護全局數據。
  • 不調用不可重入函數。

可重入與線程安全

通常而言,可重入的函數必定是線程安全的,反之則不必定成立。在不加鎖的前提下,若是一個函數用到了全局或靜態變量,那麼它不是線程安全的,也不是可重入的。若是咱們加以改進,對全局變量的訪問加鎖,此時它是線程安全的但不是可重入的,由於一般的加鎖方式是針對不一樣線程的訪問(如Java的synchronized),當同一個線程屢次訪問就會出現問題。只有當函數知足可重入的四條條件時,纔是可重入的。多線程

synchronized是可重入鎖

回到引言裏的問題,若是一個獲取鎖的線程調用其它的synchronized修飾的方法,會發生什麼?併發

從設計上講,當一個線程請求一個由其餘線程持有的對象鎖時,該線程會阻塞。當線程請求本身持有的對象鎖時,若是該線程是重入鎖,請求就會成功,不然阻塞。函數

咱們回來看synchronized,synchronized擁有強制原子性的內部鎖機制,是一個可重入鎖。所以,在一個線程使用synchronized方法時調用該對象另外一個synchronized方法,即一個線程獲得一個對象鎖後再次請求該對象鎖,是永遠能夠拿到鎖的操作系統

在Java內部,同一個線程調用本身類中其餘synchronized方法/塊時不會阻礙該線程的執行,同一個線程對同一個對象鎖是可重入的,同一個線程能夠獲取同一把鎖屢次,也就是能夠屢次重入。緣由是Java中線程得到對象鎖的操做是以線程爲單位的,而不是以調用爲單位的。線程

synchronized可重入鎖的實現

以前談到過,每一個鎖關聯一個線程持有者和一個計數器。當計數器爲0時表示該鎖沒有被任何線程持有,那麼任何線程都均可能得到該鎖而調用相應方法。當一個線程請求成功後,JVM會記下持有鎖的線程,並將計數器計爲1。此時其餘線程請求該鎖,則必須等待。而該持有鎖的線程若是再次請求這個鎖,就能夠再次拿到這個鎖,同時計數器會遞增。當線程退出一個synchronized方法/塊時,計數器會遞減,若是計數器爲0則釋放該鎖。設計

相關文章
相關標籤/搜索