當(dāng)Java類庫沒有提供適合的同步工具時,就需要構(gòu)建自定義同步工具。
可阻塞狀態(tài)依賴操作的結(jié)構(gòu)
acquir lock on object state;//請求獲取鎖
while(precondition does not hold){//沒有滿足前提條件
release lock;//先釋放鎖
wait until precondition might hold;//等待滿足前提條件
optionlly fail if interrupted or timeout expires;//因?yàn)橹袛嗷蛘叱瑫r執(zhí)行失敗
reacquire lock;//重新嘗試獲取鎖
}
perform action//執(zhí)行
release lock;//釋放鎖
有界緩存實(shí)現(xiàn)基類示例
public class BaseBoundBuffer<V> {
private final V[] buf;
private int tail;
private int head;
private int count;
@SuppressWarnings("unchecked")
public BaseBoundBuffer(int capacity) {
buf = (V[]) new Object[capacity];
}
public synchronized void doPut(V v) {
buf[tail] = v;
if (++tail == buf.length)
tail = 0;
count++;
}
public synchronized V doTake() {
V v = buf[head];
if (++head == buf.length)
head = 0;
count--;
return v;
}
public final synchronized boolean isFull() {
return count == buf.length;
}
public final synchronized boolean isEmpty() {
return count == 0;
}
}
阻塞實(shí)現(xiàn)方式一:拋異常給調(diào)用者
public synchronized void put1(V v) throws Exception{
if(isFull())
throw new Exception("full error");
doPut(v);
}
分析:異常應(yīng)該應(yīng)用于發(fā)生異常情況中,在這里拋異常不合適;需要調(diào)用者是處理前提條件失敗的情況,并沒有解決根本問題。
阻塞實(shí)現(xiàn)方式二:通過輪詢和休眠
public void put2(V v) throws InterruptedException {
while (true) {//輪詢
synchronized (this) {
if (!isFull()) {
doPut(v);
return;
}
}
Thread.sleep(SLEEP_TIME);//休眠
}
}
分析:很難權(quán)衡休眠時間SLEEP_TIME設(shè)置。如果設(shè)置過小,CPU可能會輪詢多次,消耗CPU資源也越高;如果設(shè)置過大,響應(yīng)性就越低。
阻塞實(shí)現(xiàn)方式三:條件隊(duì)列
條件隊(duì)列中的元素是一個個等待相關(guān)條件的線程。每個Java對象都可以作為一個鎖,每個對象同樣可以作為一個條件隊(duì)列,并且Object中的wait、notify、notifyAll方法就構(gòu)成了內(nèi)部條件隊(duì)列的API。Object.wait會自動釋放鎖,并請求操作系統(tǒng)掛起當(dāng)前線程,從而使其它線程能獲得這個鎖并修改對象的狀態(tài)。Object.notify和Object.notifyAll能喚醒正在等待線程,從條件隊(duì)列中選取一個線程喚醒并嘗試重新獲取鎖。
public synchronized void put3(V v) throws InterruptedException {
while(isFull())
wait();
doput(v);
notifyAll();
}
分析:獲得較好響應(yīng),簡單易用。
使用條件隊(duì)列?
1.條件謂詞
1).定義:條件謂詞是使某個操作成為狀態(tài)依賴操作的前提條件。條件謂詞是由類中各個狀態(tài)變量構(gòu)成的表達(dá)式。例如,對于put方法的條件謂詞就是“緩存不為空”。
2).關(guān)系:在條件等待中存在一種重要的三元關(guān)系,包括加鎖、wait方法和一個條件謂詞。在條件謂詞中包含多個狀態(tài)變量,而每個狀態(tài)變量必須由一個鎖來保護(hù),因此在測試條件謂詞之前必須先持有這個鎖。鎖對象和條件隊(duì)列對象(及調(diào)用wait和notify等方法所在的對象)必須是同一個對象。
3).約束:每次調(diào)用wait都會隱式地和特定的條件謂詞相關(guān)聯(lián),當(dāng)調(diào)用特定條件謂詞時,調(diào)用者必須已經(jīng)持有與條件隊(duì)列相關(guān)的鎖,這個鎖必須還保護(hù)這組成條件謂詞的狀態(tài)變量
2.條件隊(duì)列使用規(guī)則
1).通常都有一個條件謂詞
2).永遠(yuǎn)在調(diào)用wait之前測試條件謂詞,并且在wait中返回后再次測試;
3).永遠(yuǎn)在循環(huán)中調(diào)用wait;
4).確保構(gòu)成條件謂詞的狀態(tài)變量被鎖保護(hù),而這個鎖必須與這個條件隊(duì)列相關(guān)聯(lián);
5).當(dāng)調(diào)用wait、notify和notifyAll時,要持有與條件隊(duì)列相關(guān)聯(lián)的鎖;
6).在檢查條件謂詞之后,開始執(zhí)行被保護(hù)的邏輯之前,不要釋放鎖;
3.通知
盡量使用notifyAll,而不是nofify.因?yàn)閚ofify會隨機(jī)喚醒一個線程從休眠狀態(tài)變?yōu)锽locked狀態(tài)(Blocked狀態(tài)是種線程一直處于嘗試獲取鎖的狀態(tài),即一旦發(fā)現(xiàn)鎖可用,馬上持有鎖),而notifyAll會喚醒條件隊(duì)列中所有的線程從休眠狀態(tài)變?yōu)锽locked狀態(tài).考慮這么種情況,假如線程A因?yàn)闂l件謂詞Pa進(jìn)入休眠狀態(tài),線程B因?yàn)闂l件謂詞Pb進(jìn)入休眠狀態(tài).這時Pb為真,線程C執(zhí)行單一的notify.如果JVM隨機(jī)選擇了線程A進(jìn)行喚醒,那么線程A檢查條件謂詞Pa不為真后又進(jìn)入了休眠狀態(tài).從這以后再也沒有其它線程能被喚醒,程序會一直處于休眠狀態(tài).如果使用notifyAll就不一樣了,JVM會喚醒條件隊(duì)列中所有等待線程從休眠狀態(tài)變?yōu)锽locked狀態(tài),即使隨機(jī)選出一個線程一因?yàn)闂l件謂詞不為真進(jìn)入休眠狀態(tài),其它線程也會去競爭鎖從而繼續(xù)執(zhí)行下去.
4.狀態(tài)依賴方法的標(biāo)準(zhǔn)形式
void stateDependentMethod throwsInterruptedException{
synchronized(lock){
while(!conditionPredicate))
lock.wait();
}
//dosomething();
....
notifyAll();
}
顯示Condition對象
顯示的Condition對象是一種更靈活的選擇,提供了更豐富的功能:在每個鎖上可以存在多個等待,條件等待可以是中斷的獲不可中斷的,基于時限的等待,以及公平的或非公平的隊(duì)列操作。一個Condition可以和一個Lock關(guān)聯(lián)起來,就像一個條件隊(duì)列和一個內(nèi)置鎖關(guān)聯(lián)起來一樣。要創(chuàng)建一個Condition,可以在相關(guān)聯(lián)的Lock上調(diào)用Lock.newCondition方法。以下用顯示條件變量重新實(shí)現(xiàn)有界緩存
public class ConditionBoundedBuffer<V> {
private final V[] buf;
private int tail;
private int head;
private int count;
private Lock lock = new ReentrantLock();
private Condition notFullCondition = lock.newCondition();
private Condition notEmptyCondition = lock.newCondition();
@SuppressWarnings("unchecked")
public ConditionBoundedBuffer(int capacity) {
buf = (V[]) new Object[capacity];
}
public void doPut(V v) throws InterruptedException {
try {
lock.lock();
while (count == buf.length)
notFullCondition.await();
buf[tail] = v;
if (++tail == buf.length)
tail = 0;
count++;
notEmptyCondition.signal();
} finally {
lock.unlock();
}
}
public V doTake() throws InterruptedException {
try {
lock.lock();
while (count == 0)
notEmptyCondition.await();
V v = buf[head];
buf[head] = null;
if (++head == buf.length)
head = 0;
count--;
notFullCondition.signal();
return v;
} finally {
lock.unlock();
}
}
}