三十、阻塞队列之LinkedBlockingQueue

68 阅读5分钟

阻塞队列之LinkedBlockingQueue

LinkedBlockingQueue也是有界队列,可以设置容量大小。如果没有设置,默认容量是整数最大值。

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);
}

LinkedBlockingQueue的基本属性

// 链表的节点类
static class Node<E> {
	E item;
	Node<E> next;
	Node(E x) { item = x; }
}

// 链表的容量
private final int capacity;

// 链表中元素个数
private final AtomicInteger count = new AtomicInteger();

// 链表的头节点
transient Node<E> head;

// 链表的尾节点
private transient Node<E> last;

// 消费者的锁
private final ReentrantLock takeLock = new ReentrantLock();

// 消费者的Condition
private final Condition notEmpty = takeLock.newCondition();

// 生产者的锁
private final ReentrantLock putLock = new ReentrantLock();

// 生产者的Condition
private final Condition notFull = putLock.newCondition();

LinkedBlockingQueue存放元素的方法

add方法

public boolean add(E e) {
	if (offer(e))
		return true;
	else
		throw new IllegalStateException("Queue full");
}

offer非阻塞方法

  1. 判断入参是否为null,如果为null,抛出异常
  2. 判断链表是否已满,如果已满,返回false
  3. 将数据封装成Node节点
  4. 加锁
  5. 再次判断链表是否已满,如果已满,返回false
  6. 如果未满,在链表尾部添加数据
  7. 判断当前链表是否已满,如果未满,唤醒生产者
  8. 释放锁
  9. 唤醒消费者
public boolean offer(E e) {
	if (e == null) throw new NullPointerException();
	final AtomicInteger count = this.count;
	if (count.get() == capacity)
		// 链表中元素个数达到链表容量,返回false
		return false;
	int c = -1;
	Node<E> node = new Node<E>(e);
	final ReentrantLock putLock = this.putLock;
	putLock.lock();
	try {
		if (count.get() < capacity) {
			// 往链表中添加元素
			enqueue(node);
			// getAndIncrement是先返回值,再+1
			// 拿到count的值,并且将count+1
			c = count.getAndIncrement();
			if (c + 1 < capacity)
				// 当前链表中元素个数小于链表容量
				// signal方法只会将条件队列头节点放到同步队列中
				// 要唤醒同步队列中的节点,还是在unlock方法中
				notFull.signal();
		}
	} finally {
                // 唤醒生产者同步队列中的线程
		putLock.unlock();
	}
	if (c == 0)
		// 此处的c表示元素放入前,链表中元素个数
		// 进入这里说明链表中有元素了,唤醒消费者
		signalNotEmpty();
	return c >= 0;
}

private void enqueue(Node<E> node) {
	last = last.next = node;
}

private void signalNotEmpty() {
	final ReentrantLock takeLock = this.takeLock;
	takeLock.lock();
	try {
		// signal方法将消费者条件队列中节点放到同步队列中
		notEmpty.signal();
	} finally {
		// 唤醒消费者同步队列中的线程
		takeLock.unlock();
	}
}

offer阻塞方法

  1. 判断入参是否为null,如果为null,抛出异常
  2. 判断链表是否已满,如果已满,返回false
  3. 将数据封装成Node节点
  4. 加锁
  5. while循环判断链表是否已满,如果已满,判断方法等待时间是否已过
  6. 如果未过,线程阻塞一段时间,并且释放锁
  7. 线程被唤醒,尝试获取锁,获取锁后,重复5、6、7步
  8. 如果链表未满,在链表尾部添加数据
  9. 判断当前链表是否已满,如果未满,唤醒生产者
  10. 释放锁
  11. 唤醒消费者
public boolean offer(E e, long timeout, TimeUnit unit)
	throws InterruptedException {

	if (e == null) throw new NullPointerException();
	long nanos = unit.toNanos(timeout);
	int c = -1;
	final ReentrantLock putLock = this.putLock;
	final AtomicInteger count = this.count;
	putLock.lockInterruptibly();
	try {
		while (count.get() == capacity) {
			if (nanos <= 0)
				return false;
			nanos = notFull.awaitNanos(nanos);
		}
		enqueue(new Node<E>(e));
		c = count.getAndIncrement();
		if (c + 1 < capacity)
			notFull.signal();
	} finally {
		putLock.unlock();
	}
	if (c == 0)
		signalNotEmpty();
	return true;
}

