第二十二講 多執行緒——多執行緒間的通訊——多個生產者和消費者的升級解決方案
這裡我也是採用循序漸進的方式來講解JDK1.5版本中提供的多執行緒升級解決方案,希望能更加容易地讓大家接受。 為了解決多生產多消費的效率低下這一核心問題,在這兒我就告訴大家勢必要用到JDK1.5中java.util.concurrent.locks包中的物件,其中就有Lock介面。須知同步程式碼塊或者同步函式的鎖操作是隱式的,而JDK1.5中出現的Lock介面按照面向物件的思想,將鎖單獨封裝成了一個物件,並提供了對鎖的顯示操作,諸如以下操作:
- lock():獲取鎖;
- unlock():釋放鎖。
總而言之,Lock介面的出現比synchronized有了更多的操作,它就是同步的替代。所以我們首先將多個生產者和消費者案例中的同步更換為Lock介面的形式,程式碼如下,供大家參考。
import java.util.concurrent.locks.*;
// 描述資源
class Res
{
private String name; // 資源名稱
private int count = 1; // 資源編號
// 建立新Lock
private Lock lock = new ReentrantLock();
// 定義標記。
private boolean flag;
// 提供了給商品賦值的方法
public void set(String name) //
{
// 獲取鎖。
lock.lock ();
try
{
while (flag) // 判斷標記為true,就執行wait()等待。為false,就生產。
try{this.wait();}catch(InterruptedException e){ } // t0(等待)、t1(等待)
this.name = name + "--" + count; // 麵包1、麵包2、麵包3
count++; // 4
System.out.println(Thread.currentThread ().getName() + "...生產者..." + this.name); // t0、麵包1 t0、麵包2、t1、麵包3
// 生產完畢,將標記改為true。
flag = true;
// 喚醒所有等待執行緒(包括本方執行緒)。
this.notifyAll();
}
finally
{
// 釋放鎖。
lock.unlock();
}
}
// 提供獲取一個商品的方法
public void get() // t3
{
lock.lock();
try
{
while (!flag)
try{this.wait();}catch(InterruptedException e){ } // t2(等待)、t3(等待)
System.out.println(Thread.currentThread().getName() + ".......消費者......." + this.name); // t2、麵包1
// 將標記改為false。
flag = false;
// 喚醒所有等待執行緒(包括本方執行緒)。
this.notifyAll();
}
finally
{
lock.unlock();
}
}
}
// 生成者
class Producer implements Runnable
{
private Res r;
Producer(Res r)
{
this.r = r;
}
public void run()
{
while (true)
r.set("麵包");
}
}
// 消費者
class Consumer implements Runnable
{
private Res r;
Consumer(Res r)
{
this.r = r;
}
public void run()
{
while (true)
r.get();
}
}
class NewProducerConsumerDemo
{
public static void main(String[] args)
{
// 1、建立資源
Res r = new Res();
// 2、建立兩個任務。
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
// 3、建立執行緒
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()沒有了同步區域,沒有了所屬的同步鎖。同步升級了,其中鎖已經不再是任意物件了,而是Lock型別的物件,那麼和任意物件繫結的監視器方法,是不是也升級了,有了專門和Lock型別鎖的繫結的監視器方法呢?通過查閱API幫助文件,可知Condition介面替代了Object類中的監視器方法。以前是監視器方法封裝到了每一個物件中,現在是將監視器方法封裝到了Condition物件中,方法名為await()、signal()、signalAll()。那麼問題又來了,監視器物件Condition如何和Lock繫結呢?答案是可以通過Lock介面的newCondition()方法完成。 按照上面的分析,我將多個生產者和消費者案例的程式碼修改如下:
import java.util.concurrent.locks.*;
// 描述資源
class Res
{
private String name; // 資源名稱
private int count = 1; // 資源編號
// 建立新Lock
private Lock lock = new ReentrantLock();
// 建立和Lock介面繫結的監視器物件
private Condition con = lock.newCondition();
// 定義標記。
private boolean flag;
// 提供了給商品賦值的方法
public void set(String name) //
{
// 獲取鎖。
lock.lock();
try
{
while (flag) // 判斷標記為true,就執行wait()等待。為false,就生產。
try{con.await();}catch(InterruptedException e){ } // t0(等待)、t1(等待)
this.name = name + "--" + count; // 麵包1、麵包2、麵包3
count++; // 4
System.out.println(Thread.currentThread().getName() + "...生產者..." + this.name); // t0、麵包1 t0、麵包2、t1、麵包3
// 生產完畢,將標記改為true。
flag = true;
// 喚醒所有等待執行緒(包括本方執行緒)。
// this.notifyAll();
con.signalAll();
}
finally
{
// 釋放鎖。
lock.unlock();
}
}
// 提供獲取一個商品的方法
public void get() // t3
{
lock.lock();
try
{
while (!flag)
try{con.await();}catch(InterruptedException e){ } // t2(等待)、t3(等待)
System.out.println(Thread.currentThread().getName() + ".......消費者......." + this.name); // t2、麵包1
// 將標記改為false。
flag = false;
// 喚醒所有等待執行緒(包括本方執行緒)。
// this.notifyAll();
con.signalAll();
}
finally
{
lock.unlock();
}
}
}
// 生成者
class Producer implements Runnable
{
private Res r;
Producer(Res r)
{
this.r = r;
}
public void run()
{
while (true)
r.set("麵包");
}
}
// 消費者
class Consumer implements Runnable
{
private Res r;
Consumer(Res r)
{
this.r = r;
}
public void run()
{
while (true)
r.get();
}
}
class NewProducerConsumerDemo
{
public static void main(String[] args)
{
// 1、建立資源
Res r = new Res();
// 2、建立兩個任務。
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
// 3、建立執行緒
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();
}
}
改完,執行以上程式,雖然執行沒問題,但是問題依舊,一樣喚醒了本方執行緒,效率仍舊低下!接下來我們就要看看如何真正解決多生產多消費的低效率問題了。 經過上面一步一步地分析,到這裡,我們差不多可以寫出真正解決多生產多消費效率低問題的程式了,現將程式碼貼出如下,以供大家參考。
import java.util.concurrent.locks.*;
// 描述資源
class Res
{
private String name; // 資源名稱
private int count = 1; // 資源編號
// 建立新Lock
private Lock lock = new ReentrantLock();
// 建立和Lock介面繫結的監視器物件。建立兩個。
// 生產者監視器
private Condition producer_con = lock.newCondition();
// 消費者監視器
private Condition consumer_con = lock.newCondition();
// 定義標記。
private boolean flag;
// 提供了給商品賦值的方法
public void set(String name) //
{
// 獲取鎖。
lock.lock();
try
{
while (flag) // 判斷標記為true,就執行wait()等待。為false,就生產。
try{producer_con.await();}catch(InterruptedException e){ } // t0(等待)、t1(等待)
this.name = name + "--" + count; // 麵包1、麵包2、麵包3
count++; // 4
System.out.println(Thread.currentThread().getName() + "...生產者..." + this.name); // t0、麵包1 t0、麵包2、t1、麵包3
// 生產完畢,將標記改為true。
flag = true;
// 生產完畢,應該喚醒一個消費者來消費。
consumer_con.signal();
}
finally
{
// 釋放鎖。
lock.unlock();
}
}
// 提供獲取一個商品的方法
public void get() // t3
{
lock.lock();
try
{
while (!flag)
try{consumer_con.await();}catch(InterruptedException e){ } // t2(等待)、t3(等待)
System.out.println(Thread.currentThread().getName() + ".......消費者......." + this.name); // t2、麵包1
// 將標記改為false。
flag = false;
// 消費完後,應該喚醒一個生產者來生產。
producer_con.signal();
}
finally
{
lock.unlock();
}
}
}
// 生成者
class Producer implements Runnable
{
private Res r;
Producer(Res r)
{
this.r = r;
}
public void run()
{
while (true)
r.set("麵包");
}
}
// 消費者
class Consumer implements Runnable
{
private Res r;
Consumer(Res r)
{
this.r = r;
}
public void run()
{
while (true)
r.get();
}
}
class NewProducerConsumerDemo
{
public static void main(String[] args)
{
// 1、建立資源
Res r = new Res();
// 2、建立兩個任務。
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
// 3、建立執行緒
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();
}
}