JUC併發編程學習(十)-阻塞隊列、同步隊列

BlockingQueue

隊列的特點:先進先出

阻塞隊列在擁有隊列的基本特徵的同時,還額外支持兩個附加操作。這兩個附加的操作支持阻塞的插入和移除方法。

  • 阻塞插入
    隊列插入元素時,當隊列空間已經使用滿了,不得不阻塞

  • 阻塞移除
    隊列中有元素,在取元素時可以移除,隊列爲空時,阻塞不能取出,等待隊列中有新的元素才能取出。

在這裏插入圖片描述

阻塞隊列常用於生產者和消費者的場景,生產者是向隊列裏添加元素的線程,消費者是從隊列裏取元素的線程。阻塞隊列就是生產者用來存放元素、消費者用來獲取元素的容器。

接口架構圖

在這裏插入圖片描述
可以看出BlockingQueue隊列和List、Set接口類似,都是繼承的Collection。說明基本集合元素操作方法應該類似。

ArrayBlockingQueue API 的使用

  • ArrayBlockingQueue 是一個有限的blocking queue,由數組支持。
  • 這個隊列排列元素FIFO(先進先出)。
  • 隊列的頭部是隊列中最長時間的元素。隊列的尾部是隊列中最短時間的元素。
  • 新元素插入隊列的尾部,隊列檢索操作獲取隊列頭部的元素。
  • 這是一個經典的“有界緩衝區”,其中固定大小的數組保存由生產者插入的元素並由消費者提取。
  • 隊列的固定大小創建後,容量無法更改。

ArrayBlockingQueue 以插入方法、移除方法、檢查隊首三個方法爲單元,形成了四組API,分別是拋出異常組、返回特殊值組、超時退出組、一直阻塞組,如下:
在這裏插入圖片描述
這4組API都有各自的業務場景。

1.拋出異常

package com.jp.studyBlockQueue;

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

/**
 * @className:
 * @PackageName: com.jp.studyBlockQueue
 * @author: youjp
 * @create: 2020-05-26 18:46
 * @description:    TODO 阻塞隊列的學習
 * @Version: 1.0
 */
public class BlockQueueDemo{

    public static void main(String[] args) {

        ArrayBlockingQueue queue=new ArrayBlockingQueue(3); //只能裝3只羊
        for (int i = 1; i <= 3; i++) {
            queue.add(""+i+"只小羊");
        }
        
        //檢測隊首queue.element()
        System.out.println(queue.element());
        queue.remove();
        System.out.println(queue.element());
        queue.remove();
        System.out.println(queue.element());
        queue.remove();

        //已經移除隊列內容,檢查隊首
        System.out.println(queue.element());
    }

}

在這裏插入圖片描述
當隊列爲空時,此時不可取出元素。此時檢查隊首拋出了沒有元素可以迭代異常。

2.返回特殊值

使用offer替代了之前的add方法,使用poll替代了元素的移除,peek用於檢查隊首。這些方法都有返回值,這些執行方法是沒有異常的。

package com.jp.studyBlockQueue;

import java.util.concurrent.ArrayBlockingQueue;

/**
 * @className:
 * @PackageName: com.jp.studyBlockQueue
 * @author: youjp
 * @create: 2020-05-26 18:46
 * @description:    TODO 阻塞隊列的學習 2:有返回值的方法
 * @Version: 1.0
 */
public class BlockQueueDemo1 {

    public static void main(String[] args) {

        ArrayBlockingQueue queue=new ArrayBlockingQueue(3);
        for (int i = 1; i <= 4; i++) {
            System.out.println( "第"+i+"只小羊入列:"+queue.offer(""+i+"只小羊"));
        }


        System.out.println("檢測到隊列首元素爲"+queue.peek());  //檢測隊首
        System.out.println("出列:第"+queue.poll());   //移除
        System.out.println("檢測到隊列首元素爲"+queue.peek());  //檢測隊首
        System.out.println("出列:第"+queue.poll());   //移除
        System.out.println("檢測到隊列首元素爲"+queue.peek());  //檢測隊首
        System.out.println("出列:第"+queue.poll());   //移除


        System.out.println("檢測到隊列首元素爲"+queue.peek());  //檢測隊首
    }



}

執行結果:
在這裏插入圖片描述

3.超時退出

