什么是阻塞队列:
阻塞队列是一种特殊的队列数据结构,具有阻塞的特性。它主要用于多线程编程中,提供了线程安全的数据交换的方法。
- 当队列满的时候,入队列就会阻塞,除非另一方拿走队列里边的数据。
- 当队列空的时候,如果这时候有人拿走数据也会阻塞,因为已经没有数据了,除非有人往队列中加数据。
阻塞队列常常用来实现生产者消费者模型,也是面试中常考的部分:
什么是生产者消费者模型:
生产者消费者模型是一种经典的多线程或多进程设计模式。在这个模型中,主要有两类角色:生产者和消费者。在这个模型中,生产者将数据放入阻塞队列中,而消费者则从中拿数据,实际上也就是生产者和消费者通过阻塞队列进行通信。
生产者消费者模型在开发中的常见使用场景:
1.阻塞队列相当于一个缓冲区域,平衡了消费者和生产者之间的关系:
比如某游戏在“周年庆”等活动场景下,游戏的服务器同一时刻可能会收到大量的登录请求。如果直接处理这些请求,服务器可能扛不住。这个时候就可以把这些请求都放到一个阻塞队列中,然后再由消费者线程慢慢处理每个登录请求。
2.生产者和消费者之间能够解耦:
生产者和消费者之间,生产者不用关系谁拿走了数据,而消费者不用关心谁给他的数据,这就是解耦,通过这样的模型使得他们之间没有重合的部分能够各司其职。
java中的阻塞队列:
BlockingQueue是一个接口,我们要向下转型从而实现阻塞队列。
BlockingQueue有两个主要方法put(用于放入元素),take(用于拿取元素)
我们要开始实现简单的BlockIngQueue了
这里的1000是该阻塞队列的容量。
这就是阻塞队列,在示例中,创建了一个容量为 1000 的 ArrayBlockingQueue
对象 bq
,作为生产者和消费者之间的共享缓冲区。然后创建了两个线程thread和thread1,分别表示生产者和消费者。
由于在消费者中,take 一次就睡眠0.5秒,使上述代码只take一次后,bq 队列就直接 put 满了,后续则会生产一个消费一个。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class Demo18 {
public static void main(String[] args) {
BlockingQueue<Integer> blockingQueue=new ArrayBlockingQueue<>(1000);
Thread thread=new Thread(()->{
int count=0;
while (true){
try {
blockingQueue.put(count);
System.out.println("阻塞队列中存储了"+count);
count++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread thread1=new Thread(()->{
while (true){
try {
int count=blockingQueue.take();
System.out.println("阻塞队列中消费了"+count);
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread.start();
thread1.start();
}
}
自己实现一个阻塞队列:
- 这是一个循环队列
- 通过synchronized来防止出现线程安全的问题
- 这里的wait是互相唤醒,必须牢记。
public class MyBlockingQueue {
private Integer[] elem;
private int head;
private int tail;
private int size;//阻塞队列中的个数
public MyBlockingQueue(int size) {
elem=new Integer[size];
}
//放入方法
public void put(Integer input) throws InterruptedException {
//使用while而不是if是为了防止if被notify打断
synchronized (this) {
while (size >= elem.length) {
this.wait();
}
elem[tail] = input;
tail++;
if (tail >= elem.length) {
tail = 0;
}
//到最后已经放入完成后
size++;
//唤醒下边的wait
this.notify();
}
}
public Integer take() throws InterruptedException {
synchronized (this) {
while (size == 0) {
//这里被上面的notify唤醒
this.wait();
}
Integer take = elem[head];
head++;
if (head>=elem.length){
head=0;
}
size--;
//唤醒等待元素
this.notify();
return take;
}
}
}
public class Demo19 {
public static void main(String[] args) throws InterruptedException {
MyBlockingQueue myBlockingQueue=new MyBlockingQueue(100);
Thread thread=new Thread(()->{
int count=0;
while (true){
try {
myBlockingQueue.put(count);
System.out.println("生产了"+count);
count++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread thread1=new Thread(()->{
while (true){
try {
Integer take = myBlockingQueue.take();
System.out.println("消费了"+take);
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread.start();
Thread.sleep(100);
thread1.start();
}
}
总结:
- 阻塞队列中给我们实现了加锁,也就是没有线程安全问题。
- 生产者消费者模型是阻塞队列的具体实现。
- Java 提供了多种阻塞队列的实现,包括
ArrayBlockingQueue
、LinkedBlockingQueue
、PriorityBlockingQueue
等。 - 一些阻塞队列具有固定的容量限制,如
ArrayBlockingQueue
,而另一些则可以是无界的,如LinkedBlockingQueue
。