Java線程同步的方法

若是向一個變量寫值,而這個變量接下來可能會被另外一個線程所讀取,或者從一個變量讀值,而它的值多是前面由另外一個線程寫入的,此時就必須使用同步。html

sychronized

Java語言的關鍵字,當它用來修飾一個方法或者一個代碼塊的時候,可以保證在同一時刻最多隻有一個線程執行該段代碼,它是在軟件層面依賴JVM實現同步。   java

synchronized 方法或語句的使用提供了對與每一個對象相關的隱式監視器鎖的訪問,但卻強制全部鎖獲取和釋放均要出如今一個塊結構中:當獲取了多個鎖時,它們必須以相反的順序釋放,且必須在與全部鎖被獲取時相同的範圍內釋放全部鎖。git

經過在方法聲明中加入 synchronized關鍵字來聲明 synchronized 方法。github

  synchronized 方法控制對類成員變量的訪問:每一個類實例對應一把鎖,每一個 synchronized 方法都必須得到調用該方法的類實例的鎖方能執行,不然所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能得到該鎖,從新進入可執行狀態。這種機制確保了同一時刻對於每個類實例,其全部聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態(由於至多隻有一個可以得到該類實例對應的鎖),從而有效避免了類成員變量的訪問衝突(只要全部可能訪問類成員變量的方法均被聲明爲 synchronized)。編程

  synchronized 方法的缺陷:若將一個大的方法聲明爲synchronized 將會大大影響效率,典型地,若將線程類的方法 run() 聲明爲synchronized,因爲在線程的整個生命期內它一直在運行,所以將致使它對本類任何 synchronized 方法的調用都永遠不會成功。多線程

解決synchronized 方法的缺陷  併發

經過 synchronized關鍵字來聲明synchronized 塊。分佈式

synchronized(lock) {
// 訪問或修改被鎖保護的共享狀態

其中的代碼必須得到對象 syncObject (類實例或類)的鎖方能執行。因爲能夠針對任意代碼塊,且可任意指定上鎖的對象,故靈活性較高。ide

  當兩個併發線程訪問同一個對象中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程獲得執行。另外一個線程必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。函數

  當一個線程訪問對象的一個synchronized(this)同步代碼塊時,另外一個線程仍然能夠訪問該對象中的非synchronized(this)同步代碼塊。其餘線程對對象中全部其它synchronized(this)同步代碼塊的訪問將被阻塞。

  若是線程進入由線程已經擁有的監控器保護的 synchronized 塊,就容許線程繼續進行,當線程退出第二個(或者後續) synchronized 塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個 synchronized 塊時,才釋放鎖。

  在修飾代碼塊的時候須要一個reference對象做爲鎖的對象。在修飾方法的時候默認是當前對象做爲鎖的對象。在修飾類時候默認是當前類的Class對象做爲鎖的對象.

lock

鎖是用來控制多個線程訪問共享資源的方式,通常來講,一個鎖可以防止多個線程同時訪問共享資源(可是有些鎖能夠容許多個線程併發的訪問共享資源,好比讀寫鎖)。在Lock接口出現以前,Java程序是靠synchronized關鍵字實現鎖功能的,而JavaSE5以後,併發包中新增了Lock接口(以及相關實現類)用來實現鎖功能,它提供了與synchronized關鍵字相似的同步功能,只是在使用時須要顯式地獲取和釋放鎖。雖然它缺乏了(經過synchronized塊或者方法所提供的)隱式獲取釋放鎖的便捷性,可是卻擁有了鎖獲取與釋放的可操做性、可中斷的獲取鎖以及超時獲取鎖等多種synchronized關鍵字所不具有的同步特性。

使用synchronized關鍵字將會隱式地獲取鎖,可是它將鎖的獲取和釋放固化了,也就是先獲取再釋放。固然,這種方式簡化了同步的管理,但是擴展性沒有顯示的鎖獲取和釋放來的好。例如,針對一個場景,手把手進行鎖獲取和釋放,先得到鎖A,而後再獲取鎖B,當鎖B得到後,釋放鎖A同時獲取鎖C,當鎖C得到後,再釋放B同時獲取鎖D,以此類推。這種場景下,synchronized關鍵字就不那麼容易實現了,而使用Lock卻容易許多。Lock 接口的實現容許鎖在不一樣的做用範圍內獲取和釋放,並容許以任何順序獲取和釋放多個鎖,從而支持使用這種技術。在大多數狀況下,應該使用如下語句:

   Lock l = ...; //lock接口的實現類對象
     l.lock();
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock();
     }  

在finally塊中釋放鎖,目的是保證在獲取到鎖以後,最終可以被釋放。

不要將獲取鎖的過程寫在try塊中,由於若是在獲取鎖(自定義鎖的實現)時發生了異常,異常拋出的同時,也會致使鎖無端釋放。

Lock接口提供的synchronized關鍵字所不具有的主要特性以下表所示。

Lock是一個接口,它定義了鎖獲取和釋放的基本操做,Lock的API以下表所示。

 

Lock 接口實現提供了比使用 synchronized 方法和語句可得到的更普遍的鎖定操做。此實現容許更靈活的結構,能夠具備差異很大的屬性,能夠支持多個相關的 Condition 對象。在硬件層面依賴特殊的CPU指令實現同步更加靈活。

