JAVA之阻塞隊列接口結構和實現類

目錄

阻塞隊列

阻塞隊列的核心方法:

阻塞隊列的種類分析:

題目:一個初始值爲零的變量,兩個線程對其交替操作,一個加1一個減1

題目:線程通信之順序調用 ReentrantLock,實現A->B->C三個線程順序執行

題目:線程通信之生產者消費者阻塞 BlockingQueue隊列版


阻塞隊列

首先它是一個隊列,而一個阻塞隊列在數據結構中所起的作用大致如下圖:

當阻塞隊列是空時,從隊列中獲取元素的操作將會被阻塞。

當阻塞隊列是滿時,往隊列中添加元素的操作將會被阻塞。

在多線程領域:所謂阻塞,在某些情況下會掛起線程(即阻塞),一旦條件滿足,被掛起的線程又會自動被喚醒

爲什麼需要BlockingQueue

好處是:我們不需要關心什麼時候需要阻塞線程,什麼時候需要喚醒線程,因爲這一切BlockingQueue都給你一手包辦了。

在concurrent包發佈以前,在多線程環境下,我們每個程序員都必須去自己控制這些細節,尤其還要兼顧效率和線程安全,而這會給我們的程序帶來不小的複雜度。

阻塞隊列的核心方法:

拋出異常

當阻塞隊列滿時,再往隊列裏add插入元素會拋出 java.lang.IllegalStateException: Queue full

當阻塞隊列空時,在往隊列裏remove移除元素會拋出 java.util.NoSuchElementException

特殊值

插入方法,成功true 失敗false

移除方法,成功返回出隊列的元素,隊列裏沒有就返回null

一直阻塞

當隊列滿時,生產者線程繼續往隊列裏put元素,隊列會一直阻塞生產線程直到put數據or響應中斷退出

當隊列空時,消費者試圖從隊列裏take元素,隊列會一直阻塞消費者線程直到隊列可用

超時退出 當阻塞隊列滿時,隊列會阻塞生產者一定時間,超過限時後生產者線程會退出
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
// 拋出異常
blockingQueue.add("a");// 添加
blockingQueue.remove();// 刪除
blockingQueue.remove("a");// 刪除
blockingQueue.element();// 檢查頭部元素

// 阻塞
blockingQueue.put("b");// 添加
blockingQueue.take();// 刪除

// 特殊值
blockingQueue.offer("c");// 添加
blockingQueue.poll();// 刪除
blockingQueue.offer("d", 3, TimeUnit.SECONDS);
blockingQueue.poll(3, TimeUnit.SECONDS);
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();

