Lock和synchronized在功能上是同樣的。不過Lock提供了一些其餘功能,包括定時的鎖等待、可中斷的鎖等待、公平性,以及實現非塊結構的加鎖。html
從性能上Lock的實現類ReentrantLock在JDK5.0以前要好於synchronized,在JDK6.0以後,synchronized作了優化,因此二者的性能相差無幾了。java
那在使用上應該選擇哪一個呢?在《Java併發編程實戰》中有一句話:"僅當內置鎖不能知足需求時,才能夠考慮使用ReentrantLock"。在一些內置鎖沒法
git
知足需求的狀況下,ReentrantLock能夠做爲一種高級工具。當須要一些高級功能時才應該使用ReentrantLock,這些功能包括:可定時的、可輪詢的與可
編程
中斷的鎖獲取操做,公平隊列,以及非塊結構的鎖。不然,仍是應該優先使用synchronized。安全
在JDK5.0中,內置鎖與ReentrantLock相比還有另一個優勢:在線程轉儲中能給出在哪些調用幀中得到了哪些鎖,並可以檢測出發生死鎖的線程。JVM併發
並不知道哪些線程持有ReentrantLock,所以在調試使用ReentrantLock的線程問題時,將起不到幫助做用。JDK6.0解決了這個問題,它提供了一個管理和調試app
接口,鎖能夠經過該接口進行註冊,從而與ReentrantLock相關的加鎖信息就能出如今線程轉儲中,並經過其餘的管理接口和調試接口來訪問。ide
在內置鎖中,死鎖是一個很嚴重的問題,恢復程序惟一的方法是從新啓動程序,而防止死鎖的惟一方法就是在構造程序時避免出現不一致的鎖順序。函數
ReentrantLock有可定時與可輪詢的功能,這就從另外一個方面避免了死鎖的發生(注意,使用ReentrantLock必定要在finally中釋放鎖)。高併發
重入鎖ReentrantLock,顧名思義,就是支持重進入的鎖(synchronized隱式的支持重進入),它表示該鎖可以支持一個線程對資源的重複加鎖。除此以外,
該鎖還支持獲取鎖時的公平和非公平性選擇。
一、ReentrantLock的基本使用。
package org.burning.sport.javase.thread.reentrantlock; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockTest implements Runnable{ private static Lock lock = new ReentrantLock(); private int i; @Override public void run() { while(true) { increment(); } } public void increment() { try { lock.lock(); i++; System.out.println(Thread.currentThread().getName() + ":" + i); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } finally { //必定要在finally這裏解鎖,不然就是定時炸彈 lock.unlock(); } } public static void main(String[] args) { ReentrantLockTest test = new ReentrantLockTest(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); Thread t3 = new Thread(test); t1.setName("線程一:"); t2.setName("線程二:"); t3.setName("線程三:"); t1.start(); t2.start(); t3.start(); } }
二、鎖的公平性
若是在絕對時間上,先對鎖進行獲取的請求必定先被知足,那麼這個鎖是公平的,反之,是不公平的。ReentrantLock提供了一個構造函數,可以控制鎖是不是公平的。
public ReentrantLock(boolean fair); 默認是非公平的。公平鎖保證了鎖的獲取按照FIFO原則,而代價是進行大量的線程切換影響性能,非公平的鎖則會形成線程的 「飢餓」。
三、Lock的中斷響應
線程的中斷 中能夠看到,Lock是能夠響應線程觸發的中斷的。只要在獲取鎖的時候用 lock.lockInterruptibly();就能夠了。
四、輪詢鎖與定時鎖
lock.tryLock(); 和 lock.tryLock(5, TimeUtil.SECONDS); 在內置鎖中,死鎖是一個很嚴重的問題,恢復程序惟一的方法是從新啓動程序,而防止死鎖的惟一方法就是在
構造程序時避免出現不一致的鎖順序。ReentrantLock有可定時與可輪詢的功能,這就從另外一個方面避免了死鎖的發生(注意,使用ReentrantLock必定要在finally中釋放鎖)。
public class TryLockTest implements Runnable { private Lock lock = new ReentrantLock(); @Override public void run() { try { if (lock.tryLock(5, TimeUnit.SECONDS)) { lock.lock(); //do something ..... } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
五、ReentrantLock的好搭檔Condition條件
Condition定義了等待/通知兩種類型的方法,當前線程調用這些方法時,須要提早獲取到Condition對象關聯的鎖。Condition對象是由Lock對象(調用Lock對象的new Condition()方法)
建立出來的,換句話說,Condition是依賴Lock對象的。
示例代碼:
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ConditionUseCase { Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void conditionWait() throws InterruptedException { lock.lock(); try { condition.await(); } finally { lock.unlock(); } } public void conditionSignal() throws InterruptedException { lock.lock(); try { condition.signal(); } finally { lock.unlock(); } } }
Condition的(部分)方法及描述:
void await() throws InterruptedException
當前線程進入等待狀態直到被通知(signal)或中斷,當前線程將進入運行狀態且從await()方法返回的狀況,包括:
其餘線程調用該Condition的signal()或signalAll()方法,而當前線程被選中喚醒
□ 其餘線程(調用interrupt()方法)中斷當前線程;
□ 若是當前等待線程從await()方法返回,那麼代表該線程已經獲取了Condition對象所對應的鎖;
void awaitUninterruptibly()
當前線程進入等待狀態直到被通知,從方法名稱上能夠看出該方法對中斷不敏感
long awaitNanos(long nanosTimeout) throws InterruptedException
當前線程進入等待狀態直到被通知、中斷或者超時。返回值表示剩餘的時間,若是在nanosTimeout納秒
以前被喚醒,那麼返回值就是(nanosTimeout - 實際耗時) ,若是返回值是0或者負數,那麼能夠認定已經超時了
boolean awaitUntil(Date deadline) throws InterruptedException
當前線程進入等待狀態直到被通知、中斷或者到某個時間。若是沒有到指定時間就被通知,方法返回true,不然,
表示到了指定時間,方法返回false
void signal() 喚醒一個等待在Condition上的線程,該線程從等待方法返回前必須得到與Condition相關聯的鎖
void signalAll() 喚醒全部等待在Condition上的線程,可以從等待方法返回的線程必須得到與Condition相關聯的鎖。
示例代碼:
LockSupport是一個很是方便實用的線程阻塞工具,它能夠在線程內任意位置讓線程阻塞。和Thread.suspend()相比,它彌補了因爲resume()在前發生,
致使線程沒法記錄執行的狀況。和Object.wait()相比,它不須要先得到某個對象的鎖,也不會拋出InterruptedException異常。
LockSupport提供的阻塞和喚醒的方法
void park() 阻塞當前線程,若是調用 unpark(Thread thread) 方法或者當前線程被中斷,才能從park方法返回
void parkNanos(long nanos) 阻塞當前線程,最長不超過nonos納秒,返回條件在park()的基礎上增長了超時返回
void parkUntil(long deadline) 阻塞當前線程,知道deadline時間(從1970年開始到deadline時間的毫秒數)
void unpark(Thead thread) 喚醒處於阻塞狀態的線程Thread
LockSupport sample demo:
package org.burning.sport.javase.thread.reentrantlock.lock.support; import java.util.concurrent.locks.LockSupport; public class LockSupportTest { private static Object u = new Object(); static ChangeObjectThread thread1 = new ChangeObjectThread("t1"); static ChangeObjectThread thread2 = new ChangeObjectThread("t2"); public static class ChangeObjectThread extends Thread { public ChangeObjectThread(String threadName) { super.setName(threadName); } @Override public void run() { synchronized (u) { System.out.println("in " + getName()); LockSupport.park(); } } } public static void main(String[] args) throws InterruptedException { thread1.start(); Thread.sleep(100); thread2.start(); LockSupport.unpark(thread1); LockSupport.unpark(thread2); thread1.join(); thread2.join(); } }
一、使用示例:
package org.burning.sport.javase.thread.readwritelock; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class Cache { static Map<String, String> map = new HashMap<>(); static ReadWriteLock lock = new ReentrantReadWriteLock(); static Lock readLock = lock.readLock(); static Lock writeLock = lock.writeLock(); public static final Object get(String key) { readLock.lock(); try { return map.get(key); } finally { readLock.unlock(); } } public static final void put(String key, String value) { writeLock.lock(); try { map.put(key, value); } finally { writeLock.unlock(); } } public static void clear() { writeLock.lock(); try { map.clear(); } finally { writeLock.unlock(); } } }
二、讀寫鎖的實現分析:
2.一、讀寫狀態設計
讀寫鎖一樣依賴自定義同步器來實現同步功能。而讀寫狀態就是其同步器的同步狀態。同步狀態表示鎖被一個線程重複獲取的次數,而讀寫鎖的自定義同步器須要在
同步狀態(一個整形變量)上維護多個讀線程和一個寫線程的狀態。若是在一個整形變量上維護多種狀態,就必定須要 「按位切割使用」 這個變量,讀寫鎖將變量切分爲了兩
個部分,高16位表示讀,低16位表示寫。
2.二、寫鎖的獲取與釋放
寫鎖是一個支持重入的排它鎖。若是當前線程已經獲取了寫鎖,則增長寫狀態。寫鎖的釋放與ReentrantLock的釋放過程基本相似,每次釋放均減小寫狀態,當寫狀態
爲0時表示寫鎖已被釋放。
2.三、讀鎖的獲取與釋放
讀鎖是一個支持重入的共享鎖,它可以被多個線程同時獲取,在沒有其餘寫線程訪問(或者寫狀態爲0)時,讀鎖總會被成功的獲取,而所作的也只(線程安全的)是增長讀
狀態。讀鎖的每次釋放均減小讀狀態,減小的值是(1<<16)。
2.四、鎖降級
鎖降級指的是寫鎖降級成爲讀鎖。若是當前線程擁有寫鎖,而後將其釋放,最後再獲取讀鎖,這種分段完成的過程不能稱之爲鎖降級。鎖降級是指把持住(當前擁有)寫鎖,
再獲取到讀鎖,隨後釋放寫鎖的過程。(ReentrantReadWriteLock不支持鎖升級)
【1】《Java併發編程的藝術》,方騰飛
【2】《Java併發編程實戰》,童雲蘭
【3】《Java高併發程序設計》,葛一鳴
【4】《Think In Java》,第21章 併發