阻塞队列与线程池基础

250 阅读6分钟
  • 阻塞队列:在架构中产生了许多中间件(在Android中的消息也有)

    • 队列:先进先出的数据结构

    • 常用的阻塞队列

      • 示意图:

        image-20220223134649774

      • 重要概念:

        • 有界:队列长度有限,满了,生产者阻塞

        • 无界:随便放,没有最大长度,没有数据的时候就阻塞

          • 不可能无限的,资源有限(太过分了,JVM直接删掉)
      • 部分函数:

      • ArrayBlockingQueue:必须传入队列容量,根据放入的顺序决定队列位置

        • 只有一个构造:

           public ArrayBlockingQueue(int capacity) {
               this(capacity, false);
           }
          
      • PriorityBlockingQueue:优先级队列中只指定了队列初始化长度,但是不限制队列的长度上限,类似Handler

        • 构造函数一:只指定了队列初始化长度

             public PriorityBlockingQueue(int initialCapacity) {
                   this(initialCapacity, null);
               }
          
        • 构造函数二:指定了队列初始化长度+比较器

           //在构造函数中传比较器(默认是以字典序)进来
           //默认情况下是自然的放入顺序,但是可以自定义比较器(构造函数的时候),对放入的元素进行排序(底层由堆进行实现的)
               public PriorityBlockingQueue(int initialCapacity,
                                            Comparator<? super E> comparator) {
                   if (initialCapacity < 1)
                       throw new IllegalArgumentException();
                   this.lock = new ReentrantLock();
                   this.notEmpty = lock.newCondition();
                   this.comparator = comparator;
                   this.queue = new Object[initialCapacity];
               }
          
      • DelayQueue:

        • 实现了BlockingQueue接口(传入一个泛型,对泛型有约束,这个泛型需要实现Delayed接口)

           public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
               implements BlockingQueue<E> {
          
        • Delayed接口:派生自Comparable接口,内部还有一个方法(返回当前元素的剩余时间,谁短就先拿谁出来):设计单机的缓存系统(对于放入的元素设置有限期,还有元素最小使用等等)

           public interface Delayed extends Comparable<Delayed> {
           ​
               //不同于一般阻塞队列(队列不空,拿元素的动作不会被阻塞),个DelayQueue在取出元素时(队列不空+元素的剩余时间为0)
               long getDelay(TimeUnit unit);
           }
          
      • SynchronousQueue:不存储元素的阻塞队列,处理数据的直接传递(消去直接传递中的耦合)就像流水线,只是将东西扔到传送带

        • 在生产者向队列put时,在另一端必须有消费者调用take()马上拿走;不然生产者是放不进去的
      • LinkedTransferQueue:不直接放入阻塞队列,有尝试操作

    • 阻塞队列:BlockingDeque(接口,有阻塞方法与非阻塞方法)处理生产者,消费者问题

      • 具体阻塞问题:引入容器(解决IO差异,解耦)

        • 生产者太快,被阻塞
        • 消费者太快,被阻塞
      • 阻塞条件:队列满,还想进,被阻塞;队列空,还想出,被阻塞

      • 在java中的体现:方法基本成对出现

        • 非阻塞方法:

          • 队列插入

            • add:插不进,抛出异常
            • offer:插不进,返回false
          • 队列取出:

            • remove:没有拿出的,抛出异常
            • poll:没有拿出来的,返回null
        • 阻塞方法:

          • 队列插入:

            • put:当队列满了,向其中放元素(这个动作被阻塞)
          • 队列取出:

            • take:当队列为空,往外取出元素(这个动作被阻塞)
        • 示意图:

          image-20220223133856410

  • 线程池:事先创建一批线程在哪里,要用就去拿(节省资源,时间,复用)

    • 线程池解决的问题:

      • 线程:是有资源消耗(创建、销毁的动作)
      • 对于任务执行:线程的创建时间,任务的执行时间,线程的销毁时间
      • 线程是OS中很稀缺的资源(消耗CPU,消耗内存)
      • 线程太多导致机器宕机
  • JDK中的线程池与工作机制

    • 接口描述:

      • 在JDK中线程池的顶层接口是Exeutor:这个接口其实没有什么具体的操作,提供了一个excute方法而已:并将任务的提交与执行做一次拆分

         public interface Executor {
         ​
             void execute(Runnable command);
         }
        
      • 真正在线程池中使用的接口:ExecutorService内的接口(尤其是ThreadPoolExecutor)

        image-20220223142440553

    • ThreadPoolExecutor

      • 线程池的创建:参数的含义:

        • 构造方法:

               public ThreadPoolExecutor(int corePoolSize,
                                         int maximumPoolSize,
                                         long keepAliveTime,
                                         TimeUnit unit,
                                         BlockingQueue<Runnable> workQueue,
                                         ThreadFactory threadFactory,
                                         RejectedExecutionHandler handler) 
          
        • int corePoolSize:线程池核心线程数,正在跑的线程

        • int maximumPoolSize:线程池所能使用的线程最大数量

        • long keepAliveTime与TimeUnit unit(单位s,ms由程序猿决定)

          • 当能使用的线程最大数量大于核心线程数:证明有线程空闲

          • 这两个参数就用来控制空闲线程的回收

            • 当keepAliveTime>unit线程回收
        • BlockingQueue workQueue)

          • 线程用于处理任务的,当需要处理的任务(1000个)大于线程池最大线程数(100个,线程池满载工作),这个时候就先将任务放到阻塞队列里面去
        • ThreadFactory threadFactory:

          • 在线程创建的时候进行微调,定义一个名字
        • RejectedExecutionHandler handler)

          • 当阻塞队列容量为1000,线程池最大线程数100,此时最多可以接受1100个任务;但是来了2101个任务;那么对超出线程池能力的任务就需要才用具体的拒绝策略,默认就不要这些任务了
      • 线程池的工作机制:

        • 具体流程:

          1. 定义好corePoolSize(假设为3个),那么启动线程池(里面就有3个线程了)
          1. 执行excute:向线程池提交任务,此时的corePool中的线程在任务提交之前是空闲的,那么就开始做任务了,直到线程满载

          2. 当corePool满载,外界继续向线程池提交任务:

            • 将任务扔到阻塞队列中去,直到阻塞队列填满了
          3. 再提交任务,那么就兴起新的线程,放到corePool里面去执行任务,直到corePool中的线程数等于实现设定的线程池最大线程数(maximumPoolSize)

          4. 还要提交任务:此时对于这部分超出线程池预定能力的任务就使用拒绝策略进行处理

        • 示意图:

          image-20220223143656929

      • 线程池的拒绝策略:没有所谓优先级,一个线程池就只能有一种拒绝策略

        • 示意图:

          image-20220223144452017

        1. 扔掉队头元素

        2. 抛出异常(线程池的默认拒绝策略)

        3. 让调用者线程去执行任务,谁说要干,就让谁去干

        4. 将最新的任务扔掉

        5. 自己去实现接口,然后扔给线程池的构造函数

          • Android中的写个日志,后面来玩
      • 向线程池中提交任务

        • excute(ThreadPoolExecutor的):接收一个Runnable,不关心线程的执行结果

           public void execute(Runnable command) {
               if (command == null)
                   throw new NullPointerException();
          
        • submit(AbstractExecutorService(ThreadPoolExecutor的父类))的:关心线程的执行结果(需要拿到这个返回值)

          image-20220223145159445

      • 线程池的关闭:

        • shutdown:尝试关闭一个线程池,将当前没有执行任务的线程中断

        • shutdownNow:不管线程是否执行任务,直接中断

          • 不一定成功:

            • 需要在线程中检测中断信号(线程的中断是一种协作机制)
            • 中断就只是发一个信号而已

\