这篇文章继续来介绍线程任务队列——LinkedBlockingQueue,LinkedBlockingQueue是基于链接节点的阻塞队列,它是线程安全的。
LinkedBlockQueue是在jdk1.5之后出现的,先看看它的所继承或实现的超类的关系图。
再看看LinkedBlockingQueue从超类里面所继承过来的方法,最主要的是看AbstractQueue和BlockingQueue
直接看看BlockingQueue接口里面的方法。
public interface BlockingQueue<E> extends Queue<E> {
//将指定的元素插入到此队列中,成功就返回true,失败久返回flase
boolean add(E e);
//将指定的元素插入到此队列中,如果可以立即执行此操作,而不会违反容量 //限制, true在成功时 false如果当前没有可用空间,则返回false。
boolean offer(E e);
//将指定元素插入到此队列中,如果不能立即执行,线程就一直等待,直至该元
//该元素被插入到队列中去,或者在等待过程过中线程被中断
void put(E e) throws InterruptedException;
//在指定多少时间之后向队列插入指定元素,成功返回ture,失败返回flase
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
//删除队头元素,如果队列为空,就等待
E take() throws InterruptedException;
//等待指定的时间后删除队头元素,如果队列为空就等待
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
//剩余容量
int remainingCapacity();
//删除指定的元素
boolean remove(Object o);
//队列中是否包含某元素
public boolean contains(Object o);
//删除队列中所有可用的元素,并将它们添加到给定集合里面
int drainTo(Collection<? super E> c);
//删除给定数量的可用元素,并将它们添加到指定的队列中去
int drainTo(Collection<? super E> c, int maxElements);
}
LinkedBlockingQueue基本实现了BlockingQueue里面的方法,等会看看LinkedBlockingQueue的源码是怎么实现的。
我们知道LinkedBlockingQueue是线程安全的,怎么保证的它的线程安全呢?主要是靠ReentrantLock和Condition来实现的。
/** Lock held by take, poll, etc */
//当从队列中取出元素时,所加的锁。
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
//notEmpty等待对象,队列中没元素可取的时候,就把当前取出线程挂起
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
//向队列添加元素时,所加的锁
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
//notFull等待对象,当队列已满时,挂起向队列添加元素的线程
private final Condition notFull = putLock.newCondition();
下面看看LinkedBlockingQueue中的几个重要的方法:
**
入队方法
**
1.put(E e)
public void put(E e) throws InterruptedException {
//检查插入的是否为空元素,如果为空就跑异常
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
//获取当前的插入锁
final ReentrantLock putLock = this.putLock;
//当前队列元素数量
final AtomicInteger count = this.count;
//加锁,这是可中断锁
putLock.lockInterruptibly();
try {
//队列元素已满,等待(阻塞队列)
while (count.get() == capacity) {
notFull.await();
}
//将node入队
enqueue(node);
//统计元素数量+1
c = count.getAndIncrement();
//如果队列未满,唤醒其他在等待的队列
if (c + 1 < capacity)
notFull.signal();
} finally {
//释放锁
putLock.unlock();
}
//如果队列中有元素,唤醒消费者来消费
if (c == 0)
signalNotEmpty();
}
2.offer(E e)
public boolean offer(E e) {
//非空判断
if (e == null) throw new NullPointerException();
//当前队列元素数量
final AtomicInteger count = this.count;
//判断队列是否已满
if (count.get() == capacity)
return false;
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
//加锁
putLock.lock();
try {
//如果队列未满
if (count.get() < capacity) {
//将node入队
enqueue(node);
c = count.getAndIncrement();
//如果队列未满,唤醒其他线程向队列添加元素的操作
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
//如果队列中有元素,唤醒其他线程来消费
if (c == 0)
signalNotEmpty();
return c >= 0;
}
3.offer(E e,long timeout,TimeUnit unit)
这个方法的实现跟offer(E e)差不多,只是这个方法在指定的延迟时间后插入。
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
long nanos = unit.toNanos(timeout);
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
//等待时间超时,直接返回false
if (nanos <= 0)
return false;
//利用了Condition的awaitNanos方法来进行阻塞等待 // 直至超时
nanos = notFull.awaitNanos(nanos);
}
enqueue(new Node<E>(e));
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return true;
}
上面方法小结:
put(E e)跟 offer(E e)最大的区别是:当用put方法将元素入队时,如果队列元素已满了,put方法会将当前线程阻塞,而offer方法直接返回false。
**
出队方法
**
1.take()
take()方法跟put()的逻辑其实是差不多一样的。
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
//上锁,可中断锁
takeLock.lockInterruptibly();
try {
//当队列没有元素的时候,就阻塞
while (count.get() == 0) {
notEmpty.await();
}
//出队
x = dequeue();
c = count.getAndDecrement();
//队列中非空,唤醒其他等待经常出队操作的线程
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
//唤醒其他线程来生产元素(入队)
if (c == capacity)
signalNotFull();
return x;
}
2.poll()
public E poll() {
final AtomicInteger count = this.count;
if (count.get() == 0)
return null;
E x = null;
int c = -1;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
//队列中有元素
if (count.get() > 0) {
x = dequeue();
c = count.getAndDecrement();
//唤醒其他线程来消费
if (c > 1)
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
//唤醒生产线程
if (c == capacity)
signalNotFull();
return x;
}
3.poll(long timeout, TimeUnit unit)
具体跟offer(E e ,long timeout,TimeUnit unit)差不多
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E x = null;
int c = -1;
long nanos = unit.toNanos(timeout);
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
//如果超时 直接返回null
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
出队方法总结:
take进行出队的时候,如果队列中没有元素就会将线程阻塞,而poll方法不会,如果队列没有元素就直接返回null。