put方法

  1. 判断入参是否为null,如果为null,抛出异常
  2. 判断链表是否已满,如果已满,返回false
  3. 将数据封装成Node节点
  4. 加锁
  5. while循环判断链表是否已满,如果已满,线程阻塞,并且释放锁
  6. 线程被唤醒,尝试获取锁,获取锁后,重复5、6步
  7. 如果链表未满,在链表尾部添加数据
  8. 判断当前链表是否已满,如果未满,唤醒生产者
  9. 释放锁
  10. 唤醒消费者
public void put(E e) throws InterruptedException {
	if (e == null) throw new NullPointerException();
	int c = -1;
	Node<E> node = new 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();
}

LinkedBlockingQueue获取元素的方法

remove方法

public E remove() {
	E x = poll();
	if (x != null)
		return x;
	else
		throw new NoSuchElementException();
}

poll无参方法

  1. 判断链表是否为空,如果为空,返回null
  2. 加锁
  3. 判断链表是否为空,如果为空,返回null
  4. 链表不为空,获取链表头部节点
  5. 判断链表是否不为空,如果不为空,唤醒消费者
  6. 释放锁
  7. 判断取走数据前,链表是否已满,如果已满,唤醒生产者
public E poll() {
	final AtomicInteger count = this.count;
	if (count.get() == 0)
		// 链表中没有元素,返回null
		return null;
	E x = null;
	int c = -1;
	final ReentrantLock takeLock = this.takeLock;
	takeLock.lock();
	try {
		if (count.get() > 0) {
			// 链表中有元素,获取元素
			x = dequeue();
			c = count.getAndDecrement();
			if (c > 1)
				// 及时唤醒消费者
				notEmpty.signal();
		}
	} finally {
		takeLock.unlock();
	}
	if (c == capacity)
		// 进入这里说明获取数据前,链表是满的
		// 现在取走了一个数据,链表中有空闲位置,可以唤醒生产者
		signalNotFull();
	return x;
}

private E dequeue() {
	Node<E> h = head;
	Node<E> first = h.next;
	h.next = h;
	head = first;
	E x = first.item;
	first.item = null;
	return x;
}

private void signalNotFull() {
	final ReentrantLock putLock = this.putLock;
	putLock.lock();
	try {
		notFull.signal();
	} finally {
		putLock.unlock();
	}
}

poll有参方法

  1. 判断链表是否为空,如果为空,返回null
  2. 加锁
  3. while循环判断链表是否为空,如果为空,判断方法等待时间是否已过
  4. 如果时间未过,线程阻塞一段时间,并且释放锁
  5. 线程被唤醒,尝试获取锁,获取锁后,重复执行3、4、5步
  6. 链表不为空,获取链表头部数据
  7. 判断链表是否不为空,如果不为空,唤醒消费者
  8. 释放锁
  9. 判断取走数据前,链表是否已满,如果已满,唤醒生产者
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
	E x = null;
	int c = -1;
	long nanos = unit.toNanos(timeout);
	final AtomicInteger count = this.count;
	final ReentrantLock takeLock = this.takeLock;
	// 竞争锁失败处于挂起状态时,可以响应中断
	takeLock.lockInterruptibly();
	try {
		while (count.get() == 0) {
			if (nanos <= 0)
				return null;
			nanos = notEmpty.awaitNanos(nanos);
		}
		x = dequeue();
		c = count.getAndDecrement();
		if (c > 1)
			notEmpty.signal();
	} finally {
		takeLock.unlock();
	}
	if (c == capacity)
		signalNotFull();
	return x;
}

take方法

  1. 判断链表是否为空,如果为空,返回null
  2. 加锁
  3. while循环判断链表是否为空,如果为空,线程阻塞,并且释放锁
  4. 线程被唤醒,尝试获取锁,获取锁后,重复执行3、4步
  5. 链表不为空,获取链表头部数据
  6. 判断链表是否不为空,如果不为空,唤醒消费者
  7. 释放锁
  8. 判断取走数据前,链表是否已满,如果已满,唤醒生产者
public E take() throws InterruptedException {
	E x;
	int c = -1;
	final AtomicInteger count = this.count;
	final ReentrantLock takeLock = this.takeLock;
	takeLock.lockInterruptibly();
	try {
		while (count.get() == 0) {
			notEmpty.await();
		}
		x = dequeue();
		c = count.getAndDecrement();
		if (c > 1)
			notEmpty.signal();
	} finally {
		takeLock.unlock();
	}
	if (c == capacity)
		signalNotFull();
	return x;
}