JDK容器与并发—Queue—LinkedBlockingQueue

概述

      基于单链表的有界或无界阻塞队列。

1)FIFO;

2)可用带容量参数的构造函数设置队列的容量,否则为Integer.MAX_VALUE;

3)在大部分并发场景中,基于链表的队列比基于数组的队列有更高的吞吐量,但性能的预测性更低。

数据结构

      单链表,一把take锁,一个take条件,一把put锁,一个put条件:

// 链表首节点
// head.item == null
private transient Node<E> head;

// 链表尾节点
// last.next == null
private transient Node<E> last;

// takeLock
private final ReentrantLock takeLock = new ReentrantLock();

// 队列为空时,在takeLock上的等待
private final Condition notEmpty = takeLock.newCondition();

// putLock
private final ReentrantLock putLock = new ReentrantLock();

// 队列已满时,在putLock上的等待
private final Condition notFull = putLock.newCondition();

static class Node<E> {
	E item;

	 // 取值有三种情况:
	 // next为后继节点或this(表明后继节点为head.next)或null(表明其为链表尾节点)
	Node<E> next;

	Node(E x) { item = x; }
}

构造器

// 无参构造
// 无界链表:Integer.MAX_VALUE
public LinkedBlockingQueue() {
	this(Integer.MAX_VALUE);
}

// 带容量参数构造
public LinkedBlockingQueue(int capacity) {
	if (capacity <= 0) throw new IllegalArgumentException();
	this.capacity = capacity;
	last = head = new Node<E>(null);
}

// 带Collection参数构造,将其中元素入队
// 无界链表:Integer.MAX_VALUE
public LinkedBlockingQueue(Collection<? extends E> c) {
	this(Integer.MAX_VALUE);
	final ReentrantLock putLock = this.putLock;
	putLock.lock(); // 为了可见性加锁,这里不会有竞争
	try {
		int n = 0;
		for (E e : c) {
			if (e == null)
				throw new NullPointerException();
			if (n == capacity)
				throw new IllegalStateException("Queue full");
			enqueue(new Node<E>(e));
			++n;
		}
		count.set(n);
	} finally {
		putLock.unlock();
	}
}

增删查

基础方法

// notEmpty signal,在put/offer中调用
private void signalNotEmpty() {
	final ReentrantLock takeLock = this.takeLock;
	takeLock.lock();
	try {
		notEmpty.signal();
	} finally {
		takeLock.unlock();
	}
}

// notFull signal,在take/poll中调用
private void signalNotFull() {
	final ReentrantLock putLock = this.putLock;
	putLock.lock();
	try {
		notFull.signal();
	} finally {
		putLock.unlock();
	}
}

// 节点入队
private void enqueue(Node<E> node) {
	// assert putLock.isHeldByCurrentThread();
	// assert last.next == null;
	last = last.next = node;
}

// 将链表首节点出队
private E dequeue() {
	// assert takeLock.isHeldByCurrentThread();
	// assert head.item == null;
	Node<E> h = head;
	Node<E> first = h.next;
	h.next = h; // help GC
	head = first;
	E x = first.item;
	first.item = null;
	return x;
}

      步骤(注意:offer非阻塞版,在获取putLock前先检测队列是否已满,若已满则立即返回false,否则获取putLock):
1)获取putLock;
2)若队列未满,则才将节点入队,成功后若队列还未满,向put/offer线程发送notFull信号;
3)若队列已满,put一直等到队列未满,offer阻塞版则等待timeout时间,超出则立即返回false;
4)释放putLock锁。
5)若入队成功且c==0,则获取takeLock锁,向take/poll线程发送notEmpty信号,再释放takeLock锁。

public void put(E e) throws InterruptedException {
	if (e == null) throw new NullPointerException();
	int c = -1; // 用局部变量标识结果,若返回前为-1,则表明操作失败。put/take线程都采用该方法。
	Node<E> node = new Node(e);
	final ReentrantLock putLock = this.putLock;
	final AtomicInteger count = this.count;
	putLock.lockInterruptibly();		  // 获取到putLock锁前,可中断的锁的获取  
	try {
		while (count.get() == capacity) { // 队列已满,则一直wait
			notFull.await();
		}
		enqueue(node);					  // 将节点入队
		c = count.getAndIncrement();
		if (c + 1 < capacity)			  // 队列仍未满
			notFull.signal();			  // 向其他put/offer线程发送notFull信号
	} finally {
		putLock.unlock();				  // 释放putLock
	}
	if (c == 0)							  // 入队成功
		signalNotEmpty();				  // 获取takeLock锁,向take/poll线程发送notEmpty信号,再释放takeLock锁。
}

      与增的过程对立。

      步骤(注意:poll非阻塞版,在获取takeLock前先检测队列是否为空,若为空则立即返回null,否则获取takeLock):
