说到堵塞队列,是一个很抽象,生硬的东西,我们来举例它在实际的应用中是什么样子的。
一、生产者与消费者
生产者与消费者,就比如我们现在需要某个对象去做某个事情。
我们需要用随机数对象,
private static Random ra = new Random();
我们这就是生产出来了一个随机数对象。
我们使用随机数对象
String fileName = ra.nextInt() + ".txt";
上面我们就做了生产者和消费者。当然这个生产出来的东西有点太耐用了,能被消费者反复使用。
我们就把这个看作是生产了一支笔(写随机数的笔),我们使用笔(用它生产随机数)。
现在有50个学生,我们就要写50个生产语句吗。
我们是先获取几个学生,再去生产笔吗。那么效率会不会太低了,学生急着用怎么办
那我们先生产笔,再给学生吗。那么资源消耗是不是有点太大了,而且多少个合适,一般这样写了,都是生成很多个以防万一吧。
综上所述,我们可以开启线程,其中一个是生产者线程,一个是消费者线程。生产者产出之后,消费者去使用。
public class Book {
private int id;
public Book(int id) {
this.id = id;
}
@Override
public String toString() {
return "书籍{" +
"id=" + id +
'}';
}
}
Book book = new Book(BookList.size());
当然这样还没有完全解决问题,我们在中间放一个容器,让生成者把生产好的东西放进去,消费者去取,我们就解决了生产者和消费者之间的强耦合(没有必要一对一生产,生产者可以多生产几个,也可能少生产了几个,不是实时同步)。在上面我们用list集合来放book
生产者生产好了就这样处理,book。
Book book = new Book(BookList.size());
System.out.println(Thread.currentThread().getName()+"做了"+book);
BookList.add(book);
消费者就可以去取book。
Book book = BookList.remove(0);
System.out.println(Thread.currentThread().getName()+"卖了"+book);
但是如果list为0,消费者还要去取呢。list已经很多了生产者还要去存呢。
我们可以的确可以自己设置一下条件。比如先给list设置个最大值,最小值。
存的时候不能大于最大值,取的时候不能小于最小值。前者会让生产者线程休眠,后者会让消费者然后两个线程可以互相唤醒。
if (BookList.size() == 0){
System.out.println("卖完了,不能买了");
System.out.println(Thread.currentThread().getName()+"别卖了,开始摸鱼");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
this.notifyAll();
}
//做满了就不做
if (BookList.size() == MAX_COUNT) {
System.out.println("书店满了");
System.out.println(Thread.currentThread().getName() + "厂商开始摸鱼");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//开始卖书
this.notifyAll();
}
这些和我们的堵塞队列有什么关系呢,容器的使用和设置很麻烦,java中的堵塞队列是一类接口,就是代替这部分的。
二、堵塞队列
阻塞队列(Blocking Queue)是一种特殊类型的队列,它在队列为空时获取元素的操作会被阻塞,直到队列中有可用元素;而在队列已满时添加元素的操作会被阻塞,直到队列有空闲位置。
阻塞队列常用于多线程编程,特别适用于生产者-消费者模式。它提供了一种线程安全的方式来共享数据,以避免线程间的竞争条件和资源浪费。
阻塞队列通常具备以下几个特性:
- 插入和移除方法是线程安全的,可以由多个线程同时操作。
- 当队列为空时,试图从队列中获取元素的操作将被阻塞,直到有新元素插入。
- 当队列已满时,试图向队列中添加元素的操作将被阻塞,直到有空闲位置。
- 提供不同的阻塞策略,如等待超时、放弃、阻塞等待等。
常见的阻塞队列实现包括:
- ArrayBlockingQueue:基于数组实现的有界阻塞队列。
- LinkedBlockingQueue:基于链表实现的可选界限阻塞队列。
- PriorityBlockingQueue:基于优先级的无界阻塞队列。
- DelayQueue:基于延迟时间的无界阻塞队列。
使用阻塞队列可以简化多线程编程中的同步和通信,提高线程的效率和可维护性。
自定义对象 Book 放入阻塞队列中,需要确保 Book 类正确实现了 equals() 和 hashCode() 方法。这是因为阻塞队列在执行操作时通常需要比较对象的相等性来确定是否已存在相同的元素。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ProducerConsumerExample {
private static final int CAPACITY = 5; // 队列容量
public static void main(String[] args) {
BlockingQueue<Book> queue = new ArrayBlockingQueue<>(CAPACITY); // 创建阻塞队列
// 创建生产者线程和消费者线程
Thread producerThread = new Thread(new Producer(queue));
Thread consumerThread = new Thread(new Consumer(queue));
// 启动线程
producerThread.start();
consumerThread.start();
}
// 生产者线程
static class Producer implements Runnable {
private final BlockingQueue<Book> queue;
public Producer(BlockingQueue<Book> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
int i = 1;
while (true) {
Book book = new Book("Book" + i, "Author" + i, 29.99 * i);
System.out.println("生产者生产: " + book);
queue.put(book); // 将书籍放入队列中
i++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 消费者线程
static class Consumer implements Runnable {
private final BlockingQueue<Book> queue;
public Consumer(BlockingQueue<Book> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (true) {
Book book = queue.take(); // 从队列中获取书籍
System.out.println("消费者消费: " + book);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}