Java實現自定義阻塞佇列
阿新 • • 發佈:2020-10-13
今天重溫了下 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,接下來先取資料,再插入資料,則證明阻塞佇列生效。下面是控制檯執行的效果:
- 初始化佇列
- 休眠 2s 後取隊首元素,再插入隊尾元素
- 此時我們會發現,程式還未停止,因為此時佇列已滿,但是執行緒
t1
還未插入h
元素,因此執行緒被阻塞著,直至下次佇列有空餘空間才會被喚醒。
4.至此,一個自定義阻塞佇列就已經實現了。
5.細心的朋友會發現,我列印的 log 裡用到了一個 StrUtil.format() 方法,這個和 slf4j 的 log 用法一致,可以使用佔位符。這個是用到了一個國產良心工具類,hutool,國產開源,需要大家的支援,覺得好用的話,期望可以去 碼雲 或者 github 上給個 Star 吧!
以上就是Java實現自定義阻塞佇列的詳細內容,更多關於Java 自定義阻塞佇列的資料請關注我們其它相關文章!