多生產者多消費者問題(Lock介面、Condition介面)
在多生產者多消費者問題中,我們通過while判斷和notifyAll()全喚醒方法解決了問題,但是notifyAll()同時也帶來了弊端,它要喚醒所有的被等待的執行緒,意味著既喚醒了對方,也喚醒了本方,在喚醒本方執行緒後還要不斷判斷標記,就降低了程式的效率。我們希望只喚醒對方的執行緒。
在jdk進行升級後,就解決了類似的問題。
Lock(介面)實現提供了比使用synchronized方法和語句可獲得的更廣泛的鎖定操作。
synchronized同步程式碼塊格式:
注:同步程式碼塊,對於鎖的操作是隱式的。Object obj=new Object(); void show() { synchronized(obj) { //需要被同步的程式碼 } }
jdk1.5以後將同步和鎖封裝成了物件,並將操作鎖的隱式方式定義到了該物件中,將隱式動作變成了顯示動作。
下面採用jdk新特性實現Lock介面自定義鎖實現同步:
Lock lock=new ReentrantLock(); //ReentrantLock類是Lock類的子類,實現Lock介面
void show()
{
try
{
lock.lock(); //獲取鎖
//程式碼
}
finally
{
lock.unlock(); //釋放鎖
}
}
我們將多生產者多消費者的例子(多生產者多消費者問題)進行改寫:
import java.util.concurrent.locks.*; //Lock是其他包中的類,使用時需要匯入 //資源 class Resource { private String name; private int count=1; private boolean flag=false; //設定標誌位值為false,表示沒有被生產 Lock lock=new ReentrantLock(); //實現一個介面 public void set(String name) { lock.lock(); try { while(flag) //while保證在此處被喚醒的執行緒重新判斷標誌位 try{this.wait();} catch(InterruptedException e){} this.name=name+count; count++; System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name); flag=true; notifyAll(); //保證能夠喚醒消費者執行緒 } finally { lock.unlock(); } } public synchronized void out() { lock.lock(); try { while(!flag) try{this.wait();} catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...消費者......"+this.name); flag=false; notifyAll(); //保證能夠喚醒生產者執行緒 } finally { lock.unlock(); } } } //生產者 class Producer implements Runnable { private Resource r; Producer(Resource r) { this.r=r; } public void run() { while(true) { r.set("烤鴨"); } } } //消費者 class Consumer implements Runnable { private Resource r; Consumer(Resource r) { this.r=r; } public void run() { while(true) { r.out(); } } } class ProducerConsumerDemo { public static void main(String[] args) { Resource r=new Resource(); Producer pro=new Producer(r); Consumer con=new Consumer(r); //2個生產者,2個消費者 Thread t0=new Thread(pro); Thread t1=new Thread(pro); Thread t2=new Thread(con); Thread t3=new Thread(con); t0.start(); t1.start(); t2.start(); t3.start(); } }
執行結果:
但是在上例中,我們用到的鎖是lock,不再是this,而程式碼中呼叫wait()、notifyAll()方法的鎖仍是this,就會出現錯誤,在jdk新特性中,用Lock來代替synchronized,用condition來代替監視器方法(wait()、notify()、notifyAll())。
condition介面:
還是要將上例繼續進行改寫:interface Condition //Condition介面,替代Object類裡的wait()、notify()、notifyAll()方法 { await(); signal(); signalAll(); } Lock lock=new ReectrantLock(); //一個鎖上建立兩個監視器,一個鎖上就有多個監視器方法 Condition c1=lock.newCondition(); //每個監視器裡都有await()、signal()、signalAll()方法 Condition c2=lock.newCondition();
import java.util.concurrent.locks.*; //Lock是其他包中的類,使用時需要匯入
//資源
class Resource
{
private String name;
private int count=1;
private boolean flag=false; //設定標誌位值為false,表示沒有被生產
//建立一個鎖物件
Lock lock=new ReentrantLock(); //實現一個介面
//通過已有的鎖獲取該鎖上的監視器物件
Condition con=lock.newCondition(); //con是lock鎖上的一個監視器
public void set(String name)
{
lock.lock();
try
{
while(flag) //while保證在此處被喚醒的執行緒重新判斷標誌位
//try{lock.wait();} catch(InterruptedException e){}
try{con.await();} catch(InterruptedException e){}
this.name=name+count;
count++;
System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
flag=true;
//notifyAll(); //保證能夠喚醒消費者執行緒
con.signalAll();
}
finally
{
lock.unlock();
}
}
public synchronized void out()
{
lock.lock();
try
{
while(!flag)
//try{this.wait();} catch(InterruptedException e){}
try{con.await();} catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"...消費者......"+this.name);
flag=false;
//notifyAll(); //保證能夠喚醒生產者執行緒
con.signalAll();
}
finally
{
lock.unlock();
}
}
}
//生產者
class Producer implements Runnable
{
private Resource r;
Producer(Resource r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.set("烤鴨");
}
}
}
//消費者
class Consumer implements Runnable
{
private Resource r;
Consumer(Resource r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class ProducerConsumerDemo
{
public static void main(String[] args)
{
Resource r=new Resource();
Producer pro=new Producer(r);
Consumer con=new Consumer(r);
//2個生產者,2個消費者
Thread t0=new Thread(pro);
Thread t1=new Thread(pro);
Thread t2=new Thread(con);
Thread t3=new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
執行結果:
在前面我們提到希望只喚醒對方的執行緒,來提升程式執行的效率。如果程式中只有一個監視器,那個它能將生產者消費者全部wait,和生產者消費者全部喚醒。實際我們希望生產者喚醒消費者,消費者喚醒生產者,這樣我們建立兩組監視器,一組監視生產者,一組監視消費者。
import java.util.concurrent.locks.*; //Lock是其他包中的類,使用時需要匯入
//資源
class Resource
{
private String name;
private int count=1;
private boolean flag=false; //設定標誌位值為false,表示沒有被生產
//建立一個鎖物件
Lock lock=new ReentrantLock(); //實現一個介面
//通過已有的鎖獲取該鎖上的監視器物件
//Condition con=lock.newCondition(); //con是lock鎖上的一個監視器
//通過已有的鎖獲取兩組監視器,一組監視生產者,一組監視消費者
Condition producer_con=lock.newCondition(); //生產者的監視器
Condition consumer_con=lock.newCondition(); //消費者的監視器
public void set(String name)
{
lock.lock();
try
{
while(flag) //while保證在此處被喚醒的執行緒重新判斷標誌位
//try{lock.wait();} catch(InterruptedException e){}
try{producer_con.await();} catch(InterruptedException e){} //生產者執行緒等待
this.name=name+count;
count++;
System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
flag=true;
//notifyAll(); //保證能夠喚醒消費者執行緒
//con.signalAll();
consumer_con.signal(); //喚醒消費者的一個執行緒
}
finally
{
lock.unlock();
}
}
public synchronized void out()
{
lock.lock();
try
{
while(!flag)
//try{this.wait();} catch(InterruptedException e){}
try{consumer_con.await();} catch(InterruptedException e){} //消費者執行緒等待
System.out.println(Thread.currentThread().getName()+"...消費者......"+this.name);
flag=false;
//notifyAll(); //保證能夠喚醒生產者執行緒
//con.signalAll();
producer_con.signal(); //喚醒生產者的一個執行緒
}
finally
{
lock.unlock();
}
}
}
//生產者
class Producer implements Runnable
{
private Resource r;
Producer(Resource r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.set("烤鴨");
}
}
}
//消費者
class Consumer implements Runnable
{
private Resource r;
Consumer(Resource r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class ProducerConsumerDemo
{
public static void main(String[] args)
{
Resource r=new Resource();
Producer pro=new Producer(r);
Consumer con=new Consumer(r);
//2個生產者,2個消費者
Thread t0=new Thread(pro);
Thread t1=new Thread(pro);
Thread t2=new Thread(con);
Thread t3=new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
執行結果:
總結:
一:Lock介面:出現替代了同步程式碼塊或者同步函式,將同步的隱式鎖操作變成現實鎖操作,同時更為靈活,可以一個鎖上加上多組監視器。
方法:lock():獲取鎖
unlock():釋放鎖,通常需要定義在finally程式碼塊中
二:Condition介面:出現替代了Object中 的wait notify notifyAll方法,將這些監視器方法單獨進行了封裝,變成Condition監視器物件,可以任意鎖進行組合。
方法:await()
signal()
signalAll()