翻译
类注释
1 | /** |
源码
类结构
说明:
- ArrayBlockingQueue继承于AbstractQueue,并且它实现了BlockingQueue接口。
- ArrayBlockingQueue内部是通过Object[]数组保存数据的,也就是说ArrayBlockingQueue是一个基于数组的阻塞并发队列,并且在初始化的时候必须指定整个容器的大小(也就是成员变量数组的大小),并且后面也会知道,整个容器是不会扩容的。
- ArrayBlockingQueue与ReentrantLock是组合关系,ArrayBlockingQueue中包含一个ReentrantLock对象(lock)。ReentrantLock是可重入的互斥锁,ArrayBlockingQueue就是根据该互斥锁实现“多线程对竞争资源的互斥访问”。而且,ReentrantLock分为公平锁和非公平锁,关于具体使用公平锁还是非公平锁,在创建ArrayBlockingQueue时可以指定,并且默认使用的是非公平锁。
ArrayBlockingQueue与Condition是组合关系,ArrayBlockingQueue中包含两个Condition对象(notEmpty和notFull)。而且,Condition又依赖于ArrayBlockingQueue而存在,通过Condition可以实现对ArrayBlockingQueue的更精确的访问
Condition的signal(),await()需要使用在lock,unlock之间才有效。
重要属性
1 | /** The queued items */ |
说明:
- 前面类注释提到 Once created, the capacity cannot be changed
那items这个成员变量修饰符上应该有一个final修饰吧。关于final可以申明时赋值或者构造里赋值。那这个肯定是构造时赋的值了??
构造方法
一共有3个,这里说其中2个:
1 | public ArrayBlockingQueue(int capacity) { |
说明:
- Lock的作用是提供独占锁机制,来保护竞争资源;
- Condition是为了更加精细的对锁进行控制,它依赖于Lock,通过某个条件对多线程进行控制。notEmpty表示“锁的非空条件”。当某线程想从队列中取数据时,而此时又没有数据,则该线程通过notEmpty.await()进行等待;当其它线程向队列中插入了元素之后,就调用notEmpty.signal()唤醒“之前通过notEmpty.await()进入等待状态的线程”。
同理,notFull表示“锁的满条件”。当某线程想向队列中插入元素,而此时队列已满时,该线程等待;当其它线程从队列中取出元素之后,就唤醒该等待的线程。
(01)若某线程(线程A)要取数据时,数组正好为空,则该线程会执行notEmpty.await()进行等待;当其它某个线程(线程B)向数组中插入了数据之后,会调用notEmpty.signal()唤醒“notEmpty上的等待线程”。此时,线程A会被唤醒从而得以继续运行。
(02)若某线程(线程H)要插入数据时,数组已满,则该线程会它执行notFull.await()进行等待;当其它某个线程(线程I)取出数据之后,会调用notFull.signal()唤醒“notFull上的等待线程”。此时,线程H就会被唤醒从而得以继续运行。
接下来看类注释中提到的方法put,take
put
1 | public void put(E e) throws InterruptedException { |
take
1 | public E take() throws InterruptedException { |
至此,我们从类注释上该了解的内容就是这些了。。
遗留问题
- final Object[] items = this.items;
为什么enqueue方法里要声明items,去引用成员变量??有人说是线程安全,但是方法里的变量不都有自己的线程栈吗?而且前后也使用了独占锁进行lock啊,说明里面就一个线程在玩啊
Object[] items = this.items;这样可以不呢??为什么方法还要声明为final? 改成如下会有什么问题吗????谁写个测试1
2
3
4
5
6
7private void enqueue(E x) {
this.items[putIndex] = x;
if (++putIndex == this.items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
本站评论搭建在 Github Issue 上,请点击进行评论。