Lock和synchronized這兩個最多見的鎖均可以達到線程安全的目的,可是功能上有很大不一樣。java
Lock並非用來代替synchronized的而是當使用synchronized不知足狀況或者不合適的時候來提供高級功能的程序員
爲何synchronized不夠用算法
靈活性的提升帶來了額外的責任。 缺乏塊結構鎖定須要手動的去釋放鎖。 在大多數狀況下,應使用如下慣用法:編程
Lock lock = new ReentrantLock(); lock.lock(); try{ }finally { lock.unlock(); }
當鎖定和解鎖發生在不一樣的範圍內時,必須當心以確保經過try-finally或try-catch保護持有鎖定時執行的全部代碼,以確保在必要時釋放鎖定。
Lock實現經過使用非阻塞嘗試獲取鎖( tryLock() ),嘗試獲取可被中斷的鎖( lockInterruptibly以及嘗試獲取鎖),提供了比使用synchronized方法和語句更多的功能。可能會超時( tryLock(long, TimeUnit) )。安全
Lock類還能夠提供與隱式監視器鎖定徹底不一樣的行爲和語義,例如保證順序,不可重用或死鎖檢測。 若是實現提供了這種特殊的語義,則實現必須記錄這些語義。服務器
請注意, Lock實例只是普通對象,它們自己能夠用做synchronized語句中的目標。 獲取Lock實例的監視器鎖與調用該實例的任何lock方法沒有指定的關係。 建議避免混淆,除非在本身的實現中使用,不然不要以這種方式使用Lock實例。數據結構
全部Lock實現必須強制執行與內置監視器鎖所提供的相同的內存同步語義,如Java語言規範中所述 :併發
不成功的鎖定和解鎖操做以及可重入的鎖定/解鎖操做不須要任何內存同步效果。ide
實施注意事項工具
鎖獲取的三種形式(可中斷,不可中斷和定時)在其性能特徵可能有所不一樣。 此外,在給定的Lock類中,可能沒法提供中斷正在進行的鎖定的功能。 所以,不須要爲全部三種形式的鎖獲取定義徹底相同的保證或語義的實現,也不須要支持正在進行的鎖獲取的中斷。 須要一個實現來清楚地記錄每一個鎖定方法提供的語義和保證。 在支持鎖獲取中斷的範圍內,它還必須服今後接口中定義的中斷語義:所有或僅在方法輸入時才這樣作。
void lock(); // 獲取鎖。
注意:lock()方法不能被中斷,這會帶來很大的隱患:一旦陷入死鎖、lock()就會陷入永久等待狀態
void lockInterruptibly() throws InterruptedException;
除非當前線程被中斷,不然獲取鎖。
獲取鎖(若是有)並當即返回。
若是該鎖不可用,則出於線程調度目的,當前線程將被掛起,並在發生如下兩種狀況之一以前處於休眠狀態:
若是當前線程:在進入此方法時已設置其中斷狀態;要麼獲取鎖時被中斷,而且支持鎖獲取的中斷,而後拋出InterruptedException並清除當前線程的中斷狀態。
注意事項
在某些實現中,中斷鎖獲取的能力多是不可能的,而且若是可能的話多是昂貴的操做。 程序員應意識到多是這種狀況。 在這種狀況下,實現應記錄在案。與正常方法返回相比,實現可能更喜歡對中斷作出響應。Lock實現可能可以檢測到鎖的錯誤使用,例如可能致使死鎖的調用,而且在這種狀況下可能引起(未經檢查的)異常。
注意 synchronized 在獲取鎖時是不可中斷的
boolean tryLock();
非阻塞獲取鎖(若是有)並當即返回true值。 若是鎖不可用,則此方法將當即返回false值。相比於Lock這樣的方法顯然功能更增強大,咱們能夠根據是否能獲取到鎖來決定後續程序的行爲
注意:該方法會當即返回,即使在拿不到鎖的時候也不會在一隻在那裏等待
該方法的典型用法是:
Lock lock = new ReentrantLock(); if(lock.tryLock()){ try{ // TODO }finally { lock.unlock(); } }else{ // TODO }
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
若是線程在給定的等待時間內獲取到鎖,而且當前線程還沒有中斷,則獲取該鎖。
若是鎖可用,則此方法當即返回true值。 若是該鎖不可用,則出於線程調度目的,當前線程將被掛起,並處於休眠狀態,直到發生如下三種狀況之一:
若是通過了指定的等待時間,則返回值false 。 若是時間小於或等於零,則該方法將根本不等待。
注意事項
在某些實現中,中斷鎖獲取的能力多是不可能的,而且若是可能的話多是昂貴的操做。 程序員應意識到多是這種狀況。 在這種狀況下,實現應記錄在案。與正常方法返回或報告超時相比,實現可能更喜歡對中斷作出響應。Lock實現可能可以檢測到鎖的錯誤使用,例如可能致使死鎖的調用,而且在這種狀況下可能引起(未經檢查的)異常。
void unlock(); //釋放鎖。
注意事項
Lock實現一般會限制哪些線程能夠釋放鎖(一般只有鎖的持有者才能釋放鎖),而且若是違反該限制,則可能引起(未經檢查的)異常。
Condition newCondition(); //返回綁定到此Lock實例的新Condition實例。
該組件與當前鎖綁定,當前線程只有得到了鎖。 才能調用該組件的wait()方法,而調用後,當前線程將釋放鎖。
注意事項
Condition實例的確切操做取決於Lock實現。
Lock對象鎖還提供了synchronized所不具有的其餘同步特性,如可中斷鎖的獲取(synchronized在等待獲取鎖時是不可中斷的),超時中斷鎖的獲取,等待喚醒機制的多條件變量Condition等,這也使得Lock鎖具備更大的靈活性。Lock的加鎖和釋放鎖和synchronized有一樣的內存語義,也就是說下一個線程加鎖後能夠看到前一個線程解鎖前發生的全部操做。
根據一下6種狀況能夠區分多種不一樣的鎖,下面詳細介紹
是否鎖住 | 鎖名稱 | 實現方式 | 例子 |
---|---|---|---|
鎖柱 | 悲觀鎖 | synchronized、lock | synchronized、lock |
不鎖住 | 樂觀鎖 | CAS算法 | 原子類、併發容器 |
悲觀鎖又稱互斥同步鎖,互斥同步鎖的劣勢:
悲觀鎖:
當一個線程拿到鎖了以後其餘線程都不能獲得這把鎖,只有持有鎖的線程釋放鎖以後才能獲取鎖。
樂觀鎖:
本身才進行操做的時候並不會有其餘的線程進行干擾,因此並不會鎖住對象。在更新的時候,去對比我在修改期間的數據有沒有人對他進行改過,若是沒有改變則進行修改,若是改變了那就是別人改的那我就不改了放棄了,或者從新來。
開銷對比:
使用場景:
是否共享 | 鎖名稱 |
---|---|
能夠 | 共享鎖(讀鎖) |
不能夠 | 排他鎖(獨佔鎖) |
共享鎖:
獲取共享鎖以後,能夠查看可是沒法修改和刪除數據,其餘線程此時也能夠獲取到共享鎖也能夠查看但沒法修改和刪除數據
案例:ReentrantReadWriteLock的讀鎖(具體實現後續系列文章會講解)
排他鎖:
獲取排他鎖的以後,別的線程是沒法獲取當前鎖的,好比寫鎖。
案例:ReentrantReadWriteLock的寫鎖(具體實現後續系列文章會講解)
是否排隊 | 鎖名稱 |
---|---|
排隊 | 公平鎖 |
不排隊 | 非公平鎖 |
非公平鎖:
先嚐試插隊,插隊失敗再排隊,非公平是指不徹底的按照請求的順序,在必定的狀況下能夠進行插隊
存在的意義:
案例:
例如:當有線程執行tryLock的時候一旦有線程釋放了鎖,那麼這個正在執行tryLock的線程立馬就能獲取到鎖即便在它以前已經有其餘線程在等待隊列中
公平鎖:
排隊,公平是指的是按照線程請求的順序來進行分配鎖
案例:以ReentrantLock爲例,建立對象的時候參數爲true(具體實現後續系列文章會講解)
注意:
非公平也一樣不提倡插隊行爲,這裏指的非公平是指在合適的時機插隊,而不是盲目的插隊
優缺點:
非公平鎖:
公平鎖:
是否能夠重入 | 鎖名稱 |
---|---|
能夠 | 可重入鎖 |
不能夠 | 不可重入鎖 |
案例:以ReentrantLock爲例(具體實現後續系列文章會講解)
是否能夠中斷 | 鎖名稱 | 案例 |
---|---|---|
能夠 | 可中斷鎖 | Lock是可中斷鎖(由於tryLock和lockInterruptibly都能響應中斷) |
不能夠 | 不可中斷鎖 | Synchronized就是不可中斷鎖 |
是否自旋 | 鎖名稱 |
---|---|
是 | 自旋鎖 |
否 | 阻塞鎖 |
使用場景:
這三種鎖優化的方式在前一篇Synchronized文章種全部講解