LinkedBlockingQueue是基于链表的阻塞式队列;其默认初始化长度为Integer.MAX_VALUE,也可以指定长度;
public LinkedBlockingQueue() { this(Integer.MAX_VALUE);// MAX_VALUE = 0x7fffffff;
} public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node<E>(null); }
其内部有一个Node的静态内部类,其中有一个成员变量 Node next。就这样形成了一个链表的结构,要获取下一个元素,只要调用next就可以了。
static class Node<E> { E item; Node<E> next; Node(E x) { item = x; } }
LinkedBlockingQueue内部读写(插入获取)各有一个锁,添加和删除不互斥,可同时进行
private final ReentrantLock takeLock = new ReentrantLock(); /** Lock held by put, offer, etc */ private final ReentrantLock putLock = new ReentrantLock();
变量:
private final AtomicInteger count = new AtomicInteger();//记录队列中的元素个数 transient Node<E> head;//阻塞队列的头部节点 private transient Node<E> last;//阻塞队列的尾部节点 private final Condition notEmpty = takeLock.newCondition();//notEmpty条件对象,当队列无数据时用于挂起删除线程 /** Wait queue for waiting puts */ private final Condition notFull = putLock.newCondition();//当队列满时,用于挂起添加线程
添加元素的方法有三个:add,put,offer,且这三个元素都是向队列尾部添加元素的意思。
区别:
add方法在添加元素的时候,若超出了度列的长度会直接抛出异常:
put方法,若向队尾添加元素的时候发现队列已经满了会发生阻塞一直等待空间,以加入元素。poll: 若队列为空,返回null。
remove:若队列为空,抛出NoSuchElementException异常。
take:若队列为空,发生阻塞,等待有元素。
添加时:尾部添加
public void put(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); // Note: convention in all put/take/etc is to preset local var // holding count negative to indicate failure unless set. 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();//如果还存在元素,唤醒消费者线程 }
private void enqueue(Node<E> node) { // assert putLock.isHeldByCurrentThread(); // assert last.next == null; last = last.next = node;//尾部节点指向新的节点 }
private void signalNotEmpty() { final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { notEmpty.signal();//唤醒删除元素的线程 } finally { takeLock.unlock(); } }删除或是取出元素:
public E poll() { final AtomicInteger count = this.count;//队列中元素个数 if (count.get() == 0)//个数为0, 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() { // assert takeLock.isHeldByCurrentThread(); // assert head.item == null; Node<E> h = head;//头部节点 Node<E> first = h.next;//下一个节点 h.next = h; // help GC//自己next指向自己 head = first;//将下一个节点变为头部节点 E x = first.item;//将头部节点取出 first.item = null;//清空数据 return x; }
LinkedBlockingQueue和ArrayBlockingQueue区别
1.队列大小不同,ArrayBlockingQueue是有界的,初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。
2.数据存储容器不同,ArrayBlockingQueue采用的是数组作为数据存储容器,而LinkedBlockingQueue采用的则是以Node节点作为连接对象的链表。
3.由于ArrayBlockingQueue采用的是数组的存储容器,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象。这可能在长时间内需要高效并发地处理大批量数据的时,对于GC可能存在较大影响。
4.两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReenterLock锁,而LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。