1)获取takeLock;
2)若队列非空,则将链表首节点出队,成功后若队列还未空,向take/poll线程发送notEmpty信号;
3)若队列为空,take一直等到队列未满,poll阻塞版则等待timeout时间,超出则立即返回null;
4)释放takeLock锁。
5)若出队成功且c==capacity,则获取putLock锁,向put/offer线程发送notFull信号,再释放putLock锁。

public E take() throws InterruptedException {
	E x;
	int c = -1; // 用局部变量标识结果,若返回前为-1,则表明操作失败。
	final AtomicInteger count = this.count;
	final ReentrantLock takeLock = this.takeLock;
	takeLock.lockInterruptibly();		// 获取到takeLock锁前,可中断的锁的获取 
	try {
		while (count.get() == 0) {		// 队列为空,则一直wait
			notEmpty.await();
		}
		x = dequeue();					// 将链表首节点出队
		c = count.getAndDecrement();
		if (c > 1)						// 队列仍未满
			notEmpty.signal();			// 向其他take/poll线程发送notEmpty信号
	} finally {
		takeLock.unlock();				// 释放takeLock
	}
	if (c == capacity)					// 出队成功
		signalNotFull();				// 获取putLock锁,向put/offer线程发送notFull信号,再释放putLock锁。
	return x;
}

步骤:
1)先检测队列是否为空,若为空则立即返回null;
2)否则获取takeLock;
3)获取链表首节点,若为null,则返回null;否则返回item值;
4)释放takeLock锁。

// 获取链表首元素
public E peek() {
	if (count.get() == 0)
		return null;
	final ReentrantLock takeLock = this.takeLock;
	takeLock.lock();
	try {
		Node<E> first = head.next;
		if (first == null)
			return null;
		else
			return first.item;
	} finally {
		takeLock.unlock();
	}
}

迭代器

      弱一致性。

private class Itr implements Iterator<E> {
	/*
	 * Basic weakly-consistent iterator.  At all times hold the next
	 * item to hand out so that if hasNext() reports true, we will
	 * still have it to return even if lost race with a take etc.
	 */
	private Node<E> current;
	private Node<E> lastRet;
	private E currentElement;

	Itr() {
		fullyLock();
		try {
			current = head.next;
			if (current != null)
				currentElement = current.item;
		} finally {
			fullyUnlock();
		}
	}

	public boolean hasNext() {
		return current != null;
	}

	/**
	 * Returns the next live successor of p, or null if no such.
	 *
	 * Unlike other traversal methods, iterators need to handle both:
	 * - dequeued nodes (p.next == p)
	 * - (possibly multiple) interior removed nodes (p.item == null)
	 */
	private Node<E> nextNode(Node<E> p) {
		for (;;) {
			Node<E> s = p.next;
			if (s == p)
				return head.next;
			if (s == null || s.item != null)
				return s;
			p = s;
		}
	}

	public E next() {
		fullyLock();
		try {
			if (current == null)
				throw new NoSuchElementException();
			E x = currentElement;
			lastRet = current;
			current = nextNode(current);
			currentElement = (current == null) ? null : current.item;
			return x;
		} finally {
			fullyUnlock();
		}
	}

	public void remove() {
		if (lastRet == null)
			throw new IllegalStateException();
		fullyLock();
		try {
			Node<E> node = lastRet;
			lastRet = null;
			for (Node<E> trail = head, p = trail.next;
				 p != null;
				 trail = p, p = p.next) {
				if (p == node) {
					unlink(p, trail);
					break;
				}
			}
		} finally {
			fullyUnlock();
		}
	}
}

特性

      实际上put/offer操作与take/poll操作是通过两把锁、两个条件:putLock、notFull、takeLock、notEmpty来进行协作的,来实现LinkedBlockingQueue的功能。

LinkedBlockingQueue的读与写如何保证可见性?

      当节点入队时,需要获取putLock,count增加,后续的读操作可以通过获取putLock锁(通过获取全锁:putLock+takeLock)或获取takeLock锁,读n = count.get(),根据happens-before原则,这样就可以读取到新入队的n个节点。

疑问

      为什么其更高的吞吐量,但性能的预测性更低?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值