文章目录
前言
一.阻塞队列与普通队列的区别
区别:
- 当队列是空的时,从队列中
读元素
的操作将会被阻塞
,即试图从空的阻塞队列中读元素的线程将会被阻塞,直到其他的线程往空的队列写入新的元素。 - 当队列是满时,往队列里
写元素
的操作会被阻塞
。即试图往已满的阻塞队列中写元素的线程同样也会被阻塞,直到其他的线程读元素使队列重新变得空闲起来。 如:从队列中移除一个或者多个元素,或者完全清空队列.
原理: 阻塞队列就是通过 “生产-消费者模型” 实现的,当队列为空时,消费者挂起,队列已满时,生产者挂起。阻塞其实就是将线程挂起。
场景: 因为生产者的生产速度和消费者的消费速度之间的不匹配
,就可以通过阻塞队列让速度快的暂时挂起
, 如生产者每秒生产2个数据
,而消费者每秒消费1个数据
,当队列已满时,生产者就会挂起
,等待消费者消费后,再进行唤醒
。
阻塞队列会通过
线程等待通知机制
的方式来实现生产者和消费者
之间的平衡,这是和普通队列最大的区别。
二.阻塞队列主要操作
写入数据
- offer(E e):如果队列没满,返回true,如果队列已满,返回false(不阻塞)
- offer(E e, long timeout, TimeUnit unit):可以设置等待时间,如果队列已满,则进行等待。超过等待时间,则返回false
- put(E e):无返回值,一直等待,直至队列空出位置
读取数据
- poll():如果有数据,出队,如果没有数据,返回null
- poll(long timeout, TimeUnit unit):可以设置等待时间,如果没有数据,则等待,超过等待时间,则返回null
- take():如果有数据,出队。如果没有数据,一直等待(阻塞)
三.自定义阻塞队列
1.定义阻塞队列接口
这里只简单的实现读写元素offer(E e) 、take()操作
public interface BlockingQueue<E> {
/**
* 往队列尾部添加一个元素,当队列满时阻塞当前线程
* @param e
*/
boolean offer(E e);
/**
* 从队列首部取走一个元素,当队列为空时阻塞当前线程
* @return
*/
E take();
// 返回队列中元素的数量
int size();
}
2.基于Synchronized+wait()+notify()实现
/**
* 同步锁syncronized实现的阻塞队列
*/
@Slf4j
public class SyncronizedBlockingQueue<E> implements BlockingQueue<E> {
/**
* 读条件,队列为空时,用于阻塞读线程,唤醒写线程
*/
private final Object notEmpty;
/**
* 写条件,队列已满时,用于阻塞写线程,唤醒读线程
*/
private final Object notFull;
/**
* 队列容器
*/
private Object[] items;
/**
* 出队索引
*/
private int takeIndex;
/**
* 入队索引
*/
private int putIndex;
/**
* 元素数量
*/
private int count;
/**
* 容器初始化,默认为非公平锁
*
* @param capacity
*/
public SyncronizedBlockingQueue(int capacity) {
this(capacity, false);
}
/**
* 容器初始化,可设置重入锁的类型
*
* @param capacity
*/
public SyncronizedBlockingQueue(int capacity, boolean fair) {
//容器大小小于等于0,抛出空指针
if (capacity <= 0) {
new NullPointerException();
}
//初始化容器
this.items = new Object[capacity];
//非空时的条件(读)
notEmpty = new Object();
//非满时的条件(写)
notFull = new Object();
}
/**
* 往队列尾部添加一个元素,当队列满时阻塞当前线程
*
* @param obj
* @return
*/
@Override
public boolean offer(E obj) {
//入队元素为空,抛出异常
if (obj == null) {
throw new NullPointerException();
}
//1.队列已满
synchronized (notFull) {
try {
while (count == items.length) {
log.info("队列已满,等待消费数据,size:{},items.length:{}", count, items.length);
//阻塞写线程
notFull.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2.队列未满
synchronized (notEmpty) {
//将当前元素当前入队索引处,即尾部
items[putIndex] = obj;
//写入元素后入队索引等于 容器大小,表明容器已满,将入队索引重置为0,从头开始(++putIndex:先加1在使用)
if (++putIndex == items.length) {
putIndex = 0;
}
//元素数量+1(count++:先使用再加1)
count++;
//唤醒读线程
notEmpty.notify();
return true;
}
}
/**
* 从队列首部取走一个元素,当队列为空时阻塞当前线程
*
* @return
*/
@Override
public E take() {
//1.队列为空
synchronized (notEmpty) {
try {
while (count == 0) {
log.info("队列为空,等待生产数据,size:{},items.length:{}", count, items.length);
//阻塞读线程
notEmpty.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2.队列不为空
synchronized (notFull) {
//获取当前出队索引处的元素
Object obj = items[takeIndex];
//如果到了最后一个,将出队索引重置为0,则从头开始(++takeIndex:先加1在使用)
if (++takeIndex == items.length) {
takeIndex = 0;
}
//元素数量-1(count--:先使用再减1)
count--;
//唤醒写线程
notFull.notify();
//返回元素
return (E) obj;
}
}
/**
* 队列大小
* @return
*/
@Override
public synchronized int size() {
//获取锁
return this.count;
}
}
测试:生产速度大于消费速度
public static void main(String[] args) {
SyncronizedBlockingQueue<Object> blockingQueue = new SyncronizedBlockingQueue<>(5);
new Thread(() -> {
for (int i = 1; i <= 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
blockingQueue.offer(i);
log.info("生产者生产了:{}", i);
}
}, "producer").start();
new Thread(() -> {
for (int i = 1; i <= 100; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object take = blockingQueue.take();
log.info("消费者消费了:{}", take);
}
}, "consumer").start();
}
执行结果
- 生产者线程每
100ms
生产1个数据,消费者线程每200ms
消费1个数据,即生产速度大于消费速度
,当生产者线程将容器写满后,会进入阻塞
状态,等待消费者消费数据后唤醒
3.基于Lock+Condition实现
/**
* 可重入锁ReentrantLock实现的阻塞队列
*/
@Slf4j
public class ReentrantLockBlockingQueue<E> implements BlockingQueue<E> {
/**
* 队列容器
*/
private Object[] items;
/**
* 出队索引
*/
private int takeIndex;
/**
* 入队索引
*/
private int putIndex;
/**
* 元素数量计算器:用于判定容器边界
*/
private int count;
/**
* 可重入锁
*/
private ReentrantLock lock = new ReentrantLock();
/**
* 读条件,队列为空时,用于阻塞读线程,唤醒写线程
*/
private Condition notEmpty;
/**
* 写条件,队列已满时,用于阻塞写线程,唤醒读线程
*/
private Condition notFull;
/**
* 容器初始化,默认为非公平锁
* @param capacity
*/
public ReentrantLockBlockingQueue(int capacity) {
this(capacity,false);
}
/**
* 容器初始化,可设置重入锁的类型
* @param capacity
*/
public ReentrantLockBlockingQueue(int capacity, boolean fair) {
//容器大小小于等于0,抛出空指针
if (capacity <= 0) {
new NullPointerException();
}
//初始化可重入锁
lock = new ReentrantLock(fair);
//初始化容器
this.items = new Object[capacity];
//非空时的条件(读)
notEmpty = lock.newCondition();
//非满时的条件(写)
notFull = lock.newCondition();
}
/**
* 往队列尾部添加一个元素,当队列满时阻塞当前线程
*
* @param obj
* @return
*/
@Override
public boolean offer(E obj) {
//入队元素为空,抛出异常
if (obj == null) {
throw new NullPointerException();
}
//获取锁
lock.lock();
try {
//1.队列已满
while (count == items.length) {
log.info("队列已满,等待消费数据,size:{},tab.length:{}", count, items.length);
//阻塞写线程
notFull.await();
}
//2.队列未满
//将当前元素当前入队索引处,即尾部
items[putIndex] = obj;
//写入元素后入队索引等于 容器大小,表明容器已满,将入队索引重置为0,从头开始(++putIndex:先加1再使用)
if (++putIndex == items.length) {
putIndex = 0;
}
//元素数量+1(count++:先使用再加1)
count++;
//唤醒读线程
notEmpty.signal();
return true;
} catch (Exception e) {
//唤醒读线程
notEmpty.signal();
} finally {
//释放锁
lock.unlock();
}
return false;
}
/**
* 从队列首部取走一个元素,当队列为空时阻塞当前线程
*
* @return
*/
@Override
public E take() {
//获取锁
lock.lock();
try {
//1.队列为空
while (count == 0) {
log.info("队列为空,等待生产数据,size:{},tab.length:{}", count, items.length);
//阻塞读线程
notEmpty.await();
}
//2.队列不为空
//获取当前出队索引处的元素
Object obj = items[takeIndex];
//如果到了最后一个,将出队索引重置为0,则从头开始(++takeIndex:先加1在使用)
if (++takeIndex == items.length) {
takeIndex = 0;
}
//元素数量-1(count--:先减1再使用)
count--;
//唤醒所有写线程
notFull.signal();
//返回元素
return (E) obj;
} catch (Exception e) {
//唤醒写线程
notFull.signal();
} finally {
//释放锁
lock.unlock();
}
return null;
}
/**
* 队列大小
* @return
*/
@Override
public int size() {
//获取锁
lock.lock();
try {
return this.count;
} finally {
lock.unlock();
}
}
}
测试:消费速度大于生产速度
public static void main(String[] args) {
ReentrantLockBlockingQueue<Object> blockingQueue = new ReentrantLockBlockingQueue<Object>(5);
new Thread(() -> {
for (int i = 1; i <= 100; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
blockingQueue.offer(i);
log.info("生产者生产了:{}", i);
}
}, "producer").start();
new Thread(() -> {
for (int i = 1; i <= 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object take = blockingQueue.take();
log.info("消费者消费了:{}", take);
}
}, "consumer").start();
}
执行结果
- 生产者线程每
100ms
生产1个数据,消费者线程每200ms
消费1个数据, 即消费速度大于生产速度
,当消费者线程取出容器的所有数据后,会进入阻塞
状态,等待生产者生产数据后唤醒
参照了ArrayBlockingQueue的源码写的,有问题欢迎大家修正。