我们上一篇解释了什么是阻塞队列,队列的架构梳理和种类分析(https://blog.csdn.net/qq_30546099/article/details/112723231),那么这篇文章就谈谈阻塞队列到底用到什么地方了,并详细解释
【问题一】阻塞队列用到了什么地方
- 生产者消费者
- 线程池
- 消息中间件
1.生产者消费者
在使用Lock之前,我们都使用Object 的wait和notify实现同步的。举例来说,一个producer和consumer,consumer发现没有东西了,等待,produer生成东西了,唤醒。
wait属于Object类的和线程没关系。注:多线程的判断不能用if,只能用while。因为wait可能存在虚假唤醒
线程consumer | 线程producer |
synchronize(obj){ obj.wait();//没东西了,等待 } | synchronize(obj){ obj.notify();//有东西了,唤醒 } |
有了lock后,世道变了,现在是: | |
lock.lock(); condition.await(); lock.unlock(); | lock.lock(); condition.signal(); lock.unlock(); |
为了突出区别,省略了若干细节。区别有三点:
1. lock不再用synchronize把同步代码包装起来;
2. 阻塞需要另外一个对象condition;
3. 同步和唤醒的对象是condition而不是lock,对应的方法是await和signal,而不是wait和notify。
为什么需要使用condition呢?简单一句话,lock更灵活。以前的方式只能有一个等待队列,在实际应用时可能需要多个,比如读和写。为了这个灵活性,lock将同步互斥控制和等待队列分离开来,互斥保证在某个时刻只有一个线程访问临界区(lock自己完成),等待队列负责保存被阻塞的线程(condition完成)。
通过查看ReentrantLock的源代码发现,condition其实是等待队列的一个管理者,condition确保阻塞的对象按顺序被唤醒。
在Lock的实现中,LockSupport被用来实现线程状态的改变,后续将更进一步研究LockSupport的实现机制。
通过Demo来看【传统版的生产者消费者ProdConsumer_TraditionDemo】
package com.neu.controller.study;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
///传统版的生产者消费者模式
/**
* 资源类
*/
class ShareData{
private int number=0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws Exception{
lock.lock();
try{
//1 判断
while (number != 0){
//等待,不能生产
condition.await();
}
//2 干活
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//3 通知唤醒
condition.signalAll();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void decrement() throws Exception{
lock.lock();
try{
//1 判断
while (number == 0){
//等待,不能生产
condition.await();
}
//2 干活
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//3 通知唤醒
condition.signalAll();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
/**
*
* 题目:一个初始值为零的变量,两个线程对其交替操作,一个加1,一个减1,来5轮
* 1 线程 操作 资源类
* 2 判断 干活 通知
* 3 防止虚假唤醒机制
*
* wait属于object类的,和线程没关系
* 多线程的判断不能用if,只能用while。因为wait可能存在虚假唤醒
*
*/
public class ProdConsumer_TraditionDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(()->{
for (int i=1;i<=5;i++){
try {
shareData.increment();
}catch(Exception e){
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i=1;i<=5;i++){
try {
shareData.decrement();
}catch(Exception e){
e.printStackTrace();
}
}
},"BB").start();
}
}
运行结果
AA 1
BB 0
AA 1
BB 0
AA 1
BB 0
AA 1
BB 0
AA 1
BB 0
通过Demo来看【阻塞队列版ProdConsumer_BlockQueueDemo】
package com.neu.controller.study;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyResource{
private volatile boolean FLAG = true;//默认开启,进行生产+消费
private AtomicInteger atomicInteger = new AtomicInteger();
BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void myProd() throws Exception{
String data = null;
boolean retValue;
while(FLAG){
data = atomicInteger.incrementAndGet()+"";//++i 1
retValue = blockingQueue.offer(data,2L, TimeUnit.SECONDS);//2秒取一个
if(retValue){
System.out.println(Thread.currentThread().getName()+"\t插入队列"+data+"成功");
}else{
System.out.println(Thread.currentThread().getName()+"\t插入队列"+data+"失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName()+"\t大老板叫停了,表示flag=flase,生产停止");
}
/**
* 消费
*/
public void myConsumer() throws Exception{
String result = null;
while(FLAG){
result = blockingQueue.poll(2L,TimeUnit.SECONDS);
if(null==result || result.equalsIgnoreCase("")){
FLAG = false;
System.out.println(Thread.currentThread().getName()+"\t 超过2秒,消费退出");
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName()+"\t消费队列蛋糕"+result+"成功");
}
}
public void stop() throws Exception{
this.FLAG = false;
}
}
/*
* volatile/CAS/atomicInteger/BlockQueue/线程交互/原子引用
* 传参传接口
* */
public class ProdConsumer_BlockQueueDemo {
public static void main(String[] args) throws Exception{
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 生产线程启动");
try{
myResource.myProd();
}catch (Exception e){
e.printStackTrace();
}
},"Prod").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 消费线程启动");
System.out.println();
System.out.println();
try{
myResource.myConsumer();
System.out.println();
}catch (Exception e){
e.printStackTrace();
}
},"Consumer").start();
try{TimeUnit.SECONDS.sleep(5);}catch (InterruptedException e){e.printStackTrace();}
System.out.println();
System.out.println();
System.out.println();
System.out.println("5秒钟到,main停止");
myResource.stop();
}
}
运行结果
java.util.concurrent.ArrayBlockingQueue
Prod 生产线程启动
Prod 插入队列1成功
Consumer 消费线程启动
Consumer 消费队列蛋糕1成功
Prod 插入队列2成功
Consumer 消费队列蛋糕2成功
Prod 插入队列3成功
Consumer 消费队列蛋糕3成功
Prod 插入队列4成功
Consumer 消费队列蛋糕4成功
Prod 插入队列5成功
Consumer 消费队列蛋糕5成功5秒钟到,main停止
Prod 大老板叫停了,表示flag=flase,生产停止
Consumer 超过2秒,消费退出
2.线程池
再说线程池之前,我们应该了解创建多线程的几种方式:extend Thread和implement Runnable和implement Callable接口
/** * Runnable和Callable的区别 * 1.Runnable没有返回值 * Callable有返回值。银行对账,带返回值的工作线程 * 2.Runable不会抛异常 Callable会抛异常 * 3.接口性的方法不同 run call */
小demo查看下
package com.neu.controller.study;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
//class MyThread implements Runnable
//{
// @Override
// public void run() {
// }
//}
/**
* 区别
* 1.Runnable没有返回值
* Callable有返回值。银行对账,带返回值的工作线程
* 2.Runable不会抛异常 Callable会抛异常
* 3.接口性的方法不同 run call
*/
class MyThread implements Callable<Integer> {
public Integer call() throws Exception{
System.out.println(".........come in callable");
return 1024;
}
}
/**
* 多线程中,第3种获得多线程的方式
*
*/
public class CallableDemo {
public static void main(String[] args) {
//两个线程 一个main线程 一个AA线程
//适配器模式
FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
// futureTask.get();
new Thread(futureTask,"AA").start();
new Thread(futureTask,"BB").start();
// int r2 = futureTask.get();
int r1 = 100;
// while (!futureTask.isDone()){
//
// }
int r2 = 0;//futureTask.get();//要求获得Callable线程的计算结果,如果没有计算完成就要强求,会导致阻塞,直到计算完成,建议放在最后
System.out.println(".....result:"+r1+r2);
}
}
运行结果:
.....result:1000
.........come in callable
以上说了创建多线程的三种方式,现在又多了一种:通过线程池创建多线程
/** * 4种使用多线程的方式 * 1.继承Thread类 * 2.实现Runnable接口(run方法),没有返回值,不抛出异常 * 3.实现Callable接口(call方法),有返回值,抛异常 * 4.通过线程池 * * 第4种获得使用Java多线程的方式,线程池 */
package com.neu.controller.study;
import java.util.concurrent.*;
/**
* 4种使用多线程的方式
* 1.继承Thread类
* 2.实现Runnable接口(run方法),没有返回值,不抛出异常
* 3.实现Callable接口(call方法),有返回值,抛异常
* 4.通过线程池
*
* 线程池
*
* 第4种获得使用Java多线程的方式,线程池
*/
public class MyThreadPoolDemo {
public static void main(String[] args) {
//知道我们的服务器是几核的
System.out.println(Runtime.getRuntime().availableProcessors());//电脑的线程,就是本机电脑的核数
//一般我们都是用继承ThreadPoolExecutor实现的,而不用jdk自带的
ExecutorService threadPool = new ThreadPoolExecutor(
2,//核心线程数
5,//最大线程数
1L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3),//任务缓冲队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());//丢弃任务,但是不抛出异常
try{
for (int i=1;i<=10;i++){
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"\t 办理业务");
});
// try{TimeUnit.MILLISECONDS.sleep(200);}catch(Exception e){e.printStackTrace();}
}
}catch(Exception e){
e.printStackTrace();
}finally{
threadPool.shutdown();
}
}
// public static void threadPoolInit(){
// //System.out.println(Runtime.getRuntime().availableProcessors());//电脑的线程
ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池5个处理线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();//一池一个处理线程
// ExecutorService threadPool = Executors.newCachedThreadPool();//一池N个线程
//
// //模拟10个用户来办理业务,每一个用户就是一个来自外部的请求的线程
// try{
// for (int i=1;i<=10;i++){
// threadPool.execute(()->{
// System.out.println(Thread.currentThread().getName()+"\t 办理业务");
// });
try{TimeUnit.MILLISECONDS.sleep(200);}catch(Exception e){e.printStackTrace();}
// }
//
// }catch(Exception e){
// e.printStackTrace();
// }finally{
// threadPool.shutdown();
// }
// }
}
运行结果,随机使用线程池中的哪个线程进行访问
8
pool-1-thread-1 办理业务
pool-1-thread-2 办理业务
pool-1-thread-2 办理业务
pool-1-thread-3 办理业务
pool-1-thread-2 办理业务
pool-1-thread-4 办理业务
pool-1-thread-1 办理业务
pool-1-thread-5 办理业务
以上是基本的阻塞队列使用的场景,下面进行详细的介绍下线程池