先说下 put 和 take 这2个 会让线程 处于watting状态的方法
arraybockingQueue 是基于 数组 和 读写指针
还有 condition 实现的 condition 是aqs的内部类
aqs 内部类中 Node 可以理解为 对象监视器monitor中的 entry set 入口集
而condition 可以理解为 wait set 等待集
不过 condition 是可以允许有多个的。
[Node 也是condition的实现 只不过是单向链表而 CLH 同步队列是 双向链表]
其中子所以有2个 条件等待 队列 是因为 在同步队列CLH队列上 等待唤醒的节点
有可能是 take 也有可能是put 如果 unlock 唤醒 nextNode.Thread 的话
有可能put满了 还是put的线程 那么 为了防止这样出现 所以
做一个where 判断 如果put满了 那么从 clh队列 加入 put condition 然后又 从
put condition 移到 clh 队列 等待唤醒
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();// 获取独占式 非公平锁 默认 这个是可中断的
// 如果 唤醒之时不是 unpark唤醒的直接抛出异常
try {
while (count == items.length) // count 代表了 数组中 放入object 的个数
notFull.await(); // 如果一直没有人消费 那么停止 生产 进入等待链表中等待 被唤醒
// 被唤醒后 进入insert 如果上面被唤醒 count 不等于length 那么
// 一定是可以 insert的
enqueue(e);
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
//赋值
items[putIndex] = x;
// 可以把 这个看成一个圆形 每put 一次 那么下次的 put指针就加一
// 和 netty中的bytebuf中的读写指针 或者说和 nio中的 bytebuff 的positing 类似
// 每次put后 item个数加1 然后唤醒 在take 等待的节点移动到clh队列中等待唤醒
// put take 都是按顺序 的 所以只要拿到锁 count != 数组的长度 那么一定可以put成功
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
// 这几个方法都一样 没什么好说的了
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
// 取出来 然后 读take 指针 移动
final Object[] items = this.items;
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}