Java之BlockingQueue
再看BlockingQueue之前,首先得了解什么是Queue,以解了解Queue的逻辑结构还有特性,了解ReentrantLock可重入锁是什么。
BlockingQueue是阻塞队列,多应用于多线程开发,线程池。
这里讲解基本的操作,如添加、删除、返回队首元素、清除队列
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
添加 | add() | offer() | pull() | offer() |
移除 | remove() | poll() | take() | poll() |
检测队首元素 | element() | peak() |
第一种add() remove() element() 继承自AbstractQueue.java
/**
* 抛出异常 add remove
*/
public static void test1(){
//队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);//3代表容量
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//IllegalStateException: Queue full 抛出异常!
System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.element());//查看队首元素是谁
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//NoSuchElementException 抛出异常!
}
首先看一下ArrayBlockingQueue()的源码,会发现ArrayBlockingQueue()源码在ArrayBlockingQueue.java下
//ArrayBlockingQueue.java
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
继续点开this
//ArrayBlockingQueue.java
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
这就是底层的代码,应该都看得懂吧,多看看源码,还是非常有帮助的
add()方法
点开源码我们发现add()方法来自于ArrayBlockingQueue.java
//ArrayBlockingQueue.java
public boolean add(E e) {
return super.add(e);
}
继续点super后面的add(e),会发现ArrayBlockingQueue.java里面的add()方法继承于AbstractQueue.java
//AbstractQueue.java
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
看清楚,这里注意,当队列满的时候,会抛出IllegalStateException(“Queue full”)异常
还有源码中,add()方法的实现基于offer()方法。
remove()方法
同理看一下源码
//AbstractQueue.java
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
当队列为空时,继续移除会抛出NoSuchElementException()异常,remove()方法实现基于poll()方法
element()方法
//AbstractQueue.java
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
有关于offer()、poll()、peek()方法下面会讲解到
第二种offer()、poll()、peek()方法
/**
* 有返回值,没有异常 offer poll
*/
public static void test2(){
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(2);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
//System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.peek());
System.out.println(blockingQueue.poll());//false 不抛出异常!
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//System.out.println(blockingQueue.poll());//null 返回null
}
直接看方法的源码吧,方便理解
offer()方法
//ArrayBlockingQueue.java
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
可以看到这里加入了可重入锁,保证了线程安全
如果对队列如何存不了解的话,可以点开enqueue(e)
//ArrayBlockingQueue.java
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
自己理解一下
poll()方法
//ArrayBlockingQueue.java
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
dequeue()队列取源码
//ArrayBlockingQueue.java
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
peek()方法
//ArrayBlockingQueue.java
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
第三种offer(E e, long timeout, TimeUnit unit),poll(long timeout, TimeUnit unit)
/**
* 等待,阻塞(等待超时)
*/
public static void test3() throws InterruptedException{
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
blockingQueue.offer("d",2, TimeUnit.SECONDS);
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(3, TimeUnit.SECONDS));
}
这里直接上源码,就是多了参数
//ArrayBlockingQueue.java
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
//ArrayBlockingQueue.java
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
这里可能对TimeUnit不了解,以及toNanos方法,这里讲一下
//TimeUnit.java部分代码
public enum TimeUnit {
/**
* Time unit representing one thousandth of a microsecond
*/
NANOSECONDS {
public long toNanos(long d) { return d; }
public long toMicros(long d) { return d/(C1/C0); }
public long toMillis(long d) { return d/(C2/C0); }
public long toSeconds(long d) { return d/(C3/C0); }
public long toMinutes(long d) { return d/(C4/C0); }
public long toHours(long d) { return d/(C5/C0); }
public long toDays(long d) { return d/(C6/C0); }
public long convert(long d, TimeUnit u) { return u.toNanos(d); }
int excessNanos(long d, long m) { return (int)(d - (m*C2)); }
},
这里可以看到toNanos方法,可能还是不太了解但是翻到TimeUnit.java的最后会看到这么一个方法
//TimeUnit.java
public void sleep(long timeout) throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
Thread.sleep(ms, ns);
}
}
是不是很熟悉,如果知道Thread.sleep(),方法的就会发现,这里怎么也有sleep()方法,其实是Thread.sleep()方法的包装,这里将月、日、时、分、秒封装成方法,提供了更强的可读性,建议使用TimeUnit
第四种pu()t take()
/**
* 等待,阻塞(一直阻塞) put take
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//blockingQueue.put("d");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
}
直接上源码,对照offer()和poll()看
//ArrayBlockingQueue.java
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
//ArrayBlockingQueue.java
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
请大家注意对比一下两个的返回值
有什么问题的话,请大家赐教