LinkedBlockingDeque:
内部维护的节点对象:
public class LinkedBlockingDeque<E>
extends AbstractQueue<E>
implements BlockingDeque<E>, java.io.Serializable {
static final class Node<E> {
E item;
入队:
1.初始化链表:
last = head = new Node< E >(null);Dummy节点(哨兵节点)来占位(last和head都指向dummy节点),item为null
2.第一个节点入队:
last = last.next = node; 把新节点赋值为当前last节点的后节点,并把last节点设置为当前节点
3.第二个及之后的节点入队:
last = last.next = node;
出队:
Node<E> h = head;
Node<E> first = h.next;
h.next = h;
head = first;
E x = first.item;
first.item = null;
return x;
1. Node h = head
将临时变量h赋值给头节点
2. first = h.next;
将临时变量first赋值给h的下一个
3. h.next = h
将h的next指向自己,目的是不让next指向其他节点,保证此节点能安全的被垃圾回收
4. head = first
5.
E x = first.item;
first.item = null;
return x;
加锁分析:
高明之处在于加了2把锁:
- 用一把锁,同一时刻,最多只允许有一个线程(生产者或消费者,二选一)执行
- 用两把锁,同一时刻可以允许2个线程同事(一个生产者一个消费者)执行
- 消费者与消费者线程仍然串行
- 生产者与生产者线程仍然串行
线程安全分析:
- 当节点总数大于2时(包括dummy节点),putLoc保证的是last节点的线程安全,takeLock保证的是head节点的线程安全。两把锁保证了入队和出队没有竞争
- 当节点总数等于2时(即一个dummy节点,一个正常节点)这时候,仍然是两把锁锁两个对象,不会竞争
- 当节点总数等于1时(就一个dummy节点),这时take线程会被noteEmpty条件阻塞,有竞争,会阻塞
private final ReentrantLock putLock = new ReentrantLock();
private final ReentrantLock takeLock = new ReentrantLock();
put操作
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
LinkedBlockingQueue.Node<E> node = new LinkedBlockingQueue.Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
take操作
和put类型,不做分析
和Array性能比较:
主要列举LinkedBlockingQueue(推荐)和ArrayBlockingQueue的性能比较
- Linked支持有界,Array强制有界
- Linked实现是链表,Array是数组
- Linked是懒惰的,而Array需要提前初始化Node数组
- Linked每次入队会生成新Node,而Array的Node是提前创建好的
- Linked两把锁,Array一把锁
ConcurrentLinkedQueue:
ConcurrentLinkedQueue的设计和LinkedBlockingQueue非常像,也是:
- 两把锁,同一时刻,可以允许两个线程(一个生产者一个消费者)执行
- dummy节点的引入让两把锁将来锁住的是不同对象,避免竞争
- 只是这锁使用了cas来实现
事实上,ConcurrentLinkedQueue应用还是非常广泛的: 例如之前讲的Tomcat的Connector结构时,Acceptor作为生产者向Poller消费者传递事件信息时,正是采用了ConcurrentLinkedQueue将ScoketChannel给Poller使用:
|