Java 多線程編程 生產者 消費者模式 (ReentrantLock鎖 / Condition的等待通知機制 await等待 signal喚醒)

synchronized和 ReentrantLock的相同點

  • 兩種鎖都是獨佔鎖, 只允許線程互斥的訪問臨界區
  • 兩種鎖都可以重入, 在一個線程可以重複獲取多次鎖
    兩者區別是 synchronized重入不用考慮解鎖相關問題, 而 ReentrantLock必須確保重複獲取鎖的次數和釋放鎖的次數相同, 否者相關鎖無法解

synchronized和 ReentrantLock的區別

  • synchronized是加解鎖是隱式的, ReentrantLock是手動加解鎖
  • synchronized是通過 Object的 wait和 notify方法來實現線程間的等待通知機制, 但是 ReentrantLock是通過 Condition接口來實現了等待通知機制
  • ReentrantLock支持響應中斷, 出現死鎖現象可以通過 lockInterruptibly()方法響應中斷. 但是 synchronized出現死鎖時會無限等待,
  • ReentrantLock支持公平鎖, 被鎖後等了最長時間的線程將會優先解鎖, 而默認是非公平鎖就是隨機的 synchronized只支持非公平鎖
    • 非公平鎖性能優於公平鎖, 但是公平鎖能防止線程飢餓現象發生. 創建方式: new ReentrantLock(true) 參數 true是創建公平鎖, false或無參是非公平鎖

1. 消息類

  • 消息數超過存放最大數, 則生產者線程阻塞(等待消費者消費後喚醒)
  • 消息數等於空, 則消費者線程阻塞(等待生產者生產後喚醒)

public class Message {
    /** 當前消息數量*/
    private int count = 0;
    /** 信息存放最大限數*/
    private int maximum = 20;
    /** 重入鎖*/
    private Lock lock;
    /** 生產者鎖控制器*/
    private Condition producerCondition;
    /** 消費者鎖控制器*/
    private Condition consumerCondition;

    public Message() {}

    public Message(final Lock lock, final Condition producerCondition, final Condition consumerCondition) {
        this.lock = lock;
        this.producerCondition = producerCondition;
        this.consumerCondition = consumerCondition;
    }

    /**
     * 生產消息
     * */
    public void set() {
        /** 獲取鎖*/
        lock.lock();
        try {
            if (count <= maximum) {
                /** 生產一個消息*/
                System.out.println("生產者 線程" + Thread.currentThread().getName() + "生產了一個消息, 當前有" + (++count) + "個消息");
                /** 喚醒等待的消費者*/
                consumerCondition.signal();
            } else {
                try {
                    /**
                     * 如果當前消息大於 maximum信息最大數
                     * 生產者進入睡眠/等待狀態
                     * */
                    producerCondition.await();
                    System.out.println("生產者 線程" + Thread.currentThread().getName() + "進入睡眠");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            /** 釋放鎖*/
            lock.unlock();
        }
    }

    /**
     * 消費消息
     * */
    public void get() {
        /** 獲取鎖*/
        lock.lock();
        try {
            if (count > 0) {
                /** 消費一個消息*/
                System.out.println("消費者 線程" + Thread.currentThread().getName() + "消費了一個消息, 當前有" + (--count) + "個消息");
                /** 喚醒等待的生產者*/
                producerCondition.signal();
            } else {
                try {
                    /** 如果沒有消息, 消費者進入睡眠/等待狀態*/
                    consumerCondition.await();
                    System.out.println("消費者 線程" + Thread.currentThread().getName() + "進入睡眠");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            /** 釋放鎖*/
            lock.unlock();
        }
    }

}

2. 生產者類


public class Producer implements Runnable {
    private Message message;
    public Producer(Message message) {
        this.message = message;
    }

    @Override
    public void run() {
        while(true) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            message.set();
        }
    }

}

3. 消費者類


public class Consumer implements Runnable {
    private Message message;
    public Consumer(Message message) {
        this.message = message;
    }

    @Override
    public void run() {
        while(true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            message.get();
        }
    }

}

Runnable是接口, Thread是 Runnable的實現類, 由於 Java對多接口實現比較靈活, 開發多線程時建議實現 Runnable接口. 啓動時用 Thread的 start(). 例: new Thread().start()

4. App.java


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class App {
    public static void main(String[] args) {
        /** 重入鎖*/
        final Lock lock = new ReentrantLock();
        /** 生產者鎖控制器*/
        final Condition producerCondition = lock.newCondition();
        /** 消費者鎖控制器*/
        final Condition consumerCondition = lock.newCondition();
        final Message message = new Message(lock, producerCondition, consumerCondition);
        /** 建幾個生產線程*/
        new Thread(new Producer(message)).start();
        new Thread(new Producer(message)).start();
        new Thread(new Producer(message)).start();
        /** 建幾個消費線程*/
        new Thread(new Consumer(message)).start();
        new Thread(new Consumer(message)).start();
        new Thread(new Consumer(message)).start();
        new Thread(new Consumer(message)).start();
    }

}

如果您覺得有幫助,歡迎點贊哦 ~ 謝謝!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章