在java中,進(jìn)入同步塊synchronized和調(diào)用對象的notifyAll()方法是沒有順序保證的甥材,因此就存在線程饑餓的風(fēng)險叶洞,即有的線程永遠(yuǎn)處于等待同步進(jìn)入同步塊或者等待被喚醒的狀態(tài)。
可以看一個例子:
//1.0
public class Say{
//打印線程ID
public synchronized sayId(long id){
System.out.println(id);
}
}
public class MyRunnable implements Runnable {
private Say say;
public MyRunnable(Say say){
this.say=say;
}
@Override
public void run() {
while(true)
say.saySomething(Thread.currentThread().getId());
}
}
public class Main{
public static void main(String args[]){
Say say=new Say();
for(int i=0;i<5;i++){
new Thread(new MyRunnable(say).start();
}
}
}
在這個例子中座哩,每個線程會循環(huán)調(diào)用say實(shí)例的打印方法苍凛,而該方法是一個同步方法趣席,存在競爭關(guān)系〈己看一波運(yùn)行結(jié)果:
顯然并非每個線程都公平的進(jìn)入同步方法宣肚,那么我們需要通過鎖的方式進(jìn)行改進(jìn)
我們先來看一下sum.misc下的Lock類:
public class Lock {
private boolean locked = false;
public Lock() { }
public final synchronized void lock() throws InterruptedException {
while(this.locked) {
this.wait();
}
this.locked = true;
}
public final synchronized void unlock() {
this.locked = false;
this.notifyAll();
}
}
這是一個最簡單的鎖實(shí)現(xiàn),locked防止信號丟失和假喚醒悠栓,如果基于這個鎖對Say進(jìn)行改造霉涨,那么結(jié)果并不會有任何改變,因?yàn)閚otify方法不保證喚醒順序惭适。
可以看到笙瑟,所有的線程均在lock對象上進(jìn)行wait操作,因此lock在notify的時候不能保證喚醒的順序癞志,如果我們需要公平往枷,那么所有的線程應(yīng)該擁有獨(dú)立的等待對象,那么lock就可以通過喚醒各線程的等待對象以保證公平凄杯〈斫啵基于這種樸素的思想,考慮設(shè)置一個隊(duì)列戒突,將每個線程的等待對象存儲起來:
public class FairLock{
//鎖是否被占用
private boolean isLocked=false;
//占用線程
private Thread lockingThread=null;
private List<WaitObject> queue=new ArrayList<WaitObject>();
//這個lock不用同步屯碴,因?yàn)樾枰鄠€線程都能進(jìn)入該方法
public void lock(){
WaitObject waitObject=new WaitObject();
synchronized(this){
queue.add(waitObject);
}
boolean isLockedForThisThread=true;
//判斷是否需要等待
while(isLockedForThisThread){
//判斷條件,如果已經(jīng)上鎖妖谴,或者隊(duì)首的等待對象不是自身的等待對象
synchronized(this){
isLockedForThisThread=isLocked||queue.get(0)!=waitObject;
if(!isLockedForThisThread){
//該線程無需等待窿锉,可以擁有鎖
isLocked=true;
queue.remove(waitObject);
lockingThread=Thread.currentThread();
return;
}
}
//否則,該線程需要等待
try{
waitObject.doWait();
}catch(InterruptedException e){
synchronized(this){
queue.remove(waitObject);
}
}
}
}
public synchronized void unlock(){
isLocked=false;
lockingThread=null;
if(queue.size()>0){
//看是否還有線程在等待
queue.get(0).doNotify();
}
}
}
WaitObject其實(shí)與之前的Lock結(jié)構(gòu)幾乎一致膝舅,就不在給出嗡载。關(guān)于isLockedForThisThread的作用,我的理解是為了判斷當(dāng)前線程是否需要等待仍稀,此處用while循環(huán)的作用也是當(dāng)該線程被喚醒的時候需要再次進(jìn)入循環(huán)洼滚,刪除掉當(dāng)前的等待對象,并標(biāo)記當(dāng)前持有鎖的線程技潘。
下面給出運(yùn)行結(jié)果圖:
當(dāng)然遥巴,上述只是一個簡單的小例子,關(guān)于java庫中對公平鎖的實(shí)現(xiàn)以及其他內(nèi)容享幽,以后的文章再探討铲掐。
注:本文的fairlock代碼參考Jakob Jenkov的Starvation and Fairness一節(jié)