大家好,我是walker
一个从文科自学转行的程序员~
爱好编程,偶尔写写编程文章和生活
欢迎关注公众号【I am Walker】,回复“电子书”,就可以获得200多本编程相关电子书哈~
我的gitee:https://gitee.com/shen-chuhao/walker.git 里面很多技术案例!
什么是阻塞队列?
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。
这两个附加的操作是:
- 在队列为空时,获取元素的线程会等待队列变为非空。
- 当队列满时,存储元素的线程会等待队列可用。
应用场景
阻塞队列常用于生产者和消费者的场景,
- 生产者是往队列里添加元素的线程,
- 消费者是从队列里拿元素的线程。
- 阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
阻塞队列使用最经典的场景就是 socket 客户端数据的读取和解析,读取数据的线程不断将数据放入队列,然后解析线程不断从队列取数据解析。
如何使用阻塞队列来实现生产者-消费者模型?
案例:生产包子,消费包子
生产者
package productConsumerMode.blockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 生产者
*/
public class Producer implements Runnable {
//该字段用于做是否循环的字段 这里使用volatile保证可见性和防止指令重排
private volatile boolean isRunning = true;
//线程休眠时间,防止线程调用速度太快
private final static int SLEEP_TIME = 1000;
//包子阻塞队列,用来存储生产的包子
private BlockingQueue<String> bumQueue;
//包子序号
private static AtomicInteger serial = new AtomicInteger();
/**
* 构造方法
* 参数为:包子队列
*/
public Producer(BlockingQueue<String> bumQueue) {
this.bumQueue = bumQueue;
}
/**
* 重写run方法
*/
@Override
public void run() {
//当isRunning=true的时候,循环进行生产
while (isRunning) {
try {
/**
* boolean offer(E e, long timeout, TimeUnit unit) 若2秒还没有加入,则代表队列阻塞,这时候打印加入队列失败,否则则加入队列
*/
if (!bumQueue.offer("包子" + serial.getAndAdd(1), 2, TimeUnit.SECONDS)) {
System.out.println("队列的数量为:" + bumQueue.size() + ",无法加入队列");
} else {
System.out.println("【生产者】" + Thread.currentThread().getName() + "生产包子" + serial.get());
}
//线程休眠
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void stopProduct() {
isRunning = false;
}
}
消费者
package productConsumerMode.blockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 消费者
*/
public class Consumer implements Runnable {
//包子队列
private BlockingQueue<String> bumQueue;
//是否支持消费
private volatile boolean isConsumer=true;
//构造方法
public Consumer(BlockingQueue<String> bumQueue) {
this.bumQueue = bumQueue;
}
@Override
public void run() {
while(isConsumer){
//从队列中获取包子
String bum = bumQueue.poll();
//若包子存在,则消费 若不存在则提示没有包子消费
if (bum != null) {
System.out.println("【消费者】" + Thread.currentThread().getName() + "消费" + bum);
}else{
System.out.println("包子数量剩余"+bumQueue.size()+",没有包子可以消费");
}
//此处让线程休眠一下,防止消费太快
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void stopConsumer(){
isConsumer=false;
}
}
测试
package productConsumerMode.blockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @Author: WalkerShen
* @DATE: 2022/3/16
* @Description: 使用blockingQueue实现生产者消费者模式
* 案例:生产者生产包子 消费者买包子
**/
public class BlockingQueueTest {
public static void main(String[] args) throws InterruptedException {
//创建一个阻塞队列,容量为10
BlockingQueue<String> bumQueue=new LinkedBlockingQueue<>(10);
//创建Runnable
Producer producer1 = new Producer(bumQueue);
Producer producer2 = new Producer(bumQueue);
Consumer consumer1 = new Consumer(bumQueue);
Consumer consumer2 = new Consumer(bumQueue);
//使用Executors工具执行线程
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(consumer1);
executorService.execute(consumer2);
executorService.execute(producer1);
executorService.execute(producer2);
//让主线程休眠20秒,这里是为了查看执行的结果
Thread.sleep(20*1000);
//停止生产和消费
producer1.stopProduct();
producer2.stopProduct();
consumer1.stopConsumer();
consumer2.stopConsumer();
//最后关闭线程池
executorService.shutdown();
}
}
JDK7 提供的阻塞队列
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
消费者模式jdk5前后的变化
- Java 5 之前实现同步存取时,可以使用普通的一个集合,然后在使用线程的协作和线程同步可以实现生产者,消费者模式,主要的技术就是用好,wait,notify,notifyAll,sychronized 这些关键字。
- 而在 java 5 之后,可以使用阻塞队列来实现,此方式大大简少了代码量,使得多线程编程更加容易,安全方面也有保障。
源码解析
ArrayBlockingQueue
add
public boolean add(E e) {
return super.add(e);
}
add
public boolean add(E e) {
if (offer(e))
return true;
//会抛出队列满的异常
else
throw new IllegalStateException("Queue full");
}
offer
public boolean offer(E e) {
//检查是否为null
checkNotNull(e);
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//如果
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
//解锁
lock.unlock();
}
}
enqueue
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
//结果放入
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
//private final Condition notEmpty; 使用Condition,释放信号,让其他线程获取
notEmpty.signal();
}
offer
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
put
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();
}
}
remove:返回true,false
public boolean remove(Object o) {
//如果为null,则返回false
if (o == null) return false;
final Object[] items = this.items;
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//如果队列长度大于0,才可以进行移除
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
do {
//循环:如果找到对应的值,则移除,否则则继续循环,i++ 坐标++
if (o.equals(items[i])) {
removeAt(i);
return true;
}
//如果坐标等于数组产长度,则从0开始
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
//否则直接返回false
return false;
} finally {
lock.unlock();
}
}
poll:返回null和结果
public E poll() {
//加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//count是否等于0,如果等于0则返回null
return (count == 0) ? null : dequeue();
} finally {
//解锁
lock.unlock();
}
}
dequeue
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
//通知
notFull.signal();
return x;
}
take
public E take() throws InterruptedException {
//异常中断加锁 lockInterruptibly
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果队列长度为0的话,则阻塞等待获取
while (count == 0)
notEmpty.await();
//否则则
return dequeue();
} finally {
//解锁
lock.unlock();
}
}
dequeue
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}