package com.jp.studyBlockQueue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * @className:
 * @PackageName: com.jp.studyBlockQueue
 * @author: youjp
 * @create: 2020-05-27 19:03
 * @description:    TODO  阻塞隊列的學習 3:
 * @Version: 1.0
 */
public class BlockQueueDemo2 {

    public static void main(String[] args) {
        ArrayBlockingQueue queue=new ArrayBlockingQueue(3); //只能加3只羊
        for (int i = 1; i <= 3; i++) {
            System.out.println( "第"+i+"只小羊入列:"+queue.offer(""+i+"只小羊"));
        }
        //添加第4只羊測試
        try {
            System.out.println("---添加第4只羊測試---");
            System.out.println( "第4只小羊入列:"+queue.offer("第4只小羊嘗試入列",3,TimeUnit.SECONDS));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("出列:第"+queue.poll());   //移除
        System.out.println("出列:第"+queue.poll());   //移除
        System.out.println("出列:第"+queue.poll());   //移除
        try {
            System.out.println("設置等待時間,超時就退出.3秒過後結束執行");
            System.out.println("出列:第"+queue.poll(3,TimeUnit.SECONDS));   //移除
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }
}

執行結果
在這裏插入圖片描述

4.一直阻塞

這組方法,會一直阻塞等待着拿出元素。

package com.jp.studyBlockQueue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * @className:
 * @PackageName: com.jp.studyBlockQueue
 * @author: youjp
 * @create: 2020-05-27 19:16
 * @description:
 * @Version: 1.0
 */
public class BlockQueueDemo3 {
    public static void main(String[] args) throws InterruptedException {

        ArrayBlockingQueue queue=new ArrayBlockingQueue(3); //只能加3只羊
        for (int i = 1; i <= 3; i++) {
            System.out.println("入列"+i+"小羊");
           queue.put(""+i+"只小羊");
        }

        System.out.println("出列:第"+queue.take());
        System.out.println("出列:第"+queue.take());
        System.out.println("出列:第"+queue.take());
        //再次嘗試出列
        System.out.println("一直等到----阻塞等待拿出元素");
        System.out.println(queue.take());//阻塞等待拿出元素


    }
}


執行結果:
在這裏插入圖片描述

SynchronousQueue 同步隊列

Java6的併發編程包中的SynchronousQueue是一個沒有數據緩衝的BlockingQueue,生產者線程對其的插入操作put必須等待消費者的移除操作take,反過來也一樣。

不像ArrayBlockingQueue或LinkedListBlockingQueue,SynchronousQueue內部並沒有數據緩存空間,你不能調用peek()方法來看隊列中是否有數據元素,因爲數據元素只有當你試着取走的時候纔可能存在,不取走而只想偷窺一下是不行的,當然遍歷這個隊列的操作也是不允許的。隊列頭元素是第一個排隊要插入數據的線程,而不是要交換的數據。數據是在配對的生產者和消費者線程之間直接傳遞的,並不會將數據緩衝數據到隊列中。可以這樣來理解:生產者和消費者互相等待對方,握手,然後一起離開。

​ SynchronousQueue 不存儲元素,隊列是空的。

​ 每一個 put 操作。必須等待一個take。否則無法繼續添加元素!可以將SynchronousQueue理解爲只有一個數據大小的ArrayBlockingQueue當中的一直阻塞put和take。

package com.jp.studyBlockQueue;

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/**
 * @className:
 * @PackageName: com.jp.studyBlockQueue
 * @author: youjp
 * @create: 2020-05-27 19:34
 * @description:    TODO 同步隊列的學習
 * @Version: 1.0
 */
public class StudySynchronousQueueDemo {

    public static void main(String[] args) {
        SynchronousQueue synchronousQueue=new SynchronousQueue();
		


        //添加元素線程
        new Thread(()->{

            try {
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName() + ":put 1");

                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName() + ":put 2");

                synchronousQueue.put("3");
                System.out.println(Thread.currentThread().getName() + ":put 3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        },"A").start();

        

        //讀取元素線程
        new Thread(()->{

            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+synchronousQueue.take());

                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+synchronousQueue.take());

                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+synchronousQueue.take());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        },"B").start();


    }
}

執行結果:
在這裏插入圖片描述
SynchronousQueue的一個使用場景是在線程池裏。Executors.newCachedThreadPool()就使用了SynchronousQueue,這個線程池根據需要(新任務到來時)創建新的線程,如果有空閒線程則會重複使用,線程空閒了60秒後會被回收。

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