java.util.concurrent.locks包提供了鎖和等待條件的接口和類, 可用於替代JDK1.5之前的同步(synchronized)和監視器機制(主要是Object類的wait(), notify(), notifyAll()方法).
互斥鎖--Lock接口及其實現類ReentrantLock
所謂互斥鎖, 指的是一次最多只能有一個線程持有的鎖. 在jdk1.5之前, 我們通常使用synchronized機制控制多個線程對共享資源的訪問. 而現在, Lock提供了比synchronized機制更廣泛的鎖定操作, Lock和synchronized機制的主要區別:
- synchronized機制提供了對與每個對象相關的隱式監視器鎖的訪問, 並強制所有鎖獲取和釋放均要出現在一個塊結構中, 當獲取了多個鎖時, 它們必須以相反的順序釋放. synchronized機制對鎖的釋放是隱式的, 只要線程運行的代碼超出了synchronized語句塊範圍, 鎖就會被釋放. 而Lock機制必須顯式的調用Lock對象的unlock()方法才能釋放鎖, 這爲獲取鎖和釋放鎖不出現在同一個塊結構中, 以及以更自由的順序釋放鎖提供了可能. 以下代碼演示了在不同的塊結構中獲取和釋放鎖:
Java代碼
- public class LockTest {
- private static Lock lock = new ReentrantLock();
- public static void main(String[] args) {
- lock.lock();
- invokeMethod();
- }
- private static void invokeMethod() {
- lock.unlock();
- }
- }
爲了確保鎖被釋放, 通常會採用如下的代碼形式:
Java代碼
- Lock lock = new ReentrantLock();
- // 獲取鎖
- lock.lock();
- try {
- // access the resource protected by this lock
- } finally {
- // 釋放鎖
- lock.unlock();
- }
|--void lock(): 執行此方法時, 如果鎖處於空閒狀態, 當前線程將獲取到鎖. 相反, 如果鎖已經被其他線程持有, 將禁用當前線程, 直到當前線程獲取到鎖.
|--void unlock(): 執行此方法時, 當前線程將釋放持有的鎖. 鎖只能由持有者釋放, 如果線程並不持有鎖, 卻執行該方法, 可能導致異常的發生.
- Lock提供了一個非塊結構的獲取鎖嘗試--tryLock(), 一個獲取可中斷鎖的嘗試--lockInterruptibly()和一個獲取超時失效鎖的嘗試--tryLock(long time, TimeUnit unit).
|--boolean tryLock(): 如果鎖可用, 則獲取鎖, 並立即返回true, 否則返回false. 該方法和lock()的區別在於, tryLock()只是"試圖"獲取鎖, 如果鎖不可用, 不會導致當前線程被禁用, 當前線程仍然繼續往下執行代碼. 而lock()方法則是一定要獲取到鎖, 如果鎖不可用, 就一直等待, 在未獲得鎖之前,當前線程並不繼續向下執行. 通常採用如下的代碼形式調用tryLock()方法:
Java代碼
- Lock lock = new ReentrantLock();
- if (lock.tryLock()) {
- try {
- // manipulate protected state
- } finally {
- lock.unlock();
- }
- } else {
- // perform alternative actions
- }
此用法可確保如果獲取了鎖, 則會釋放鎖; 如果未獲取鎖, 則不會試圖將其釋放.
- Lock的newCondition()方法可以獲得與該鎖綁定的Condition對象, Condition的詳細介紹如下.
條件--Condition
調用Condition對象的相關方法, 可以方便的掛起和喚醒線程. Object對象的wait(), notify(), notifyAll()方法當然也可以做到這一點, 但是Object對象的這些方法存在很不方便的地方--如果多個線程調用了obj的wait()方法而掛起, 那麼我們無法做到調用obj的notify()和notifyAll()方法喚醒其中特定的一個線程. 而Condition對象就可以做到這一點. 具體的代碼請參見我的上一篇博客http://coolxing.iteye.com/blog/1236696中的解法二部分.
- void await(): 調用Condition對象的await()方法將導致當前線程被掛起, 並釋放該Condition對象所綁定的鎖. Condition對象只能通過Lock類的newCondition()方法獲取, 因此一個Condition對象必然有一個與其綁定的Lock鎖. 調用Condition對象的await()方法的前提是: 當前線程必須持有與該Condition對象綁定的鎖, 否則程序可能拋出異常.
- void signal(): 喚醒一個在該Condition對象上掛起的線程. 如果存在多個線程等待這個Condition對象的喚醒, 則隨機選擇一個. 線程被喚醒之前, 必須重新獲取到鎖(與該Condition對象綁定的Lock對象).
- void signalAll(): 喚醒所有在該Condition對象上掛起的線程. 所有被喚醒的線程將競爭與該Condition對象綁定的鎖, 只有獲取到鎖的線程才能恢復到運行狀態.
讀寫鎖--ReadWriteLock接口及其實現類ReentrantReadWriteLock
ReentrantReadWriteLock中定義了2個內部類, ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock, 分別用來代表讀取鎖和寫入鎖. ReentrantReadWriteLock對象提供了readLock()和writeLock()方法, 用於獲取讀取鎖和寫入鎖.
- 讀取鎖允許多個reader線程同時持有, 而寫入鎖最多只能有一個writter線程持有.
- 讀寫鎖的使用場合: 讀取共享數據的頻率遠大於修改共享數據的頻率. 在上述場合下, 使用讀寫鎖控制共享資源的訪問, 可以提高併發性能.
- 如果一個線程已經持有了寫入鎖, 則可以再持有讀寫鎖. 相反, 如果一個線程已經持有了讀取鎖, 則在釋放該讀取鎖之前, 不能再持有寫入鎖.
- 可以調用寫入鎖的newCondition()方法獲取與該寫入鎖綁定的Condition對象, 此時與普通的互斥鎖並沒有什麼區別. 但是調用讀取鎖的newCondition()方法將拋出異常.
使用讀寫鎖的一個例子:
Java代碼
- public class ReadWriteLockTest {
- private static ReadWriteLock lock = new ReentrantReadWriteLock();
- private static Person person = new Person("David Beckham", true);
- public static void main(String[] args) {
- new Thread() {
- public void run() {
- while(true) {
- try {
- lock.readLock().lock();
- System.out.print("name = " + person.getName());
- System.out.println(", isMan = " + person.isMan());
- } finally {
- lock.readLock().unlock();
- }
- }
- };
- }.start();
- new Thread() {
- public void run() {
- boolean state = true;
- while(true) {
- try {
- lock.writeLock().lock();
- if (state) {
- person.setName("Lady GaGa");
- person.setMan(false);
- state = false;
- } else {
- person.setName("David Beckham");
- person.setMan(true);
- state = true;
- }
- } finally {
- lock.writeLock().unlock();
- }
- }
- };
- }.start();
- }
- }
- class Person {
- private String name;
- private boolean isMan;
- public Person(String name, boolean isMan) {
- this.name = name;
- this.isMan = isMan;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public boolean isMan() {
- return isMan;
- }
- public void setMan(boolean isMan) {
- this.isMan = isMan;
- }
- }