/**
* Inserts the specified element at the tail of this queue, waiting if
* necessary for space to become available.
*/
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly(); //加 putLock 锁
try {
//当队列满时,调用notFull.await()方法释放锁,陷入等待状态。
//有两种情况会激活该线程
//第一、 某个put线程添加元素后,发现队列有空余,就调用notFull.signal()方法激活阻塞线程
//第二、 take线程取元素时,发现队列已满。则其取出元素后,也会调用notFull.signal()方法激活阻塞线程
while (count.get() == capacity) {
notFull.await();
}
// 把元素 e 添加到队列中(队尾)
enqueue(e);
// 将count自增,并且将自增前的值保存到变量c中(注意getAndIncrement返回之前旧值!)
// 这里保存自增前的值,有两层作用,1是紧接着的下面这个判断激活notFull-Condtion
// 第2个作用是判断其大小是否为0,如果为0,则代表有notEmpty.wait()的线程,则激活之
c = count.getAndIncrement();
//发现队列未满,调用notFull.signal()激活阻塞的put线程(可能存在)
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
//队列空,说明已经有take线程陷入阻塞,故调用signalNotEmpty激活阻塞的take线程
signalNotEmpty();
}
上述put方法中,比较疑惑的地方是,为什么最后要判断,当容量为0时,需要激活notEmpty-Condition阻塞的take线程?
按道理讲,只要进行了put操作,就证明肯定队列不为空了,直接进行signalNotEmpty()不可以吗?
想了想,大概原因是这样:
直接进行signalNotEmpty()可以是可以,不过性能不是最优的,因为如果之前的队列本身就不为空,则说明没有处于因notEmpty.wait()而阻塞的take线程,自然也就无需进行唤醒动作。
另外,判断之前是否有处于阻塞的take线程的方法也非常巧妙,即通过count.getAndIncrement();的返回值获得,
因getAndIncrement返回之前旧值,自然在实现count同步自增的同时,返回了之前值。
为便于理解,take代码也贴出来。
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;
}
----------
关于LinkedBlockingQueue,基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。