四大经典案例_阻塞队列_生产者消费者模型及java代码实现
大家好,我是bug郭,一名双非科班的在校大学生。对C/JAVA、数据结构、Linux及MySql、算法等领域感兴趣,喜欢将所学知识写成博客记录下来。 希望该文章对你有所帮助!如果有错误请大佬们指正!共同学习交流
作者简介:
- CSDN java领域新星创作者blog.csdn.net/bug…
- 掘金LV3用户 juejin.cn/user/bug…
- 阿里云社区专家博主,星级博主,developer.aliyun.com/bug…
- 华为云云享专家 bbs.huaweicloud.com/bug…
本节要点
- 了解一些线程安全的案例
- 学习线程安全的设计模型
- 掌握单例模式,阻塞队列,生产在消费者模型
阻塞队列
什么是阻塞队列?
顾名思义是队列的一种!
也符合先进先出的特点!
阻塞队列特点:
当队列为空时,读操作阻塞
当队列为满时,写操作阻塞
阻塞队列一般用在多线程中!并且有很多的应用场景!
最典型的一个应用场景就是生产者消费者模型
生产者消费者模型
我们知道生产者和消费者有着供需关系!
而开发中很多场景都会有这样的供需关系!
比如有两个服务器A
和B
A
是入口服务器直接接受用户的网络请求
B
应用服务器对A
进行数据提供
在通常情况下如果一个网站的访问量不大,那么A
和B
服务器都能正常使用!
而我们知道,很多网站当很多用户进行同时访问时就可能挂!
我们知道,A
入口服务器和B
引用服务器此时耦合度较高!
当一个挂了,那么另一个服务器也会出现问题!
而当我们使用生产者消费者模型就很好的解决了上述高度耦合问题!我们在他们中间加入一个阻塞队列即可!
当增加就绪队列后,我们就不用担心A
和B
的耦合!
并且A
和B
进行更改都不会影响到对方! 甚至将改变服务器,对方也无法察觉!
而阻塞队列还保证了,服务器的访问速度,不管用户量多大! 这些数据都会先传入阻塞队列,而阻塞队列如果满,或者空,都会线程阻塞! 也就不存在服务器爆了的问题!!!
也就是起到了削峰填谷的作用!不管访问量一时间多大!就绪队列都可以保证服务器的速度!
标准库中的就绪队列
我们java
中提供了一组就绪队列供我们使用!
BlockingQueue
BlockingQueue
是一个接口. 真正实现的类是 LinkedBlockingQueue
.
put
方法用于阻塞式的入队列,
take
用于阻塞式的出队列.
BlockingQueue
也有offer
, poll
, peek
等方法, 但是这些方法不带有阻塞特性.
//生产着消费者模型
public class Test2 {
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();
}
}
阻塞队列实现
虽然java
标准库中提供了阻塞队列,但是我们想自己实现一个阻塞队列!
我们就用循环队列实现吧,使用数组!
//循环队列
class MyblockingQueue{
//阻塞队列
private int[] data = new int[100];
//队头
private int start = 0;
//队尾
private int tail = 0;
//元素个数, 用于判断队列满
private int size = 0;
public void put(int x){
//入队操作
if(size==data.length){
//队列满
return;
}
data[tail] = x;
tail++;//入队
if(tail==data.length){
//判断是否需要循环回
tail=0;
}
size++; //入队成功加1
}
public Integer take(){
//出队并且获取队头元素
if(tail==start){
//队列为空!
return null;
}
int ret = data[start]; //获取队头元素
start++; //出队
if(start==data.length){
//判断是否要循环回来
start = 0;
}
// start = start % data.length;//不建议可读性不搞,效率也低
size--;//元素个数减一
return ret;
}
}
我们已经创建好了一个循环队列,目前达不到阻塞的效果!
而且当多线程并发时有很多线程不安全问题!
而我们知道想要阻塞,那不得加锁,不然哪来的阻塞!
//阻塞队列
class MyblockingQueue{
//阻塞队列
private int[] data = new int[100];
//队头
private int start = 0;
//队尾
private int tail = 0;
//元素个数, 用于判断队列满
private int size = 0;
//锁对象
Object locker = new Object();
public void put(int x){
synchronized (locker){//对该操作加锁
//入队操作
if(size==data.length){
//队列满 阻塞等待!!!直到put操作后notify才会继续执行
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
data[tail] = x;
tail++;//入队
if(tail==data.length){
//判断是否需要循环回
tail=0;
}
size++; //入队成功加1
//入队成功后通知take 如果take阻塞
locker.notify();//这个操作线程阻塞并没有副作用!
}
}
public Integer take(){
//出队并且获取队头元素
synchronized (locker){
if(size==0){
//队列为空!阻塞等待 知道队列有元素put就会继续执行该线程
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int ret = data[start]; //获取队头元素
start++; //出队
if(start==data.length){
//判断是否要循环回来
start = 0;
}
// start = start % data.length;//不建议可读性不搞,效率也低
size--;//元素个数减一
locker.notify();//通知 put 如果put阻塞!
return ret;
}
}
}
//测试代码
public class Test3 {
public static void main(String[] args) {
MyblockingQueue queue = new MyblockingQueue();
Thread customer = new Thread(()->{
int i = 0;
while (true){
System.out.println("消费了"+queue.take());
}
});
Thread producer = new Thread(()->{
Random random = new Random();
while (true){
int x = random.nextInt(100);
System.out.println("生产了"+x);
queue.put(x);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
customer.start();
producer.start();
}
}
可以看到通过wait
和notify
的配和,我就实现了阻塞队列!!!
- 点赞
- 收藏
- 关注作者
评论(0)