java中阻塞隊列的實現方式

學習阻塞隊列之前需要掌握線程的wait(),notify()以及notifyAll()的相關知識!

①wait()爲什麼要放置在while循環中,而不是使用if進行判斷

②notify()和notifyAll()有什麼區別?爲什麼有時候認爲該使用notify()的地方卻使用了notifyAll()?

上面這兩個問題是我在學習多線程時產生的疑問,我相信也會是大多數人的疑問

下面是相關介紹:

JVM會爲一個使用內部鎖的對象維護兩個集合EntrySet(鎖池)和WaitSet(等待池)

  • ①EntrySet:線程A持有對象鎖,其它線程若想獲得該對象鎖,只能進入EntrySet,並且處於線程的BLOCKED狀態
  • ②WaitSet:線程A調用了wait()方法後會釋放該對象的鎖,進入WaitSet,並且處於線程的WAITING狀態

線程B獲得對象鎖有兩個條件:

  • ①對象鎖已經被釋放(持有鎖的線程A執行完了synchronized代碼塊,或者線程A調用了wait方法)
  • ②線程B處於RUNNABLE狀態

兩個集合中的線程在什麼條件下可以轉變爲RUNNABLE狀態呢?

  • ①EntrySet:當對象鎖被釋放後,JVM會喚醒EntrySet中某一線程,此線程的狀態就由BLOCKED轉爲RUNNABLE
  • ②WaitSet:
  •    1>當對象的notify()方法被調用時,JVM會喚醒WaitSet中的某一線程,此線程狀態由WAITING轉爲RUNNABLE
  •    2>當對象的notifyAll()方法被調用時,WaitSet中的全部線程都被喚醒,這些線程狀態由WAITING轉爲RUNNABLE
  •    3>喚醒後的線程全部進入EntrySet中

當對象鎖被釋放後,所有處於RUNNABLE狀態的線程會共同去競爭該對象鎖,競爭失敗的線程會在EntrySet中等待下一次機會

疑問解惑:

①wait()爲什麼要放置在while循環中,而不是使用if進行判斷?

那生產者消費者爲例,當前無可消費時,調用消費者的wait(),使其進入等待狀態,但是消費者線程無法確定其它線程什麼時候會被notify.多生產者多消費者的情況下這是無法預知並掌控的,極有可能發生的是喚醒生產者的是另一個生產者,喚醒消費者的是另一個消費者,如果將wait()放在if判斷中就會造成過度生產過度消費的情況,典型如IndexOutOfBoundsException的異常。所以所有的java書籍都會建議開發者永遠都要把wait()放到循環語句裏面

②notify()和notifyAll()有什麼區別?爲什麼有時候認爲該使用notify()的地方卻使用了notifyAll()?

我們應該儘量使用notifyAll()的原因就是,notify()非常容易導致死鎖。當然notifyAll並不一定都是優點,畢竟一次性將Wait Set中的線程都喚醒是一筆不菲的開銷,如果你能handle你的線程調度,那麼使用notify()也是有好處的

阻塞隊列與普通隊列的區別:

①當隊列爲空時,從隊列中獲取元素的操作將被阻塞

②當隊列爲滿時,向隊列中添加元素的操作將被阻塞

一.阻塞隊列的wait()和notify()方式實現

package blockqueue;

import java.util.LinkedList;
import java.util.List;

/**
 * 第一種實現阻塞隊列的方式是使用wait()和notify()
 */
public class BlockingQueueModeFir {
    //使用list實現阻塞隊列
    private List queue = new LinkedList();
    //有界隊列上限
    private int limit = 10;
    //阻塞隊列構造
    public BlockingQueueModeFir(int limit) {
        this.limit = limit;//指定隊列上限
    }

    /**
     * 向阻塞隊列中添加元素
     * @param item
     * @throws InterruptedException
     */
    public synchronized void enqueue(Object item) throws InterruptedException {
        //阻塞隊列中元素數已達上限,則執行添加元素的線程阻塞
        while (this.queue.size() == this.limit){
            wait();
        }
        //阻塞隊列已被清空時,喚醒所有線程
        if (this.queue.size() == 0){
            notifyAll();
        }
        //阻塞隊列存在元素且元素數量未達上限,向隊列中添加元素
        this.queue.add(item);
    }

    public synchronized Object dequeue() throws InterruptedException {
        //當阻塞隊列爲空時,無法取出元素,執行等待
        while (this.queue.size() == 0){
            wait();
        }
        //當阻塞隊列已滿時,喚醒所有線程
        if (this.queue.size() == this.limit){
            notifyAll();
        }
        //阻塞隊列存在元素且元素數量未達上限,取出隊列中的元素
        return this.queue.remove(0);
    }
}

二.阻塞隊列的併發式實現

package blockqueue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * 第二種實現阻塞隊列的方式是併發式
 */
public class BlockingQueueModeSec {
    public static void main(String[] args) {
        //緩衝區允許存放3個元素
        final BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3);

        //開啓兩個線程向隊列中存入數據
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                while(true) {
                    try {
                        Thread.sleep((long) (Math.random()*1000));
                        System.out.println(Thread.currentThread().getName() + "準備放數據"
                                + (queue.size() == 3?"..隊列已滿,正在等待":"..."));
                        queue.put(1);
                        System.out.println(Thread.currentThread().getName() + "存入數據,"
                                + "隊列目前有" + queue.size() + "個數據");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }).start();
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    try {
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName() + "準備取數據"
                                + (queue.size() == 0?"..隊列已空,正在等待":"..."));
                        queue.take();
                        System.out.println(Thread.currentThread().getName() + "取出數據,"
                                + "隊列目前有" + queue.size() + "個數據");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            }
        }).start();
    }
}

 

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