Java線程與並發編程實踐----鎖框架
針對條件進行加鎖和等待。不同於對象的內置加鎖同步以及java.lang.Object的等
待/通知機制,包含鎖框架的並發工具類通過輪詢鎖、顯示等待及其它方式改善這種
機制。
鎖框架包含了經常使用的鎖、重入鎖、條件、讀寫鎖以及沖入讀寫鎖等類別。
一、鎖(Lock)
Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。此實
現允許更靈活的結構,可以具有差別很大的屬性,可以支持多個相關的 Condition 對象。
鎖是控制多個線程對共享資源進行訪問的工具。通常,鎖提供了對共享資源的獨占訪問。
一次只能有一個線程獲得鎖,對共享資源的所有訪問都需要首先獲得鎖。不過,某些鎖
可能允許對共享資源並發訪問,如 ReadWriteLock 的讀取鎖。
synchronized 方法或語句的使用提供了對與每個對象相關的隱式監視器鎖的訪問,但卻
強制所有鎖獲取和釋放均要出現在一個塊結構中:當獲取了多個鎖時,它們必須以相反的
順序釋放,且必須在與所有鎖被獲取時相同的詞法範圍內釋放所有鎖。
雖然 synchronized 方法和語句的範圍機制使得使用監視器鎖編程方便了很多,而且還
幫助避免了很多涉及到鎖的常見編程錯誤,但有時也需要以更為靈活的方式使用鎖。
例如,某些遍歷並發訪問的數據結果的算法要求使用 "hand-over-hand" 或 "chain locking":
獲取節點 A 的鎖,然後再獲取節點 B 的鎖,然後釋放 A 並獲取 C,然後釋放
B 並獲取 D,依此類推。Lock 接口的實現允許鎖在不同的作用範圍內獲取和釋放,
並允許以任何順序獲取和釋放多個鎖,從而支持使用這種技術。
二、重入鎖
類ReentrantLock實現了接口Lock,描述了一個可重入的互斥鎖。這個鎖和一個持有量
相關聯。當一個線程持有這個鎖並且調用lock()、lockUninterruptibly()或者任意一個trylock()
方法重新獲取鎖,這個持有量就遞增一。當線程調用unlock()方法,持有量就遞減1。當持有量
降為零,鎖就會被釋放。
ReentrantLock提供了與通過同步方法、代碼塊得以訪問的隱士監聽鎖同樣的並發語義
及內存語義。不過它具備可擴展的功能並且在高線程爭用的環境下(線程頻繁地請求獲取已經
被其它線程持有的鎖)具有更好地性能。
使用重入鎖實現同步塊:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Test { public static void main(String[] args) { final Lock lock = new ReentrantLock(); ExecutorService execute = Executors.newFixedThreadPool(2); class Worker implements Runnable { private final String name; public Worker(String name) { this.name = name; } @Override public void run() { lock.lock(); System.out.println(Thread.currentThread().getName() +"---"+ name); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } execute.execute(new Worker("jiaxx")); execute.execute(new Worker("jiadd")); execute.shutdown(); } }
三、條件
接口Condition把Object的wait和notification方法分解到不同的條件對象中。
通過把這些條件和任意Lock實現的使用組合起來,起到讓每個對象上具有多重等待集合
的作用。這裏Lock取代了同步方法、代碼塊,Condition取代了Object的wait、notification
方法。
使用重入鎖和條件實現生產者和消費者:
package xiancheng; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class PC { public static void main(String[] args) { Shared s = new Shared(); Thread t1 = new Thread(new Product(s,s.lock,s.condition)); Thread t2 = new Thread(new Consumer(s,s.lock,s.condition)); t1.start(); t2.start(); } } class Shared { private char c; private volatile boolean writeable = true; final Lock lock; final Condition condition; public Shared() { this.lock = new ReentrantLock(); this.condition = lock.newCondition(); } public void setChar(char ch) { lock.lock(); while (!writeable) { try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } this.c = ch; writeable = false; condition.signal(); lock.unlock(); } public char getChar() { lock.lock(); while (writeable) { try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } writeable = true; condition.signal(); lock.unlock(); return c; } } class Product implements Runnable { private final Shared s; private final Lock lock; private final Condition condition; public Product(Shared s, Lock lock, Condition condition) { this.s = s; this.lock = lock; this.condition = condition; } @Override public void run() { for (char i = 'A'; i < 'Z'; i++) { lock.lock(); s.setChar(i); System.out.println("生產者生產了一個" + i); lock.unlock(); } } } class Consumer implements Runnable { private final Lock lock; private final Condition condition; private final Shared s; public Consumer(Shared s, Lock lock, Condition condition) { this.s = s; this.lock = lock; this.condition = condition; } @Override public void run() { char ch; do { lock.lock(); ch = s.getChar(); System.out.println("消費者消費了一個" + ch); lock.unlock(); } while (ch != 'Z'); } }
四、讀寫鎖
讀寫鎖適用於對數據結構頻繁讀而較少修改的場景。
鎖框架針對這些場景提供了讀--寫鎖機制,在讀取時具有更好地並發性,而寫入時
保證安全的互斥訪問。這一機制基於接口ReadWriteLock接口。該接口維護了一對鎖,一個
針對寫操作,一個針對讀操作,讀鎖可以被多個線程持有,而寫鎖是互斥的,只允許一個線程
訪問修改共享數據。
具體代碼的實現參見我的另一篇微博:
http://blog.51cto.com/12222886/1964183
Java線程與並發編程實踐----鎖框架