new Thread(() -> {
    try {
        System.out.println(Thread.currentThread().getName() + "\tput 1");
        blockingQueue.put("a");
        System.out.println(Thread.currentThread().getName() + "\tput 2");
        blockingQueue.put("b");
        System.out.println(Thread.currentThread().getName() + "\tput 3");
        blockingQueue.put("c");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}, "AAA").start();

new Thread(() -> {
    try {
        try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
        try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
        try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}, "BBB").start();

阻塞隊列的種類分析:

  1. ArrayBlockingQueue:由數組結構組成的有界阻塞隊列。
  2. LinkedBlockingQueue:由鏈表結構組成的有界阻塞隊列。(默認大小爲:Integer.MAX_VALUE)
  3. priorityBlockingQueue:支持優先級排序的無界阻塞隊列。
  4. DelayQueue:使用優先級隊列實現的延遲無界阻塞隊列。
  5. SynchronousQueue:不存儲元素的阻塞隊列,也即單個元素的隊列。
  6. LinkedTransferQueue:由鏈表結構組成的無界阻塞隊列。
  7. LinkedBlockingDeque:由鏈表結構組成的雙向阻塞隊列。

初始值爲0的一個變量,兩個線程對其交替操作,一個加1一個減1,來5輪

1、高併發 線程操作資源類

2、判斷幹活、喚醒通知

3、嚴防多線程併發狀態下虛假喚醒

題目:一個初始值爲零的變量,兩個線程對其交替操作,一個加1一個減1

*  1、  線程      操作(方法)      資源類
*  2、  判斷      幹活      通知
*  3、  防止虛假喚醒機制

PS:多線程爲什麼要用while判斷? 2個線程的情況用 if 運行正確,擴展到4個線程以上用 if 判斷會出現虛假喚醒

class ShareData{
    private int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void incremenet()throws Exception{
        lock.lock();
        try {
            // 1    判斷
            while(number != 0){
                // 等待,不能生產
                condition.await();
            }
            // 2、幹活
            number++;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            // 3、 通知喚醒
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void decremenet()throws Exception{
        lock.lock();
        try {
            // 1    判斷
            while(number == 0){
                // 等待,不能生產
                condition.await();
            }
            // 2、幹活
            number--;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            // 3、 通知喚醒
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
/**
 * 題目:一個初始值爲零的變量,兩個線程對其交替操作,一個加1一個減1
 *  1、  線程      操作(方法)      資源類
 *  2、  判斷      幹活      通知
 *  3、  防止虛假喚醒機制
 */
public class BlockingQueueDemo {

    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    shareData.incremenet();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();
        
        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    shareData.decremenet();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();
    }
}

 

題目:線程通信之順序調用 ReentrantLock,實現A->B->C三個線程順序執行


/**
 * 多線程之間順序調用,實現A->B->C三個線程啓動,要求如下;
 * AA打印5次,BB打印10次,CC打印15次
 * 5循環10次
 */

class ShareResource{
    private int number = 1; // A:1 B:2 C:3
    private Lock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void print5(){
        lock.lock();
        try {
            // 1、判斷
            while (number != 1){
                c1.await();
            }
            // 2、幹活
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            // 3、通知
            number = 2; // 修改標誌位
            c2.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void print10(){
        lock.lock();
        try {
            // 1、判斷
            while (number != 2){
                c2.await();
            }
            // 2、幹活
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            // 3、通知
            number = 3; // 修改標誌位
            c3.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void print15(){
        lock.lock();
        try {
            // 1、判斷
            while (number != 3){
                c3.await();
            }
            // 2、幹活
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            // 3、通知
            number = 1; // 修改標誌位
            c1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
public class SyncAndReentrantLockDemo {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareResource.print5();
            }
        },"AA").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareResource.print10();
            }
        },"BB").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareResource.print15();
            }
        },"CC").start();
    }
}

題目:線程通信之生產者消費者阻塞 BlockingQueue隊列版

class MyResource{
    private volatile boolean FLAG = true;   // 默認開啓,進行生產+消費
    private AtomicInteger atomicInteger = new AtomicInteger();
    BlockingQueue<String> blockingQueue = null;

    // 構造注入方法
    public MyResource(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
        System.out.println(blockingQueue.getClass().getName());
    }

    public void myProd()throws Exception{
        String data = null;
        boolean retValue = false;
        while(FLAG){
            data = atomicInteger.incrementAndGet() + "";
            retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
            if(retValue){
                System.out.println(Thread.currentThread().getName() + "\t插入隊列" + data +"成功");
            }else{
                System.out.println(Thread.currentThread().getName() + "\t插入隊列" + data +"失敗");
            }
            try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
        }
        System.out.println(Thread.currentThread().getName() + "\t大老闆叫停,表示flag=false,生產動作結束");
    }

    public void myConsumer()throws Exception{
        while (FLAG){
            String result = blockingQueue.poll(2, TimeUnit.SECONDS);
            if(null == result || result.equalsIgnoreCase("")){
                FLAG = false;
                System.out.println(Thread.currentThread().getName() + "\t超過2秒鐘沒有取到蛋糕,消費退出");
                System.out.println();
                System.out.println();
                return;
            }
            System.out.println(Thread.currentThread().getName() + "\t消費蛋糕" + result + "成功");
        }
    }

    public void stop()throws Exception{
        this.FLAG = false;
    }
}
/**
 * 線程通信之生產者消費者阻塞隊列版
 */
public class ProdConsumer_BlockQueueDemo {

    public static void main(String[] args) {
        MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t生產線程啓動");
            try {
                myResource.myProd();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Prod").start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t消費線程啓動");
            try {
                myResource.myConsumer();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Consumer").start();

        try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println();
        System.out.println();
        System.out.println("5秒鐘到,大老闆mian線程叫停,活動結束");

        try {
            myResource.stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

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