【Java】Queue、BlockingQueue和隊列實現生產者消費者模式

1. Queue接口 - 隊列

public interface Queue<E> 
	extends Collection<E>
  • Collection的子接口,表示隊列FIFO(First In First Out)
    常用方法:
    (1)拋出異常
    boolean add(E e) // 順序添加1個元素(到達上限後,再添加則會拋出異常)
    E remove() // 獲得第1個元素並移除(如果隊列沒有元素時,則拋異常)
    E element() // 獲得第1個元素但不移除(如果隊列沒有元素時,則拋異常)
    (2)返回特殊值【推薦】
    boolean offer(E e) // 順序添加1個元素(到達上限後,再添加則會返回false)
    E poll() // 獲得第1個元素並移除(如果隊列沒有元素時,則返回null)
    E keep() // 獲得第1個元素但不移除(如果隊列沒有元素時,則返回null)

1.1 ConcurrentLinkedQueue類(線程安全)

public class ConcurrentLinkedQueue<E> 
	extends AbstractQueue<E> 
	implements Queue<E>, Serializable

說明:

  • 線程安全、可高效讀寫的隊列,高併發下性能最好的隊列
  • 無鎖、CAS比較交換算法,修改的方法包含3個核心參數(V,E,N);
  • V:要更新的變量、E:預期值、N:新值
  • 只有當V==E時,V=N;否則表示已被更新過,則取消當前操作。

使用示例:

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
public class TestQueue {
      public static void main(String[] args) {
            // 列表:尾部追加 - add...
            // 鏈表:頭尾添加 - addFirst/addLast
            // 隊列:先進先出(FIFO) - offer...
            // >>> 以上三種的對應成員方法,切記不能混用!會打亂已知規則。
            LinkedList<String> link = new LinkedList<String>();
            //Queue<String> link = new LinkedList<String>(); // 強制LinkedList遵循隊列的規則
            link.offer("A"); // offer用的是FIFO隊列方式
            link.offer("B");
            link.offer("C");
            // 用列表的方式打亂了FIFO隊列的規則
            link.add(0, "D");
            System.out.println(link.peek()); // D
            
            // 線程安全的隊列Queue
            // 嚴格遵循隊列規則,線程安全,採用CAS交換算法
            Queue<String> q = new ConcurrentLinkedQueue<String>();
            // 1.拋出異常的 2.返回結果的
            q.offer("A");
            q.offer("B");
            q.offer("C");
            
            q.poll(); // 刪除表頭,表頭更新爲B
            
            System.out.println(q.peek()); // 獲取表頭,此時爲B
      }
}

2. BlockingQueue接口 - 阻塞隊列

public interface BlockingQueue<E> 
	extends Queue<E>

常用方法:
void put(E e) // 將指定元素插入此隊列中,如果沒有可用空間,則死等
E take() // 獲取並移除此隊列頭部元素,如果沒有可用元素,則死等
說明:

  • Queue的子接口,阻塞的隊列,增加了兩個線程狀態爲無限期等待的方法
  • 可用於解決生產者、消費者問題

2.1 ArrayBlockingQueue類(有界阻塞隊列)

  • 數組結構實現,有界隊列。手工固定上限
BlockingQueue<String> abq = new ArrayBlockingQueue<String>(3);

2.2 LinkedBlockingQueue類(無界阻塞隊列)

  • 鏈表結構實現,無界隊列。默認上限Integer.MAX_VALUE
BlockingQueue<String> lbq = new LinkedBlockingQueue<String>();

3. 源碼:BlockingQueue實現生產者消費者模式

BlockingQueue是JDK5.0的新增內容,它是一個已經在內部實現了同步的隊列,實現方式採用的是await()/signal()方法。它可以在生成對象時指定容量大小,用於阻塞操作的是put()和take()方法。

  • put()方法:類似於我們上面的生產者線程,容量達到最大時,自動阻塞
  • take()方法:類似於我們上面的消費者線程,容量爲0時,自動阻塞
import java.util.concurrent.LinkedBlockingQueue;
public class TestProduceAndCustomer2 {
	public static void main(String[] args) {
		StorageQ s = new StorageQ();
		Thread p1 = new Thread(new ProducerQ(s), "A廠");
		Thread p2 = new Thread(new ProducerQ(s), "B廠");
		Thread p3 = new Thread(new ProducerQ(s), "C廠");

		Thread c1 = new Thread(new CustomerQ(s), "a人");
		Thread c2 = new Thread(new CustomerQ(s), "b人");
		Thread c3 = new Thread(new CustomerQ(s), "c人");
		p1.start();
		p2.start();
		p3.start();
		c1.start();
		c2.start();
		c3.start();
	}
}

// 倉庫 - 共享資源對象
class StorageQ {
	// 倉庫存儲的載體 - 使用無界阻塞隊列,也可指定容量大小。
    private LinkedBlockingQueue<Object> lbq = new LinkedBlockingQueue<>(10);
	public StorageQ() {
		super();
	}
	public StorageQ(LinkedBlockingQueue<Object> lbq) {
		super();
		this.lbq = lbq;
	}
	public LinkedBlockingQueue<Object> getLbq() {
		return lbq;
	}
	public void setLbq(LinkedBlockingQueue<Object> lbq) {
		this.lbq = lbq;
	}

	// 生產
    public void produce() {
        try{
            lbq.put(new Object());
            System.out.println("【生產者" + Thread.currentThread().getName()
                    + "】生產一個產品,現庫存" + lbq.size());
        } catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    
    // 消費
    public void custome() {
        try{
            lbq.take();
            System.out.println("【消費者" + Thread.currentThread().getName()
                    + "】消費了一個產品,現庫存" + lbq.size());
        } catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

// 生產者
class ProducerQ implements Runnable {
	private StorageQ s;
	public ProducerQ() {}
	public ProducerQ(StorageQ s) {
		this.s = s;
	}
	public void run() {
		while (true) {
			try {
				Thread.sleep((int) (Math.random() * 2000));
				this.s.produce();  // 沒滿 + 可鎖 = 生產+1
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

// 消費者
class CustomerQ implements Runnable {
	private StorageQ s;
	public CustomerQ() {}
	public CustomerQ(StorageQ s) {
		this.s = s;
	}
	public void run() {
		while (true) {
			try {
				Thread.sleep((int) (Math.random() * 2000));
				this.s.custome(); // 不空 + 可鎖 = 消費-1
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

運行結果:
Java隊列實現生產者與消費者

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