併發隊列的介紹及使用

在JDK1.5新加入了一個包concurrent,位於java.util.concurrent。在我們寫業務代碼的時候,可能最爲常見就是ConcurrentHashMap。當然今天我們的主角不是他,而是queue。在併發隊列上JDK提供了兩套實現。

阻塞隊列(IO):以BlockingQueue接口爲代表的阻塞隊列。
非阻塞(NIO)隊列:以ConcurrentLinkedQueue爲代表的高性能隊列。

1.阻塞隊列(IO)

1.1.什麼是阻塞隊列

1、當隊列是滿時,往隊列裏添加元素的操作會被阻塞;
2、或者當隊列是空的時,從隊列中獲取元素的操作將會被阻塞;
3、阻塞隊列是線程安全的。

1.2.阻塞隊列的應用場景

阻塞隊列常用於生產者和消費者的場景。

1、生產者是往隊列裏添加元素的線程;
2、消費者是從隊列裏拿元素的線程;
3、阻塞隊列就是生產者存放元素的容器。

1.3.BlockingQueue

1.3.1.ArrayBlockingQueue

1、是一個有邊界的阻塞隊列,它的內部實現是一個數組。有邊界的意思是它的容量是有限的,我們必須在其初始化的時候指定它的容量大小,容量大小一旦指定就不可改變。
2、採用FIFO先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。

1.3.2.ArrayBlockingQueue

1、LinkedBlockingQueue阻塞隊列大小的配置是可選的,如果我們初始化時指定一個大小,它就是有邊界的,如果不指定,它就是無邊界的。說是無邊界,其實是採用了默認大小爲Integer.MAX_VALUE的容量 。它的內部實現是一個鏈表。
2、和ArrayBlockingQueue一樣,LinkedBlockingQueue 也是以先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。

1.3.3.PriorityBlockingQueue

1、是一個沒有邊界的隊列,它的排序規則和 java.util.PriorityQueue一樣
2、需要注意,PriorityBlockingQueue中允許插入null對象。
3、所有插入PriorityBlockingQueue的對象必須實現 java.lang.Comparable接口,隊列優先級的排序規則就是按照我們對這個接口的實現來定義的。
4、我們可以從PriorityBlockingQueue獲得一個迭代器Iterator,但這個迭代器並不保證按照優先級順序進行迭代。

1.3.4.SynchronousQueue

僅允許容納一個元素。當一個線程插入一個元素後會被阻塞,除非這個元素被另一個線程消費。

2.非阻塞(NIO)隊列

和阻塞隊列相比,他不會被阻塞,並且效率比阻塞高。

2.1.ConcurrentLinkedQueue

ConcurrentLinkedQueue是一個適用於高併發場景下的隊列,通過無鎖的方式,實現了高併發狀態下的高性能。通常ConcurrentLinkedQueue性能好於BlockingQueue。它是一個基於鏈接節點的無界線程安全隊列。
該隊列的元素遵循先進先出的原則。頭是最先加入的,尾是最近加入的,該隊列不允許null元素。

2.2.1重要方法

add 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中這倆個方法沒有任何區別),在這兩個方法中,均調用offerLast(E e)方法。
將指定的元素插入此雙端隊列的末尾

  /**
     * Inserts the specified element at the tail of this deque.
     * As the deque is unbounded, this method will never throw
     * {@link IllegalStateException} or return {@code false}.
     *
     * @return {@code true} (as specified by {@link Collection#add})
     * @throws NullPointerException if the specified element is null
     */
    public boolean add(E e) {
        return offerLast(e);
    }
       /**
     * Inserts the specified element at the tail of this deque.
     * As the deque is unbounded, this method will never return {@code false}.
     *
     * @return {@code true} (as specified by {@link Queue#offer})
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(E e) {
        return offerLast(e);
    }

poll() 和peek() 都是取頭元素節點,區別在於前者會刪除元素,後者不會。

    public E poll()           { return pollFirst(); }
    public E peek()           { return peekFirst(); }

返回刪除此雙端隊列的第一個元素

/**
* Retrieves and removes the first element of this deque,
* or returns {@code null} if this deque is empty.
*
* @return the head of this deque, or {@code null} if this deque is empty
*/
public E pollFirst() {
        restart: for (;;) {
            for (Node<E> first = first(), p = first;;) {
                final E item;
                if ((item = p.item) != null) {
                    // recheck for linearizability
                    if (first.prev != null) continue restart;
                    if (ITEM.compareAndSet(p, item, null)) {
                        unlink(p);
                        return item;
                    }
                }
                if (p == (p = p.next)) continue restart;
                if (p == null) {
                    if (first.prev != null) continue restart;
                    return null;
                }
            }
        }
    }

