基于jdk1.8源码进行分析的。
之后有三四篇博文都是针对BlockingQueue接口的实现类进行源码分析。阻塞队列在我的博文分类-多线程里面也有一些相关方面的分析介绍,这里是基于具体实现类的源码分析。下面开始正题。
首先看一下BlockingQueue接口源码。
public interface BlockingQueue<E> extends Queue<E> {
//添加元素
boolean add(E e);
//添加元素
boolean offer(E e);
//添加元素
void put(E e) throws InterruptedException;
//添加元素
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
//取出元素
E take() throws InterruptedException;
//取出元素
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
//返回的是不阻塞的元素的数量
int remainingCapacity();
//取出元素
boolean remove(Object o);
//批量获取元素
int drainTo(Collection<? super E> c);
//批量获取元素
int drainTo(Collection<? super E> c, int maxElements);
}
整体接口定义上我们看到类的继承结构,该接口继承了Queue接口,我们都知道Queue是Collection接口下面的一个子接口,或者说Queue是继承自Collection接口,Collection接口我们都很熟悉,因为我们常用的List,Set都是继承自它。
BlockingQueue--阻塞队列,不仅支持队列操作,而且当出现下面情况的时候,他也能处理:
- 当获取元素时,如果队列为空,则一直等待直到队列非空。
- 当存储元素时,如果队列中没有空间进行存储,则一直等待直到有空间进行存储。
所以这也正好体现了为什么叫做阻塞队列。
当然在还有方法element和peek用于检查队列中的元素,也是队列操作常见的接口,熟悉数据结构的话,应该对此操作并不陌生。下面我们先看第一个实现类:ArrayBlockingQueue的源码分析。
类继承结构
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable
可以看到该类继承了AbstractQueue类,实现了BlockingQueue接口和Serializable接口。
成员属性
//用来存储元素的数组
final Object[] items;
//下一次执行task,poll,peek或者remove的索引
int takeIndex;
//下一次执行put,offer,或者add的索引
int putIndex;
//当前数组中存放的元素数量
int count;
//提供的lock锁
final ReentrantLock lock;
//队列空了,就挂起,等到有元素放进去后唤醒
private final Condition notEmpty;
//队列满了就挂起,等到取出一个元素后唤醒
private final Condition notFull;
构造方法
ArrayBlockingQueue(int capacity)
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
可以看到调用了另外一个构造方法
ArrayBlockingQueue(int capacity, boolean fair)
public ArrayBlockingQueue(int capacity, boolean fair) {
//数据校验,不符合,抛出异常
if (capacity <= 0)
throw new IllegalArgumentException();
//final Object[] items;
//Object数组
this.items = new Object[capacity];
//lock锁,每次创建该队列,就会有一个lock锁
lock = new ReentrantLock(fair);
//两个额外的对应的挂起条件
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)
通过集合进行批量初始化。该构造方法也是通过调用上面的构造方法实现的。
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); // Lock only for visibility, not mutual exclusion
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
我们源码看到这里可以简单总结一下:
- 该类是通过数组实现的,因为有Object数组
- 该类动态维护了两个索引,putIndex和takeIndex两个索引
- 该类没新创建一个实例就会有创建一个新的ReentrantLock锁,和Condition
- 初始化的时候可以看到,可以让用户自定义一个布尔值,在构造方法里面我们可以看到是用来创建非公平锁还是公平锁
我们下面继续。
核心方法
put(E e)
public void put(E e) throws InterruptedException {
//数据合法性校验
checkNotNull(e);
//获取锁
final ReentrantLock lock = this.lock;
//后面分析
lock.lockInterruptibly();
try {
//队列满了
while (count == items.length)
//等待
notFull.await();
//否则入队--后面分析
enqueue(e);
} finally {
//释放锁
lock.unlock();
}
}
里面调用的到了checkNotNull,源码如下:
checkNotNull(Object v)
private static void checkNotNull(Object v) {
//进行数据校验,如果为空,直接抛出异常
if (v == null)
throw new NullPointerException();
}
lockInterruptibly()
立即响应中断。
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
enqueue(E x)
入队操作
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
//记录当前数组
final Object[] items = this.items;
//数组添加元素
items[putIndex] = x;
//数据校验,如果满了,putIndex置为0
if (++putIndex == items.length)
putIndex = 0;
//元素数维护
count++;
//能添加了,唤醒不空的线程
notEmpty.signal();
}
看到这里博主不得不进行一个DEMO演示。
package demo;
import java.util.concurrent.locks.ReentrantLock;
/**
* 锁中断测试
* lock方法会忽略中断请求,继续获取锁直到成功;而lockInterruptibly则直接抛出中断异常来立即响应中断,由上层调用者处理中断
* lock()
* 适用于锁获取操作不受中断影响的情况,此时可以忽略中断请求正常执行加锁操作,因为该操作仅仅记录了中断状态
* (通过Thread.currentThread().interrupt()操作,只是恢复了中断状态为true,并没有对中断进行响应)。
* 如果要求被中断线程不能参与锁的竞争操作,则此时应该使用lockInterruptibly方法,
* 一旦检测到中断请求,立即返回不再参与锁的竞争并且取消锁获取操作(即finally中的cancelAcquire操作)
* @Description: TODO
* @author BurgessLee
* @date 2019年5月5日
*
*/
public class ReentrantLockLockInterruptibly {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() +"-starting....");
lock.lockInterruptibly();//获取锁
if(lock.isLocked()) {
System.out.println(Thread.currentThread().getName() +"-is locked....");
}
System.out.println(Thread.currentThread().getName() +"-start to sleep....");
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName() +"-end to sleep....");
System.out.println(Thread.currentThread().getName() +"-interrupt:"+ Thread.currentThread().isInterrupted());
System.out.println(Thread.currentThread().getName() +"-ending....");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(lock.isLocked()) {
System.out.println(Thread.currentThread().getName() +"-finally is locked....");
lock.unlock();
}
}
}
},"Thread0");
try {
Thread.sleep(1000);
} catch (InterruptedException e2) {
e2.printStackTrace();
}
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e2) {
e2.printStackTrace();
}
t1.interrupt();
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() +"-starting....");
lock.lock();
if(lock.isLocked()) {
System.out.println(Thread.currentThread().getName() +"-is locked....");
}
System.out.println(Thread.currentThread().getName() +"-start to sleep....");
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName() +"-end to sleep....");
System.out.println(Thread.currentThread().getName() +"-interrupt:"+ Thread.currentThread().isInterrupted());
System.out.println(Thread.currentThread().getName() +"-ending....");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(lock.isLocked()) {
System.out.println(Thread.currentThread().getName() +"-finally is locked....");
lock.unlock();
}
}
}
},"Thread1");
t2.start();//启动线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt();
}
}
这里进一步总结了关于ReentrantLock的lock方法和lockInterruptibly方法的区别,在于对于线程中断处理的问题上,具体可以看代码演示。下面我们继续源码分析。看完了put方法,我也很好奇其他的添加元素的方法是怎么实现的。下面我们就一起来学习下。
offer(E e)
public boolean offer(E e) {
//数据合法性校验
checkNotNull(e);
//获取锁
final ReentrantLock lock = this.lock;
//上锁
lock.lock();
try {
//如果满了,返回false
if (count == items.length)
return false;
else {
//没有满,添加元素,返回true
enqueue(e);
return true;
}
} finally {
//释放锁
lock.unlock();
}
}
可以看到就是一个正常队列的操作。
add(E e)
调用父类方法实现的。
public boolean add(E e) {
return super.add(e);
}
好了,下面我们一起来看一下关于take源码的实现。
take()
public E take() throws InterruptedException {
//获取锁
final ReentrantLock lock = this.lock;
//立即中断,如果没有中断,继续操作
lock.lockInterruptibly();
try {
//如果队列为空
while (count == 0)
//等待
notEmpty.await();
//否则入队操作
return dequeue();
} finally {
//释放锁
lock.unlock();
}
}
同样take元素也是调用了对应的出队的方法,下面我们看一下。
dequeue()
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
//记录当前元素数组
final Object[] items = this.items;
@SuppressWarnings("unchecked")
//根据动态维护的takeIndex来获取指定元素
E x = (E) items[takeIndex];
//释放当前元素的
items[takeIndex] = null;
//数据合法性校验
if (++takeIndex == items.length)
takeIndex = 0;
//元素数量维护
count--;
//执行出队操作
if (itrs != null)
itrs.elementDequeued();
//唤醒对应的线程
notFull.signal();
//返回出队元素
return x;
}
该方法用来执行出队操作。上面代码中注释内容已经很清楚,此处不再一一赘述。
下面我们来看一个DEMO演示。
package demo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* ArrayBlockingQueue测试例程
* @ClassName: ArrayBlockingQueueDemo
* @Description: TODO
* @author BurgessLee
* @date 2019年5月5日
*
*/
public class ArrayBlockingQueueDemo {
public static void main(String[] args) {
//1.创建队列
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +"....start....");
for(int i = 0; i < 10; i++) {
try {
// TimeUnit.SECONDS.sleep(1);
queue.put(i);
System.out.println(Thread.currentThread().getName() +"存入的元素是:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"thread0").start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +"....start....");
for(int i = 0; i < 10; i++) {
try {
// TimeUnit.SECONDS.sleep(1);
Integer take = queue.take();
System.out.println(Thread.currentThread().getName() +"获取到的元素是:" + take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"thread1").start();
}
}
打印输出结果是:
thread0....start....
thread0存入的元素是:0
thread0存入的元素是:1
thread0存入的元素是:2
thread1....start....
thread0存入的元素是:3
thread1获取到的元素是:0
thread1获取到的元素是:1
thread0存入的元素是:4
thread1获取到的元素是:2
thread0存入的元素是:5
thread1获取到的元素是:3
thread0存入的元素是:6
thread1获取到的元素是:4
thread0存入的元素是:7
thread1获取到的元素是:5
thread0存入的元素是:8
thread1获取到的元素是:6
thread0存入的元素是:9
thread1获取到的元素是:7
thread1获取到的元素是:8
thread1获取到的元素是:9
以上就是针对ArrayBlockingQueue的源码分析的全部过程了。如果有不对的地方还请指正。