1. 程式人生 > 程式設計 >Java實現自定義阻塞佇列

Java實現自定義阻塞佇列

今天重溫了下 java 多執行緒中的 notify() 方法以及 wait() 方法,一時興起,決定通過這倆個方法,實現一個簡易的自定義阻塞佇列。

阻塞佇列是什麼,與普通佇列的區別是什麼?
阻塞佇列與普通佇列的區別在於,當佇列是空的時,從佇列中獲取元素的操作將會被阻塞,或者當佇列是滿時,往佇列裡新增元素的操作會被阻塞。試圖從空的阻塞佇列中獲取元素的執行緒將會被阻塞,直到其他的執行緒往空的佇列插入新的元素。同樣,試圖往已滿的阻塞佇列中新增新元素的執行緒同樣也會被阻塞,直到其他的執行緒使佇列重新變得空閒起來。

1.新建一個 MyQueue.java 類

import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;

import com.xiaoleilu.hutool.util.StrUtil;

/**
 * 使用 notify() 和 wait() 實現自定義阻塞佇列
 *
 * @author Yangkai.Shen
 * @version 1.0
 * @date 2017.08.02 at 11:51:14
 */
public class MyQueue {

	// 1. 承載資料的容器
	private LinkedList<Object> queue = new LinkedList<Object>();

	// 2. 計數器,用於判定邊界
	private AtomicInteger count = new AtomicInteger(0);
	private final int minSize = 0;

	// 3. 初始化一個物件,用於加鎖
	private final Object lock = new Object();
	private final int maxSize;

	public MyQueue(int maxSize) {
		this.maxSize = maxSize;
	}

	/**
	 * 新增一個元素到佇列中,如果佇列元素已滿,則呼叫此方法的執行緒被阻塞,直到存在多餘空間了,再進行新增
	 *
	 * @param obj 新增 obj 到佇列尾部
	 */
	public void put(Object obj) {
		synchronized (lock) {
			// 1.沒有多餘空間,就阻塞執行緒
			while (count.get() == this.maxSize) {
				try {
					lock.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			// 2.新增元素
			queue.add(obj);
			// 3.計數器累加
			count.incrementAndGet();
			System.out.println(StrUtil.format("新加入的元素為:{}",obj));
			// 4.喚醒其他執行緒(若本來元素為空,有執行緒呼叫 get 方法,那麼原本被阻塞的,需要在此時被喚醒)
			lock.notify();
		}
	}

	/**
	 * 獲取一個元素,如果佇列元素為空,則呼叫此方法的執行緒被阻塞,直到新增新元素了,再進行獲取
	 *
	 * @return 返回佇列的第一個元素
	 */
	public Object get() {
		Object ret = null;

		synchronized (lock) {
			// 1.沒有元素,就阻塞執行緒
			while (count.get() == this.minSize) {
				try {
					lock.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			// 2.取第一個元素
			ret = queue.removeFirst();
			// 3.計數器遞減
			count.decrementAndGet();
			System.out.println(StrUtil.format("移除的元素為:{}",ret));
			// 4.喚醒其他執行緒(若元素本來已滿,有執行緒呼叫 put 方法,那麼原本被阻塞的,需要在此時被喚醒)
			lock.notify();
		}

		return ret;
	}

	public int getSize() {
		return this.count.get();
	}

}

2.新建一個測試類 MyQueueTest.java,測試類中,我們初始化一個佇列,並將元素填滿,然後啟動一個執行緒 t1,去插入資料,中間休眠 2s,再去啟動一個執行緒 t2 取資料。

import com.xiaoleilu.hutool.util.StrUtil;

import java.util.concurrent.TimeUnit;

public class MyQueueTest {
	public static void main(String[] args) {
		final MyQueue queue = new MyQueue(5);

		queue.put("a");
		queue.put("b");
		queue.put("c");
		queue.put("d");
		queue.put("e");

		System.out.println(StrUtil.format("當前佇列的長度: {}",queue.getSize()));
		Thread t1 = new Thread(() -> {
			queue.put("f");
			queue.put("g");
			queue.put("h");
		},"t1");
 Thread t2 = new Thread(() -> {
  queue.get();
  queue.get();
 });

 t1.start();
 try {
  TimeUnit.SECONDS.sleep(2);
 } catch (InterruptedException e) {
  e.printStackTrace();
 }
 t2.start();
}

3.啟動測試類,檢視執行結果。控制檯如果應該出現的效果是,佇列先初始化完成,然後休眠 2s,接下來先取資料,再插入資料,則證明阻塞佇列生效。下面是控制檯執行的效果:

  • 初始化佇列

Java實現自定義阻塞佇列

  • 休眠 2s 後取隊首元素,再插入隊尾元素

Java實現自定義阻塞佇列

  • 此時我們會發現,程式還未停止,因為此時佇列已滿,但是執行緒 t1 還未插入 h 元素,因此執行緒被阻塞著,直至下次佇列有空餘空間才會被喚醒。

4.至此,一個自定義阻塞佇列就已經實現了。

5.細心的朋友會發現,我列印的 log 裡用到了一個 StrUtil.format() 方法,這個和 slf4j 的 log 用法一致,可以使用佔位符。這個是用到了一個國產良心工具類,hutool,國產開源,需要大家的支援,覺得好用的話,期望可以去 碼雲 或者 github 上給個 Star 吧!

以上就是Java實現自定義阻塞佇列的詳細內容,更多關於Java 自定義阻塞佇列的資料請關注我們其它相關文章!