首先来看看LinkedBlockingQueue类的定义
public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable
java文档中对于 LinkedBlockingQueue类的描述如下:
LinkedBlockingQueue是一个基于线性结点拥有一个可选的边界的阻塞队列。这个队列遵守先进先出原则(FIFO),在头(head)中的结点是存在队列时间最长的结点,在尾(tail)的结点则是存在队列时间最短的结点。新插入的结点都放置在队列的尾部,检索的结点都是在头部。基于线性结点的阻塞队列拥有比基于数组的阻塞队列更高的吞吐量,但是降低了性能。
可选的容量边界防止了整个阻塞队列过度的膨胀。如果没有指定容量,那么这个阻塞队列的容量将是Integer.MAX_VALUE.
这个类实现了Collection和Iterator两个接口所有的可选方法。
使用LinkedBlockingQueue可以很好的解决生产者-消费者的模型,当生产过程和消费过程不均的时候,可以通过阻塞的方法使用双边达到平衡。另一方面,可以不加锁的方式操作同一个数据对象,而不变担心多线程导致的数据不一致的问题。
下面使用一个例子来说明一下LinkedBlockingQueue的使用,采用30个线程向只有20容量的队列中添加数据,另外采用2个线程去消费
package com.app.bean; public class Student { private String stu_name = ""; public Student(String name) { this.stu_name = name; } @Override public String toString() { return "student_nu:" + stu_name; } }
package com.app.blockingqueue; import java.util.concurrent.LinkedBlockingQueue; import com.app.bean.Student; public class LinkedBlockingQueueTest { public static void main(String[] args) { final LinkedBlockingQueue<Student> queue = new LinkedBlockingQueue<Student>( 10); // create 20 threads to put elements for (int i = 0; i < 30; i++) { final int index = i; new Thread(new Runnable() { @Override public void run() { try { queue.put(new Student("" + index)); System.out.println("-----------put element:" + index); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } // only create 2 threads to get elements for (int i = 0; i < 2; i++) { new Thread(new Runnable() { @Override public void run() { while (true) { try { Student stu = queue.take(); System.out.println("get element:" + stu.toString()); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } } }
返回的结果可以看到,put与take操作交互进行直到所有的数据操作完成。
下面来看看LinkedBlockingQueue的内部构造。既然是线性表,那么必就会有一个线性结点。
static class Node<E> { E item; /** * One of: * - the real successor Node * - this Node, meaning the successor is head.next * - null, meaning there is no successor (this is the last node) */ Node<E> next; Node(E x) { item = x; } }
该内部类Node就是线性表的内部结点的定义。包含了一个值的定义,和一个指向下个结点的后继。
private final int capacity; /** Current number of elements */ private final AtomicInteger count = new AtomicInteger(0); /** * Head of linked list. * Invariant: head.item == null */ private transient Node<E> head; /** * Tail of linked list. * Invariant: last.next == null */ private transient Node<E> last; /** Lock held by take, poll, etc */ private final ReentrantLock takeLock = new ReentrantLock(); /** Wait queue for waiting takes */ private final Condition notEmpty = takeLock.newCondition(); /** Lock held by put, offer, etc */ private final ReentrantLock putLock = new ReentrantLock(); /** Wait queue for waiting puts */ private final Condition notFull = putLock.newCondition();
capacity: 代表了当前链表的最大容量
count:代表了当前链表中实际的容量
head:代表了此链表的头结点,该结点中的item = null
last:代表了最后一个结点
takLock:取数据锁
notEmpty:取数据时的条件对象,当链表没有数据的时候,调用await()方法挂起线程
putLock:放数据锁
notFull:放数据时的条件对象,当链表数据满了的时候,调用await()方法挂起线程
private void signalNotEmpty() { final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { notEmpty.signal(); } finally { takeLock.unlock(); } }
通知一个线程,当前的链表容量不为空,可以进行取操作。将该线程从等待集中移除。
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; }
插入操作的核心操作,将当前的last结点修改为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; }
将当前head结点的next结点设置为head结点,并且设置item=null,返回该item。
/** * Lock to prevent both puts and takes. */ void fullyLock() { putLock.lock(); takeLock.lock(); } /** * Unlock to allow both puts and takes. */ void fullyUnlock() { takeLock.unlock(); putLock.unlock(); }
fullLock()和fullUnLock()方法,分别对插入和提取操作加锁和解锁。
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); } public LinkedBlockingQueue(Collection<? extends E> c) { this(Integer.MAX_VALUE); final ReentrantLock putLock = this.putLock; putLock.lock(); // Never contended, but necessary for visibility 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(); } }
构造函数,带一个容量参数的构造函数,会设置一个head结点和last结点,并且这两个结点指向同一个对象,传递的参数则设置给capacity设置为容量。带集合类型的参数,则是设置容量为Integer.MAX_VALUE,并且将集合中的数据全部添加到线性表中,并且设置当前的大小,也就是count的大小。
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; }
offer的该方法实现了计时等待,等待的时间是通过while循环去控制的,如果当前的容量满的时候,while循环回去不断的统计timeout - nanos消耗的值,当这个值小于等于0的时候,并且这个时候,阻塞队列依旧满的时候,返回一个false值;如果阻塞队列不满的时候,调用enquue()方法,添加数据。
其他的方法几个方法,与该方法类似,不再过多赘述。