Queue API詳解
- Queue API的幾種方法的使用
- ArrayBlockingQueue原理及源碼解析
- ArrayBlockingQueue的成員變量
- ArrayBlockingQueue的offer和put方法
- ArrayBlockingQueue的poll和take方法
- ArrayBlockingQueue的peek方法
- LinkedBlockingDeque原理及源碼解析
- LinkedBlockingDeque的成員變量
- LinkedBlockingDeque的構造函數
- LinkedBlockingDeque的offer和put方法
- LinkedBlockingDeque的poll和take方法
- LinkedBlockingDeque的peek方法
- ConcurrentLinkedDeque
- SynchronousQueue的簡單使用
- PriorityQueue的簡單使用
- 結束語
Queue API的幾種方法的使用
方法名稱 | 作用 | 描述 |
---|---|---|
add | 添加元素到隊列 | 如果隊列滿了就拋異常java.lang.IllegalStateException |
remove | 移除並且返回隊列頭部元素 | 如果隊列爲null,就拋異常java.util.NoSuchElementException |
element | 返回隊列頭部元素,不會移除元素 | 如果隊列爲null,就拋異常java.util.NoSuchElementException |
offer | 添加元素到隊列 | 如果隊列滿了就返回false,不會阻塞 |
poll | 移除並且返回隊列頭部元素 | 如果隊列爲null,就返回null,不會阻塞 |
peek | 返回隊列頭部元素,不會移除元素 | 如果隊列爲null,就返回null,不會阻塞 |
put | 添加元素到隊列 | 如果隊列滿了就阻塞 |
take | 移除並且返回隊列頭部元素 | 如果隊列爲null,就阻塞 |
ArrayBlockingQueue原理及源碼解析
根據名字,可以知道,ArrayBlockingQueue底層是數組實現的,而且是阻塞的隊列,下面看下put元素和take元素時的圖解:
上面的圖基本上就是ArrayBlockingQueue隊列使用時的底層實現原理了,下面根據源碼來看一下。
ArrayBlockingQueue的成員變量
/** 存放元素 */
final Object[] items;
/** 記錄下一次從什麼位置開始取元素或者移除元素 */
int takeIndex;
/** 記錄下一次從什麼位置開始放元素 */
int putIndex;
/** 隊列中元素的數量 */
int count;
/** 可重入鎖,用來放元素和取元素時加鎖 */
final ReentrantLock lock;
/** 取元素的等待集合 */
private final Condition notEmpty;
/** 存放元素的等待集合 */
private final Condition notFull;
ArrayBlockingQueue的offer和put方法
offer方法源碼:
/**
* 存放一個元素到隊列
* 如果隊列未滿,就放入隊列的尾部
* 如果隊列已滿,就返回false
*
* @throws NullPointerException 如果存放的元素是null,拋異常
*/
public boolean offer(E e) {
//校驗放入的元素是否爲空,每次放入元素到隊列,都會校驗
checkNotNull(e);
//加鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
//如果隊列中的元素已經滿了,就返回false
if (count == items.length)
return false;
else {
//未滿就調用方法,放入元素到隊列
enqueue(e);
return true;
}
} finally {
//釋放鎖
lock.unlock();
}
}
put方法源碼:
/**
* 存放一個元素到隊列
* 如果隊列未滿,就放入隊列的尾部
* 如果隊列已滿,就阻塞等待
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void put(E e) throws InterruptedException {
//校驗放入的元素是否爲空,每次放入元素到隊列,都會校驗
checkNotNull(e);
//加鎖
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果隊列中的元素已經滿了,就阻塞,掛起當前線程
//這裏使用while而不使用if,是爲了防止僞喚醒
while (count == items.length)
notFull.await();
//未滿就調用方法,放入元素到隊列
enqueue(e);
} finally {
//釋放鎖
lock.unlock();
}
}
參數校驗:
private static void checkNotNull(Object v) {
//如果傳入的元素是null,就拋異常
if (v == null)
throw new NullPointerException();
}
共同調用的enqueue方法:
/**
* 將指定的元素放入隊列的尾部
* 只有持有鎖纔可以調用
*/
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
//獲取隊列中的元素
final Object[] items = this.items;
//putIndex就是要放入的元素在隊列中的的索引
items[putIndex] = x;
//如果放入元素之後,隊列滿了,就把putIndex置爲0
//意思是下一次向隊列中放元素,就是放入隊列的第一個位置了
//putIndex的作用就是記錄下一次元素應該放到哪裏
if (++putIndex == items.length)
putIndex = 0;
//元素的個數加一
count++;
//喚醒拿元素沒有拿到掛起的線程,告訴它:
//元素已經放入了隊列,可以取元素了
notEmpty.signal();
}
ArrayBlockingQueue的poll和take方法
poll方法源碼:
/**
* 從隊列中拿元素
* 如果隊列中沒有元素了,就返回null
* 如果隊列中有元素,就返回
*/
public E poll() {
//加鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
//如果隊列中沒有元素,就返回Null
//否則就調用方法取元素然後返回
return (count == 0) ? null : dequeue();
} finally {
//釋放鎖
lock.unlock();
}
}
take方法源碼:
/**
* 從隊列中拿元素
* 如果隊列中沒有元素了,就阻塞
* 如果隊列中有元素,就返回
*/
public E take() throws InterruptedException {
//加鎖
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果隊列中沒有元素,就讓線程阻塞
//使用while而不使用if,是爲了防止僞喚醒
while (count == 0)
//掛起線程
notEmpty.await();
//如果隊列中有元素存在,就取出返回
return dequeue();
} finally {
//釋放鎖
lock.unlock();
}
}
dequeue方法源碼:
/**
* 從隊列中取元素
* 只有持有鎖纔可以調用
*/
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
//獲取隊列中的元素
final Object[] items = this.items;
@SuppressWarnings("unchecked")
//獲取要取出的是哪一個元素
E x = (E) items[takeIndex];
//取出後設置爲Null
items[takeIndex] = null;
//要是取出的是隊列中的最後一個元素
//就把takeIndex置爲0,意思是下一次取元素從隊列第一個開始取
if (++takeIndex == items.length)
takeIndex = 0;
//隊列中元素的個數減一
count--;
if (itrs != null)
itrs.elementDequeued();
//喚醒因爲存放元素時,隊列滿了,掛起的線程
//告訴它,可以存放元素了
notFull.signal();
//返回取出的元素
return x;
}
ArrayBlockingQueue的peek方法
/**
* 返回隊列頭部的元素
* 如果隊列中沒有元素了,就返回null
* 如果隊列中有元素,就返回
*/
public E peek() {
//加鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
//有元素就返回,沒有就返回null
return itemAt(takeIndex); // null when queue is empty
} finally {
//釋放鎖
lock.unlock();
}
}
注意:實例化ArrayBlockingQueue時必須指定隊列的容量大小,否則會編譯錯誤
ArrayBlockingQueue<String> queue =
new ArrayBlockingQueue<String>(3);
LinkedBlockingDeque原理及源碼解析
根據名字,可以知道LinkedBlockingDeque,底層是使用鏈表的方式存儲元素的,而且是阻塞隊列,初始化一個LinkedBlockingDeque可以不用指定隊列的容量,即可以指定一個無界的隊列。
LinkedBlockingDeque的成員變量
private static final long serialVersionUID = -387911632671998426L;
/** 鏈表節點類 */
static final class Node<E> {
/**
* 鏈表中的元素
*/
E item;
/**
* 上一個節點
*/
Node<E> prev;
/**
* 下一個節點
*/
Node<E> next;
//構造函數
Node(E x) {
item = x;
}
}
/**
* 指向第一個節點的指針
*/
transient Node<E> first;
/**
* 指向最後一個節點的指針
*/
transient Node<E> last;
/** 隊列中元素的個數 */
private transient int count;
/** 隊列的容量 */
private final int capacity;
/** 可重入鎖 */
final ReentrantLock lock = new ReentrantLock();
/** 取元素的等待集合 */
private final Condition notEmpty = lock.newCondition();
/** 存放元素的等待集合 */
private final Condition notFull = lock.newCondition();
LinkedBlockingDeque的構造函數
/**
* 無參構造函數
* 可以創建一個無界的隊列,隊列容量是int的最大值
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingDeque() {
this(Integer.MAX_VALUE);
}
/**
* 創建一個指定邊界的隊列
* 隊列的大小由編碼時指定
* @param capacity 指定的隊列容量值
* @throws IllegalArgumentException if {@code capacity} is less than 1
*/
public LinkedBlockingDeque(int capacity) {
//如果傳入的指定隊列容量值小於0,就拋異常
if (capacity <= 0) throw new IllegalArgumentException();
//指定的隊列容量大小
this.capacity = capacity;
}
/**
* 創建一個包含指定集合元素的隊列
*
* @param c 要包含這個元素的集合
* @throws NullPointerException 如果指定的集合或者其中的元素是null,拋異常
*/
public LinkedBlockingDeque(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
//加鎖
final ReentrantLock lock = this.lock;
lock.lock(); // Never contended, but necessary for visibility
try {
//遍歷指定的集合,然後放入隊列
for (E e : c) {
if (e == null)
throw new NullPointerException();
if (!linkLast(LinkedBlockingDeque.Node<E>(e)))
throw new IllegalStateException("Deque full");
}
} finally {
//釋放鎖
lock.unlock();
}
}
LinkedBlockingDeque的offer和put方法
offer方法源碼:
/**
* 添加一個元素到隊列
* 隊列未滿,就直接添加進去
* 隊列已滿,就返回false
* @throws NullPointerException 添加元素爲null。拋異常
*/
public boolean offer(E e) {
return offerLast(e);
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public boolean offerLast(E e) {
//如果添加的元素是null,就拋異常
if (e == null) throw new NullPointerException();
//初始化一個鏈表,把元素放入鏈表
Node<E> node = new Node<E>(e);
//加鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
return linkLast(node);
} finally {
//釋放鎖
lock.unlock();
}
}
/**
* 把元素添加到鏈表,如果隊列元素滿了,就返回false
*/
private boolean linkLast(Node<E> node) {
// assert lock.isHeldByCurrentThread();
//如果添加進去元素,隊列的長度大於隊列的容量,就返回false
if (count >= capacity)
return false;
//獲取鏈表的最後一個節點
Node<E> l = last;
//把鏈表的最後一個節點做爲上一個節點
node.prev = l;
//把當前要添加的元素放入鏈表
last = node;
//如果鏈表的第一個節點是null,就把元素放入鏈表的第一個位置
if (first == null)
first = node;
else
//否則就把元素放入鏈表最後一個節點的下一個節點中
l.next = node;
//隊列中元素的個數加一
++count;
//喚醒拿元素時阻塞的線程
notEmpty.signal();
//添加成功,返回true
return true;
}
put方法源碼:
/**
* 添加一個元素到隊列
* 隊列未滿,就直接添加進去
* 隊列已滿,就阻塞
* @throws NullPointerException {@inheritDoc}
* @throws InterruptedException {@inheritDoc}
*/
public void put(E e) throws InterruptedException {
putLast(e);
}
/**
* @throws NullPointerException {@inheritDoc}
* @throws InterruptedException {@inheritDoc}
*/
public void putLast(E e) throws InterruptedException {
//如果添加的元素是null,就返回NullPointerException
if (e == null) throw new NullPointerException();
//初始化一個鏈表,把元素放入鏈表
Node<E> node = new Node<E>(e);
//加鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
//當元素沒有添加成功,就掛起
//linkLast方法在上面offer中已經寫了
while (!linkLast(node))
notFull.await();
} finally {
//釋放鎖
lock.unlock();
}
}
LinkedBlockingDeque的poll和take方法
poll方法源碼:
/**
* 從隊列中取元素
* 隊列有元素存在,取出元素
* 隊列爲空,返回null
*/
public E poll() {
return pollFirst();
}
public E pollFirst() {
//加鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
//隊列有元素就返回,否則就返回null
return unlinkFirst();
} finally {
//釋放鎖
lock.unlock();
}
}
take方法源碼:
/**
* 從隊列中取元素
* 隊列有元素存在,取出元素
* 隊列爲空,就阻塞
*/
public E take() throws InterruptedException {
return takeFirst();
}
public E takeFirst() throws InterruptedException {
//加鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
E x;
//當隊列爲空時,就阻塞
//while防止僞喚醒
while ( (x = unlinkFirst()) == null)
notEmpty.await();
//隊列有元素存在,返回取出的元素
return x;
} finally {
//釋放鎖
lock.unlock();
}
}
unlinkFirst方法源碼:
/**
* Removes and returns first element, or null if empty.
* 刪除並返回第一個元素,如果爲空則返回null
*/
private E unlinkFirst() {
// assert lock.isHeldByCurrentThread();
//獲取隊列中第一個元素
//及鏈表的第一個節點
Node<E> f = first;
//第一個元素爲null,就返回null
if (f == null)
return null;
//獲取第一個節點的下一個節點
Node<E> n = f.next;
//獲取第一個節點的元素值
E item = f.item;
//把值設置爲null
f.item = null;
f.next = f; // help GC
//把隊列的第一個元素設置爲:
//要移除元素的下一個節點
first = n;
//如果是null,就把最後一個節點設置爲null
if (n == null)
last = null;
else
//否則就把上一個節點設置爲Null
n.prev = null;
//隊列的元素個數減一
--count;
//喚醒存放元素時,掛起的線程
notFull.signal();
//返回取出的元素
return item;
}
LinkedBlockingDeque的peek方法
/**
* 返回隊列頭部的元素
* 隊列有元素存在,返回元素
* 隊列爲空,返回null
*/
public E peek() {
return peekFirst();
}
public E peekFirst() {
//加鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
//如果隊列頭部元素爲空就返回null
//否則就返回頭部元素
return (first == null) ? null : first.item;
} finally {
//釋放鎖
lock.unlock();
}
}
ConcurrentLinkedDeque
根據英文意思,可以知道,ConcurrentLinkedDeque是一個非阻塞的隊列,底層是鏈表實現,具體源碼請查看其他文章。
SynchronousQueue的簡單使用
SynchronousQueue是一個容量爲0的隊列,隊列內部不存儲元素;當put一個元素時,如果沒有take方法去拿元素,就會一直阻塞,直到有take方法去拿元素纔會結束;同樣的,take元素時,如果沒有put元素,那麼就會一直阻塞,直到有put元素,纔會結束,下面看下示例:
public static void main(String[] args) {
SynchronousQueue<String> queue = new SynchronousQueue<>();
Thread th = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("start work...");
//向隊列放入元素
queue.offer("hello");
System.out.println("end work...");
}
});
th.start();
//打印取出的元素
System.out.println(queue.poll());
//打印結果爲null
try {
//打印結果爲hello
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
從上面的例子可以看出,在SynchronousQueue隊列中,offer進去的元素可能會丟失。
SynchronousQueue<String> queue = new SynchronousQueue<>();
Thread th = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("start work...");
//向隊列放入元素
try {
//會阻塞
queue.put("hello");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end work...");
}
});
th.start();
try {
//打印結果爲hello
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
put元素時,如果沒有take方法去拿元素就會阻塞;如果這時使用poll方法去拿元素,取出的元素是null,而且不會結束阻塞。
take元素時,如果沒有put或者offer元素進隊列,也會阻塞。
PriorityQueue的簡單使用
PriorityQueue是一個優先級隊列,會自動對元素進行排序,也可以自己指定排序規則。
public static void main(String[] args) {
PriorityQueue<String> queue = new PriorityQueue<String>();
//入隊列
queue.offer("36");
queue.offer("21");
queue.offer("57");
queue.offer("78");
queue.offer("22");
//出隊列
System.out.println(queue.poll());//21
System.out.println(queue.poll());//22
System.out.println(queue.poll());//36
System.out.println(queue.poll());//57
System.out.println(queue.poll());//78
}
PriorityQueue還可以自定義排序規則,通過實現compare方法即可:
public static void main(String[] args) {
PriorityQueue<Student> queue = new PriorityQueue<Student>(10, new Comparator<Student>() {
//自定義比較規則
@Override
public int compare(Student o1, Student o2) {
if (o1.age > o2.age) {
return 1;
} else {
return -1;
}
}
});
//入隊列
queue.offer(new Student("小明", 15));
queue.offer(new Student("小紅", 12));
queue.offer(new Student("小黑", 16));
//出隊列
System.out.println( queue.poll().age);//12
System.out.println(queue.poll().age);//15
System.out.println(queue.poll().age);//16
}
static class Student {
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
結束語
本文只講了兩個隊列的源碼,可能存在不足或者不夠深入的地方,還希望有朋友可以多多指正,其他幾個隊列的源碼,後續有時間的話再做解析,感謝閱讀!