队列的主要操作就是存和取,每一种操作都有对应的多个API,虽然他们都可以实现元素的存和取,但是含义和处理方式是不同的,BlockQueue给出了以下几种处理方式:
抛异常 add(), remove()
返回特殊值: offer(), poll()
阻塞等待:put(), take()
超时等待:offer(E e,long timeout,TimeUnit unit) ,poll方法同左
取出元素但不移除: E peek()
源码分析:
LinkedListQueue底层使用链表结构来存储元素,元素入队后会被封装成Node节点,Node节点除了保存数据外,还有一个额外的指针next,因此它是一个单向链表的结构。
LinkedListQueue可以是有界队列,也可以是无界队列。数组的扩容是一件很麻烦的事情,因此ArrayBlockQueue必须指定容量,数组一经创建就不可变了,它只能做有界队列。而LinkedBolckQueue由于使用的是链表结构,元素的增删很容易实现,不需要初始化时就开辟内存,更适合做无界队列,默认的构造函数中,LinkedBlockQueue的容量为Integer.MAX_VALUE,可以看作是无界队列。
类图:它的继承结构和ArrayBlockQueue一样的,可以参考上篇文章。
ArrayBlockQueue使用int变量记录数量,而LinkedBlockQueue使用AtomicInteger原子类记录数量。这是因为ArrayBlockQueue生产消费使用同一把锁,任一时刻最多只有一个线程可以修改count。而LinkedBlcokQueue生产和消费使用的是不同的锁,两者线程都可以修改count,存在线程安全问题,因此使用原子类来保证数据安全。
head和last分别指向链表的首尾节点,takeLock和putLock分别是消费者和生产者竞争的锁,每把锁各自都有一个等待队列Condition,当队列满时,线程会被放入notFull等待,队列空时,线程会被放入notEmpty等待。
put 方法才是阻塞队列的核心方法,它会将元素入队,如果队列满了它会一直阻塞,直到操作成功为止。
take 方法队列的出队都是从头节点开始出队第一个节点。没有元素时会一直等待。阻塞。
网友评论