返回此雙端隊列的第一個元素

/**
* Retrieves, but does not remove, the first element of this deque,
* or returns {@code null} if this deque is empty.
*
* @return the head of this deque, or {@code null} if this deque is empty
*/
public E peekFirst() {
	restart: for (;;) {
		E item;
		Node<E> first = first(), p = first;
		while ((item = p.item) == null) {
			if (p == (p = p.next)) continue restart;
                if (p == null)
                    break;
       	}	
	    // recheck for linearizability
       	if (first.prev != null) continue restart;
        return item;
	}
}

2.2.2代碼演示

@Test
	public void test() throws Exception {
		ConcurrentLinkedDeque<String> qStr = new ConcurrentLinkedDeque<>();
		qStr.add("1胡濤");
		qStr.add("2謝天");
		qStr.add("3楊騰龍");
		System.out.println("當前隊列:"+qStr.toString());
		String poll2 = qStr.peek();
		System.out.println("調用peek()方法獲取到"+poll2);
		System.out.println("調用poll()方法後,隊列還有:"+qStr.toString());
		String poll = qStr.poll();
		System.out.println("調用poll()方法獲取到"+poll);
		System.out.println("調用poll()方法後,隊列還有:"+qStr.toString());
	}

在這裏插入圖片描述

3.阻塞隊列的生產消費者應用

基礎知識儲備:你需要多線程的知識點,這裏我就不囉嗦多線程的知識點了。
AtomicInteger:高併發的情況下,i++無法保證原子性,往往會出現問題,所以引入AtomicInteger類。
volatile:1、保證共享變量對所有的線程的可見;2、禁止CPU進行重排序
生產者

class ProducerThread implements Runnable {
	
	private BlockingQueue<String> blockingQueue;
	
	private AtomicInteger count = new AtomicInteger();
	
	private volatile boolean FLAG = true;

	public ProducerThread(BlockingQueue<String> blockingQueue) {
		this.blockingQueue = blockingQueue;
	}

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "生產者開始啓動....");
		while (FLAG) {
			String data = count.incrementAndGet() + "";
			try {
				boolean offer = blockingQueue.offer(data, 2, TimeUnit.SECONDS);
				if (offer) {
					System.out.println(Thread.currentThread().getName() + ",生產隊列" + data + "成功..");
				} else {
					System.out.println(Thread.currentThread().getName() + ",生產隊列" + data + "失敗..");
				}
				Thread.sleep(1000);
			} catch (Exception e) {

			}
		}
		System.out.println(Thread.currentThread().getName() + ",生產者線程停止...");
	}

	public void stop() {
		this.FLAG = false;
	}

}

消費者

class ConsumerThread implements Runnable {
	private volatile boolean FLAG = true;
	private BlockingQueue<String> blockingQueue;

	public ConsumerThread(BlockingQueue<String> blockingQueue) {
		this.blockingQueue = blockingQueue;
	}

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "消費者開始啓動....");
		while (FLAG) {
			try {
				String data = blockingQueue.poll(2, TimeUnit.SECONDS);
				if (data == null || data == "") {
					FLAG = false;
					System.out.println("消費者超過2秒時間未獲取到消息.");
					return;
				}
				System.out.println("消費者獲取到隊列信息成功,data:" + data);

			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}
}
@Test
	public void proDucerAndConsumer() throws Exception {
		BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(3);
		ProducerThread producerThread = new ProducerThread(blockingQueue);
		ConsumerThread consumerThread = new ConsumerThread(blockingQueue);
		Thread t1 = new Thread(producerThread);
		Thread t2 = new Thread(consumerThread);
		t1.start();
		t2.start();
		//10秒後 停止線程..
		try {
			Thread.sleep(10*1000);
			producerThread.stop();
		} catch (Exception e) {
			// TODO: handle exception
		}

	}

運行效果
在這裏插入圖片描述

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