多線程最爲常見的應用案例——生產者消費者問題。
生產和消費同時進行,需要多線程。但是,執行的任務卻不相同,處理的資源卻是相同的,線程間的通信。
1. 描述一下資源。
2. 描述生產者,因爲具備着自己的任務。
3. 描述消費者,因爲具備着自己的任務。
public class Resource {
private String name;
private int count=1;
//定義標記
private boolean flag=false;
//1. 提供設置的方法
public synchronized void set(String name)
{
if(flag)
try{wait();}catch(InterruptedException e) { }
//給成員變量賦值並加上編號
this.name=name+count;
count++;
System.out.println(Thread.currentThread().getName()+"..."+"生產"+this.name);
//標記改爲true
flag=true;
//喚醒消費者
notify();
}
public synchronized void out()
{
if(!flag)
try{wait();}catch(InterruptedException e) { }
System.out.println(Thread.currentThread().getName()+"..."+"消費"+this.name);
flag=false;
notify();
}
}
//描述生產者
public class Production implements Runnable {
private Resource r;
Production(Resource r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.set("麪包");
}
}
}
//描述消費者
public class Consume implements Runnable{
private Resource r;
public Consume(Resource r) {
this.r=r;
}
public void run()
{
while(true)
{
r.out();
}
}
public class ThraedDemo {
public static void main(String[] args) {
//創建資源對象
Resource r=new Resource();
//創建線程任務
Production p=new Production(r);
Consume c=new Consume(r);
//創建線程
Thread t1=new Thread(p);
Thread t2=new Thread(c);
//開啓線程
t1.start();
t2.start();
}
}
結果:
......
Thread-0...生產麪包82372
Thread-1...消費麪包82372
Thread-0...生產麪包82373
Thread-1...消費麪包82373
Thread-0...生產麪包82374
Thread-1...消費麪包82374
Thread-0...生產麪包82375
Thread-1...消費麪包82375
......
當我們使其變成多消費多生產時,又會發生錯誤。
其他的類不變,就加入了消費者和生產者。
public class ThraedDemo {
public static void main(String[] args) {
//創建資源對象
Resource r=new Resource();
//創建線程任務
Production p1=new Production(r);
Consume c1=new Consume(r);
Production p2=new Production(r);
Consume c2=new Consume(r);
//創建線程
Thread t1=new Thread(p1);
Thread t2=new Thread(c1);
Thread t3=new Thread(p2);
Thread t4=new Thread(c2);
//開啓線程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
會出現這樣的結果:
Thread-0...生產麪包82372//沒有被消費
Thread-1...生產麪包82373
Thread-3...消費麪包82373
被喚醒的線程沒有判斷標記,造成問題1的產生。
解決:只要讓被喚醒的線程必須判斷標記就可以了,將if判斷標記的方式改爲while判斷標記。記住,多生產多消費,必須while判斷條件。
public class Resource {
private String name;
private int count=1;
//定義標記
private boolean flag=false;
//1. 提供設置的方法
public synchronized void set(String name)
{
while(flag)
try{wait();}catch(InterruptedException e) { }
//給成員變量賦值並加上編號
this.name=name+count;
count++;
System.out.println(Thread.currentThread().getName()+"..."+"生產"+this.name);
//標記改爲true
flag=true;
//喚醒消費者
notify();
}
public synchronized void out()
{
while(!flag)
try{wait();}catch(InterruptedException e) { }
System.out.println(Thread.currentThread().getName()+"..."+"消費"+this.name);
flag=false;
notify();
}
}
有出現問題2:發現while判斷後,死鎖了。
原因:生產喚醒了線程池中生產方的線程,本方喚醒了本方。
解決:希望本方法一定要喚醒對方,沒有對應的方法,所以只能喚醒所有。將notify改成notifyAll。
但是,這樣效率低了。
public class Resource {
private String name;
private int count=1;
//定義標記
private boolean flag=false;
//1. 提供設置的方法
public synchronized void set(String name)
{
while(flag)
try{wait();}catch(InterruptedException e) { }
//給成員變量賦值並加上編號
this.name=name+count;
count++;
System.out.println(Thread.currentThread().getName()+"..."+"生產"+this.name);
//標記改爲true
flag=true;
//喚醒消費者
notifyAll();
}
public synchronized void out()
{
while(!flag)
try{wait();}catch(InterruptedException e) { }
System.out.println(Thread.currentThread().getName()+"..."+"消費"+this.name);
flag=false;
notifyAll();
}
}
希望本方可以喚醒對方的一個:
import java.util.concurrent.locks.*;
public class Resource {
private String name;
private int count=1;
//定義一個鎖對象
private Lock lock=new ReentrantLock();
//獲取鎖上的Condition對象,爲了解決喚醒對方的問題,可以一個鎖創建兩個監視器對象。
private Condition con=lock.newCondition();//負責消費
private Condition pro=lock.newCondition();//負責生產
//定義標記
private boolean flag=false;
public void set(String name)
{
//獲取鎖
lock.lock();
try{
while(flag)
try{pro.await();}catch(InterruptedException e) { }
//給成員變量賦值並加上編號
this.name=name+count;
count++;
System.out.println(Thread.currentThread().getName()+"..."+"生產"+this.name);
//標記改爲true
flag=true;
con.signal();
}
finally {
lock.unlock();//釋放鎖。一定要執行,所以放在finally裏
}
}
public void out()
{
lock.lock();
try {
while(!flag)
try{con.await();}catch(InterruptedException e) { }
System.out.println(Thread.currentThread().getName()+"..."+"消費"+this.name);
flag=false;
pro.signal();
}finally{
lock.unlock();
}
}
}