【阻塞队列《二》】阻塞队列用到哪里了?

我们上一篇解释了什么是阻塞队列,队列的架构梳理和种类分析(https://blog.csdn.net/qq_30546099/article/details/112723231),那么这篇文章就谈谈阻塞队列到底用到什么地方了,并详细解释

【问题一】阻塞队列用到了什么地方

  1. 生产者消费者
  2. 线程池
  3. 消息中间件

 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     办理业务

以上是基本的阻塞队列使用的场景,下面进行详细的介绍下线程池

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值