1. 程式人生 > >第二十二講 多執行緒——多執行緒間的通訊——多個生產者和消費者的升級解決方案

第二十二講 多執行緒——多執行緒間的通訊——多個生產者和消費者的升級解決方案

這裡我也是採用循序漸進的方式來講解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();
    }
}