什麼是Condition ? Condition 接口將 Object 監視器方法(wait、notify 和 notifyAll)分解成大相徑庭的對象,以便經過將這些對象與任意 Lock 實現組合使用,爲每一個對象提供多個等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用。

在java.util.concurrent.locks包中有不少Lock的實現類,經常使用的有ReentrantLock、ReadWriteLock(實現類ReentrantReadWriteLock).它們是具體實現類,不是java語言關鍵字。

ReentrantLock

  一個可重入的互斥鎖 Lock,它具備與使用 synchronized 方法和語句所訪問的隱式監視器鎖相同的一些基本行爲和語義,但功能更強大。

  最典型的代碼以下:   

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() { 
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

 

重入性:指的是同一個線程屢次試圖獲取它所佔有的鎖,請求會成功。當釋放鎖的時候,直到重入次數清零,鎖才釋放完畢。

  ReentrantLock 的lock機制有2種,忽略中斷鎖和響應中斷鎖,這給咱們帶來了很大的靈活性。好比:若是A、B 2個線程去競爭鎖,A線程獲得了鎖,B線程等待,可是A線程這個時候實在有太多事情要處理,就是 一直不返回,B線程可能就會等不及了,想中斷本身,再也不等待這個鎖了,轉而處理其餘事情。這個時候ReentrantLock就提供了2種機制,第一,B線程中斷本身(或者別的線程中斷它),可是ReentrantLock 不去響應,繼續讓B線程等待,你再怎麼中斷,我全當耳邊風(synchronized原語就是如此);第二,B線程中斷本身(或者別的線程中斷它),ReentrantLock 處理了這個中斷,而且再也不等待這個鎖的到來,徹底放棄。   

  ReentrantLock相對於synchronized多了三個高級功能:      

①等待可中斷

在持有鎖的線程長時間不釋放鎖的時候,等待的線程能夠選擇放棄等待.     

tryLock(long timeout, TimeUnit unit)

 

②公平鎖

按照申請鎖的順序來一次得到鎖稱爲公平鎖.synchronized的是非公平鎖,ReentrantLock能夠經過構造函數實現公平鎖.

new RenentrantLock(boolean fair)

 

公平鎖和非公平鎖。這2種機制的意思從字面上也能瞭解個大概:即對於多線程來講,公平鎖會依賴線程進來的順序,後進來的線程後得到鎖。而非公平鎖的意思就是後進來的鎖也能夠和前邊等待鎖的線程同時競爭鎖資源。對於效率來說,固然是非公平鎖效率更高,由於公平鎖還要判斷是否是線程隊列的第一個纔會讓線程得到鎖。      

③綁定多個Condition    

經過屢次newCondition能夠得到多個Condition對象,能夠簡單的實現比較複雜的線程同步的功能.經過await(),signal();

synchronized和lock的用法與區別    

synchronized是託管給JVM執行的,而lock是java寫的控制鎖的代碼。    

synchronized採用的是CPU悲觀鎖機制,即線程得到的是獨佔鎖。獨佔鎖意味着其餘線程只能依靠阻塞來等待線程釋放鎖。而在CPU轉換線程阻塞時會引發線程上下文切換,當有不少線程競爭鎖的時候,會引發CPU頻繁的上下文切換致使效率很低。     

Lock用的是樂觀鎖方式。每次不加鎖而是假設沒有衝突而去完成某項操做,若是由於衝突失敗就重試,直到成功爲止。樂觀鎖實現的機制就 是CAS操做(Compare and Swap)。

 ReentrantLock必須在finally中釋放鎖,不然後果很嚴重,編碼角度來講使用synchronized更加簡單,不容易遺漏或者出錯。

 ReentrantLock提供了可輪詢的鎖請求,他能夠嘗試的去取得鎖,若是取得成功則繼續處理,取得不成功,能夠等下次運行的時候處理,因此不容易產生死鎖,而synchronized則一旦進入鎖請求要麼成功,要麼一直阻塞,因此更容易產生死鎖。

 synchronized的話,鎖的範圍是整個方法或synchronized塊部分;而Lock由於是方法調用,能夠跨方法,靈活性更大。

 通常狀況下都是用synchronized原語實現同步,除非下列狀況使用ReentrantLock:

  ①某個線程在等待一個鎖的控制權的這段時間須要中斷    

  ②須要分開處理一些wait-notify,ReentrantLock裏面的Condition應用,可以控制notify哪一個線程    

  ③具備公平鎖功能,每一個到來的線程都將排隊等候

參考資料

https://github.com/Mr-YangCheng/ForAndroidInterview/blob/master/java/%5BJava%5D%20%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%AD%A5%E7%9A%84%E6%96%B9%E6%B3%95%EF%BC%9Asychronized%E3%80%81lock%E3%80%81reentrantLock%E5%88%86%E6%9E%90.md

分佈式系統互斥性與冪等性問題的分析與解決:https://tech.meituan.com/distributed-system-mutually-exclusive-idempotence-cerberus-gtis.html

方騰飛:<Java併發編程的藝術>

相關文章
相關標籤/搜索