暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

线程池

Java Miraculous 2021-02-07
135

因为线程是稀缺的资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,所以Java中提供线程池对线程进行统一的分配,调优和监控,你可以把线程池看作是一个存放线程的缓存。

拿普通的web容器来说,当一个请求过来,会为它分配一个线程来执行,想象一下,如果并发的请求数量非常多,但是每个线程执行的时间很短,短到还没有创建和销毁这个线程所用的时间长,甚至还不如一个线程处理完一个请求不销毁再去处理下个请求,那么可不可以让线程执行完任务后不销毁,而是让它继续去执行下个任务?线程池可以的。

一、线程池的使用场景

1、单个任务处理时间比较短

2、需要处理的任务数量很大

二、线程池的优势

1、重用存在的线程,减少线程创建和消亡的开销,提高性能;

2、提高响应速度,任务到达时可以不用等到线程创建就立即执行;

3、提高线程的可管理性;

三、JDK中的线程池

Executor接口是线程池框架中最顶层接口,上图是它的实现关系图谱,从图中可以看出Excutor下有一个重要的子接口叫ExecutorService,可以看到里面定义个线程池的一些行为:

    package java.util.concurrent;
    import java.util.List;
    import java.util.Collection;


    public interface ExecutorService extends Executor {

    //执行Runnable类型的任务,此方法在父接口Executor中,放到这是为了便于理解
    void execute(Runnable command);

    //在完成已提交的任务后关闭,不再接受新的任务
    void shutdown();


    //立即停止所有已提交的任务,不管有没有完成
    List<Runnable> shutdownNow();


    //测试线程池是否已经关闭
    boolean isShutdown();


    //测试所有的任务是否已经执行完毕了
    boolean isTerminated();

    //提交Callable或者Runnable类型的任务,并且返回一个Future对象
    Future<?> submit(Runnable task);
    }

    下面看下ThreadPoolExecutor,因为平时开发的时候要么是直接用它,要么继承它自己封装一个线程池,先看下它的构造方法:

      public ThreadPoolExecutor(int corePoolSize,
      int maximumPoolSize,
      long keepAliveTime,
      TimeUnit unit,
      BlockingQueue<Runnable> workQueue) {
      this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
      Executors.defaultThreadFactory(), defaultHandler);
      }

      这里说下它几个重要的参数:

      corePoolSize:线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

      maximumPoolSize:线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;

      keepAliveTime:线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时候没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;

      unit:keepAliveTime的单位;

      workQueue:用来保存等待被执行的任务的阻塞队列,且任务必须实现Runnable,在JDK中提供了如下阻塞队列:

      1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;

      2、LinkedBlockingQueue:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue;

      3、SynchronousQueue:一个不存元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常高于LinkedBlockingQueue;

      4、priorityBlockingQueue:具有优先级的无界阻塞队列;

      threadFactoty:它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory()来创建线程,使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称;

      handler:线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了四种策略:

      1、AbortPolicy:直接抛出异常,默认策略;

      2、CallerRunsPolicy:用调用者所在的线程执行任务;

      3、DiscardOldestPolicy:丢弃阻塞队列中最靠前的任务,并执行当前任务;

      4、DiscardPolicy:直接丢弃任务;

      上面这四种策略都是ThreadPoolExecutor的内部类,当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

      四、线程池的运行原理

      1、如果当前工作的线程数小于corePoolSize,则创建并启动一个线程来执行新提交的任务;

      2、如果当前工作的线程数大于等于corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中;

      3、如果当前工作的线程数大于等于corePoolSize并且小于maximumPoolSize,并且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务;

      4、如果当前工作的线程数大于等于maximumPoolSize,并且线程池的阻塞队列已满,则根据拒绝策略来处理该任务,默认的处理方式是直接抛出异常。

      这篇文章就介绍下线程池的原理,源码层面并未深入分析,以后会开篇文章专门分析,源码就有点多了。

      PS:回家的滋味太爽了,这才是过年嘛,不过也不能一直玩,学习不能丢,哈哈!









      文章转载自Java Miraculous,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

      评论