阻塞队列的基本理解
- 和队列一样,”先进先出“
- 输入阻塞,当队列里的数据已经将队列塞满时,=输入会阻塞。
- 输出阻塞,当队列里是空的时候,输入会阻塞。
经典应用–消费者/生产者模型
这里先给一个例子:
消费平台在双十一等等促销日时会出现”秒杀“的情况。这意味着在短时间服务器需要处理大量的数据,服务器的压力会特别大,也会带来如下问题:
- 资源占用过高。因为需要频繁的申请、释放资源,会增加系统的开销,降低整体的处理效率。可以理解为你有一堆事要做,虽然每件事都办的很快,但是每件事都在不同的地点。所以每一件事情中10分钟给路程,1分钟办完。时间也是浪费地很多。
- 硬件资源分配不均。从第一点可以看出用于申请、释放资源的硬件部分(CPU核心等)被过度使用,但是内存却在闲置。
- 系统的稳定性降低。过快的处理会导致系统出现静态条件(多线程/并发访问、修改共享数据时,因为访问的次序不同,导致数据不一)。
阻塞队列的优点:
- 充当缓冲区,缓解服务器的压力
阻塞队列会让需要操作的数据处在一个较为稳定的范围内(队列的长度范围内),控制数据的操作速率,解决了问题1和2.而且阻塞队列会将所需要操作的数据进行排序,保证了访问次序,解决了问题3.
实现"消峰“,放置服务器被一瞬间冲垮。
- **解耦 **强耦合的对象
就好比大家一起包饺子,一拨人擀皮,一拨人包。
擀皮的人不需要知道下一个是谁用他擀的皮去包饺子,也不需要知道包的饺子是什么样子的,他只需要擀好了就往盘子上放就好了。
包饺子的人不需要知道是谁擀的皮,也不需要知道擀皮的人是用擀面杖擀、用罐头擀或者是直接去超市买的,他只需要把盘子里的皮过来包就行了。
这里的盘子就是我们所说的阻塞队列。
标准库里的阻塞队列BlockingQueue
- BlockingQueue其实是接口,实现的类是LinkedBlockingQueue
- 阻塞方法:==put ==用于阻塞式的入队列,==take ==用于阻塞式的出队列
- 非阻塞方法:offer,pull,pick
基本要素:
- BlockingQueue < String> blockingQueue=new LinkedBlockingQueue<>();
- blockingQueue.put(“阻塞队列”);
- String get=blockingQueue.take();
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class JAVA_THREAD09 {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();
Thread customer = new Thread(() -> {
while (true) {
try {
int value = blockingQueue.take();
System.out.println("消费元素: " + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者");
customer.start();
Thread producer = new Thread(() -> {
Random random = new Random();
while (true) {
try {
int num = random.nextInt(1000);
System.out.println("生产元素: " + num);
blockingQueue.put(num);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者");
producer.start();
customer.join();
producer.join();
}
}
结果如下:
阻塞队列的基本实现
四变量
- 一存储
- volatile int[] item (这里的int类型可以换成任何想要存储的类型,这里使用循环数组来实现队列)
- 三标识
- volatile int size (用于记录队列长度,保证可见性)
- int tail (put方法需要 )
- int head (take方法需要 )
二方法
方法都主要是
2个基本状态:可执行、不可执行
4个基本内容:存、移、变、释
- put() 方法
- 不可执行(队列已满,size==item.length)
- 循环等待 wait()
- 可执行(队列未满,size!=item.length)
- 存 将 put 进入的数传入数组 (item[tail]=value)
- 移 移动 tail 的位置(tail=(tail++)%item.length) 模上长度是利用循环数组实现队列
- 变 队列的长度发生变化(size++)
- 释 释放所有线程(notifyAll() )
- 不可执行(队列已满,size==item.length)
- take() 方法
新建返回变量 ret=0;- 不可执行(队列空,size==0)
- 循环等待 wait()
- 可执行(队列不空,size!=0)
- 存 将队列头的数存入返回变量里 (ret=item[head])
- 移 移动 head 的位置(head =(head ++)%item.length)
- 变 队列长度发生改变(size–)
- 释 释放所有线程(notifyAll() )
- 不可执行(队列空,size==0)
最后再返回ret
看看阻塞队列基本实现的代码
static class BlockingQueue {
private int[] items = new int[1000];
private volatile int size = 0;
private int head = 0;
private int tail = 0;
public void put(int value) throws InterruptedException {
synchronized (this) {
// 此处最好使用 while.
// 否则 notifyAll 的时候, 该线程从 wait 中被唤醒,
// 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能又已经队列满了
// 就只能继续等待
while (size == items.length) {
wait();
}
items[tail] = value;
tail = (tail + 1) % items.length;
size++;
notifyAll();
}
}
public int take() throws InterruptedException {
int ret = 0;
synchronized (this) {
while (size == 0) {
wait();
}
ret = items[head];
head = (head + 1) % items.length;
size--;
notifyAll();
}
return ret;
}
public synchronized int size() {
return size;
}
}
// 测试代码
public static void main(String[] args) throws InterruptedException {
BlockingQueue blockingQueue = new BlockingQueue();
Thread customer = new Thread(() -> {
while (true) {
try {
int value = blockingQueue.take();
System.out.println(value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者");
customer.start();
Thread producer = new Thread(() -> {
Random random = new Random();
while (true) {
try {
blockingQueue.put(random.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者");
producer.start();
customer.join();
producer.join();
}
结果: