基于jdk1.8进行分析的。
下面我们来看BlockingQueue接口的第二个实现类LinkedBlockingQueue。
类继承结构
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable
继承了AbstractQueue类,实现了BlockingQueue接口以及Serializable接口。
内部类
Node
静态内部类
static class Node<E> {
//元素
E item;
//下一个元素指针
Node<E> next;
//构造方法
Node(E x) { item = x; }
}
成员属性
//容量边界,如果没有指定,则为Integer.MAX_VALUE
private final int capacity;
//当前队列中存储元素的数量
private final AtomicInteger count = new AtomicInteger();
//队列的头结点,且始终head.item=null,即此头结点不存储任何元素
transient Node<E> head;
//队列的尾结点,且始终last.next=null.
private transient Node<E> last;
//take操作时所需要加的锁
private final ReentrantLock takeLock = new ReentrantLock();
//当take操作时如果队列中存储的元素为空,则调用此Condition的await方法
private final Condition notEmpty = takeLock.newCondition();
//put操作时所需要加的锁和条件
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
我们可以看到和之前ArrayBlockingQueue的区别在于这里的锁和条件都是针对固定的,比如take操作和put操作,而前面则是统一的一个Lock和Condition。
构造方法
LinkedBlockingQueue()
默认构造方法
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
指定大小默认为,Integer.MAX_VALUE调用其他构造方法实现的。
LinkedBlockingQueue(int capacity)
public LinkedBlockingQueue(int capacity) {
//数据校验,不通过,直接抛出异常
if (capacity <= 0) throw new IllegalArgumentException();
//初始化容量大小,并赋值
this.capacity = capacity;
//创建一个空的链表
last = head = new Node<E>(null);
}
LinkedBlockingQueue(Collection<? extends E> c)
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();
}
}
通过集合来初始化一个链表形式的阻塞队列,当然我们看到也是通过调用上面的构造方法实现的。
核心方法
put(E e)
添加元素操作
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.
//用此局部变量c持有一个负数来指示CAS操作是否操作成功
//c = count.getAndIncrement();//利用原子性加一
int c = -1;
//创建一个Node节点
Node<E> node = new Node<E>(e);
//获取put锁
final ReentrantLock putLock = this.putLock;
//记录当前元素数量
final AtomicInteger count = this.count;
//获取锁,考虑锁中断
putLock.lockInterruptibly();
try {
//元素个数满了
while (count.get() == capacity) {
//等待,知道链表有空余位置
notFull.await();
}
//入队操作当然也是加入到链表中,后面分析看
enqueue(node);
//操作成功,更新count,并用c来保存
c = count.getAndIncrement();
//更新成功判断容量,如果下面成立
if (c + 1 < capacity)
//唤醒等待线程
notFull.signal();
} finally {
//释放锁
putLock.unlock();
}
//队列或者链表为空的情况,刚添加一个元素的情况
if (c == 0)
signalNotEmpty();
}
enqueue(Node<E> node)
入队操作实现方法
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
通过源码分析来看,就是正常的链表操作实现。
signalNotEmpty()
private void signalNotEmpty() {
//记录当前take锁
final ReentrantLock takeLock = this.takeLock;
//上锁
takeLock.lock();
try {
//唤醒线程,来取元素
notEmpty.signal();
} finally {
//释放锁
takeLock.unlock();
}
}
我们看一下其他添加元素方法。
offer(E e)
用来添加元素,我们可以看到下方代码中,同样存在多线程的操作,而我们似乎也记得在ArrayBlockingQueue里面只是对是否成功添加元素直接返回对应的布尔值,不存在对应的操作。(因代码实现较为类似,此处不再一一注释)
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
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);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
下面我们来看一下take方法的源码实现。
take()
public E take() throws InterruptedException {
//声明变量,用于保存返回值
E x;
//同样用来记录count更新是否成功,是一个CAS操作
int c = -1;
//记录当前count值
final AtomicInteger count = this.count;
//记录当前take锁
final ReentrantLock takeLock = this.takeLock;
//获取锁,考虑终端
takeLock.lockInterruptibly();
try {
//如果链表为空
while (count.get() == 0) {
//线程等待
notEmpty.await();
}
//出队操作,后面分析
x = dequeue();
//更新count,进行cas操作
c = count.getAndDecrement();
//如果还有元素,唤醒其他线程
if (c > 1)
notEmpty.signal();
} finally {
//释放锁
takeLock.unlock();
}
//如果c的容量是等于capacity,又被消费了一个,因此可以通知生产者线程来进行生产
if (c == capacity)
signalNotFull();
//返回出队的元素
return x;
}
dequeue()
出队操作,这里是链表的正常移除元素的操作。
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
//记录头结点
Node<E> h = head;
//记录第一个元素
Node<E> first = h.next;
//链表操作,将头节点向后移动,返回第一个元素的item
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
}
signalNotFull()
private void signalNotFull() {
//获取put锁
final ReentrantLock putLock = this.putLock;
//上锁
putLock.lock();
try {
//唤醒消费线程
notFull.signal();
} finally {
//释放锁
putLock.unlock();
}
}
以上就是整个核心方法的源码分析过程了。
可以看到该类不像ArrayBlockingQueue那样,内部维护了两个指针take和put,取而代之的是通过链表结构和链表操作实现的阻塞队列。下面看一个DEMO演示。
package demo;
import java.util.concurrent.LinkedBlockingQueue;
/**
* LinkedBLockingQueue测试例程
* @ClassName: LinkedBlockingQueueDemo
* @Description: TODO
* @author BurgessLee
* @date 2019年5月5日
*
*/
public class LinkedBlockingQueueDemo {
public static void main(String[] args) {
//此处不使用默认构造方法,方便例程演示
LinkedBlockingQueue<Integer> linkedBlockingQueue = new LinkedBlockingQueue<Integer>(3);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +"-starting....");
for(int i = 0; i < 10; i ++) {
try {
System.out.println(Thread.currentThread().getName() +"-puting element ....i=" + i);
linkedBlockingQueue.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() +"-end....");
}
},"thread0").start();
// try {
// Thread.sleep(4000);
// } catch (InterruptedException e1) {
// e1.printStackTrace();
// }
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +"-starting....");
for(int i = 0; i < 10; i ++) {
try {
Integer result = linkedBlockingQueue.take();
System.out.println(Thread.currentThread().getName() +"-taking element ....result=" + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() +"-end....");
}
},"thread1").start();
}
}
输出结果:
thread0-starting....
thread0-puting element ....i=0
thread0-puting element ....i=1
thread0-puting element ....i=2
thread0-puting element ....i=3
thread1-starting....
thread0-puting element ....i=4
thread1-taking element ....result=0
thread1-taking element ....result=1
thread0-puting element ....i=5
thread1-taking element ....result=2
thread0-puting element ....i=6
thread1-taking element ....result=3
thread0-puting element ....i=7
thread1-taking element ....result=4
thread0-puting element ....i=8
thread1-taking element ....result=5
thread0-puting element ....i=9
thread1-taking element ....result=6
thread0-end....
thread1-taking element ....result=7
thread1-taking element ....result=8
thread1-taking element ....result=9
thread1-end....
以上就是整个演示过程了。如果有不对的地方,还请指正,感谢阅读。