在上一章我们讲解了ArrayBlockingQueue,用数组形式实现的阻塞队列。
数组的长度在创建时就必须确定,如果数组长度小了,那么ArrayBlockingQueue队列很容易就被阻塞,如果数组长度大了,就容易浪费内存。
而队列这个数据结构天然适合用链表这个形式,而LinkedBlockingQueue就是使用链表方式实现的阻塞队列。
一. 链表实现
1.1 Node内部类
/**
* 链表的节点,同时也是通过它来实现一个单向链表
*/
static class Node {
E item;
// 指向链表的下一个节点
Node next;
Node(E x) { item = x; }
}
有一个变量e储存数据,有一个变量next指向下一个节点的引用。它可以实现最简单地单向列表。
1.2 怎样实现链表
/**
* 它的next指向队列头,这个节点不存储数据
*/
transient Node head;
/**
* 队列尾节点
*/
private transient Node last;
要实现链表,必须有两个变量,一个表示链表头head,一个表示链表尾last。head和last都会在LinkedBlockingQueue对象创建的时候被初始化。
last = head = new Node(null);
注意这里用了一个小技巧,链表头head节点并没有存放数据,它指向的下一个节点,才真正存储了链表中第一个数据。而链表尾last的确储存了链表最后一个数据。
1.3 插入和删除节点
/**
* 向队列尾插入节点
*/
private void enqueue(Node node) {
// assert putLock.isHeldByCurrentThread(); // 当前线程肯定获取了putLock锁
// 将原队列尾节点的next引用指向新节点node,然后再将node节点设置成队列尾节点last
// 这样就实现了向队列尾插入节点
last = last.next = node;
}
在链表尾插入节点很简单,将原队列尾last的下一个节点next指向新节点node,再将新节点node赋值给队列尾last节点。这样就实现了插入一个新节点。
// 移除队列头节点,并返回被删除的节点数据
private E dequeue() {
// assert takeLock.isHeldByCurrentThread(); // 当前线程肯定获取了takeLock锁
// assert head.item == null;
Node h = head;
// first节点中才存储了队列中第一个元素的数据
Node first = h.next;
h.next = h; // help GC
// 设置新的head值,相当于删除了first节点。因为head节点本身不储存数据
head = first;
// 队列头的数据
E x = first.item;
// 移除原先的数据
first.item = null;
return x;
}
要注意head并不是链表头,它的next才是指向链表头,所以删除链表头也很简单,就是将head.next赋值给head,然后返回原先head.next节点的数据。
删除的时候,就要注意链表为空的情况。head.next的值使用enqueue方法添加的。当head.next==last的时候,表示已经删除到最后一个元素了,当head.next==null的时候,就不