什么是阻塞队列
首先,阻塞队列是一个队列,满足队列的基本数据结构,先进先出。其次,当队列满时,队列会阻塞插入元素的线程,直到队列不满;当队列空时,获取元素的线程会等待队列变为非空。
阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。
如何写一个阻塞队列
手写阻塞队列是多线程面试中常见的问题,能考察面试者对多线程和锁的基础知识。
通过synchronized
关键字配合wait()
和notify()
方法,实现线程的交替运行:
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.CyclicBarrier;
public class MyBlockingQueue {
//队列
private final Queue<String> myQueue = new LinkedList<>();
//最大长度
private static final int MAXSIZE = 20;
private static final int MINSIZE = 0;
//获取队列长度
public int getSize(){
return myQueue.size();
}
//生产者
public void push(String str) throws Exception {
//拿到对象锁
synchronized (myQueue){
//如果队列满了,则阻塞
while(getSize() == MAXSIZE){
myQueue.wait();
}
myQueue.offer(str);
System.out.println(Thread.currentThread().getName() + "放入元素" + str);
//唤醒消费者线程,消费者和生产者自己去竞争锁
myQueue.notify();
}
}
//消费者
public String pop() throws Exception {
synchronized (myQueue){
String result = null;
//队列为空则阻塞
while(getSize() == MINSIZE){
myQueue.wait();
}
//先进先出
result = myQueue.poll();
System.out.println(Thread.currentThread().getName()+"取出了元素" + result);
//唤醒生产者线程,消费者和生产者自己去竞争锁
myQueue.notify();
return result;
}
}
public static void main(String args[]){
MyBlockingQueue myBlockingQueue = new MyBlockingQueue();
//两个线程,都执行完成了打印
CyclicBarrier barrier = new CyclicBarrier(2, ()->{
System.out.println("生产结束,下班了,消费者明天再来吧!");
});
//生产者线程
new Thread(()->{
//50个辛勤的生产者循环向队列中添加元素
try {
for(int i = 0; i < 50; i++){
myBlockingQueue.push("——" + i );
}
//生产完了
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},"生产者").start();
//消费者线程
new Thread(()->{
//50个白拿的消费者疯狂向队列中获取元素
try {
for(int j = 0; j < 50; j++){
myBlockingQueue.pop();
}
//消费完了
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},"消费者").start();
}
}
运行结果(部分)
对代码的一些解释
1、private final Queue<String> myQueue = new LinkedList<>();
这样创建的队列实际上偷懒了,并不是完全用数组实现的队列,用了封装的队列,但毕竟重点不在于此。
2、使用了JUC包的CyclicBarrier
类,通过调用await()
方法,实现两个线程都执行完毕后,再执行相关代码,这不属于阻塞队列的范畴。
3、synchronized
块中使用的对象锁不一定要用myQueue
,使用同一个对象就行。
4、new Thread(()->{...},"生产者").start();
是java8函数式写法,等同于继承Runnable覆写run方法。
对运行结果的一些解释
1、当队列被填满或者为空时,当前线程才会被挂起等待另一个线程唤醒锁
2、并不是队列被生产者填满了,消费者才会去取,而是在队列没满的时候,生产者和消费者两个线程都会去竞争资源,谁先拿到锁,谁先执行。