17-线程池

nobility 发布于 2021-06-05 445 次阅读


线程池

由于线程的创建与销毁性能开销较大,过多的线程会占用大量内存,所以需要**控制线程总量,线程统一管理,复用线程,**从而提高程序的效率和内存使用量,这就是使用线程池的好处

线程池的创建与停止

线程池创建参数

参数名 类型 描述
corePoolSize int 核心线程数量,线程池中完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待任务到来时再创建新线程去执行任务
maxPoolSize int 最大线程数,线程池在任务队列满的情况下,就会在核心线程数上额外增加一些线程,但最多不能超过最大线程数
keepAliveTime long 保持存活时间,当线程池当前线程数量超过核心线程数量时,多余线程的空闲时间超过该参数设置的时间,该线程就会被回收
workQueue BlockingQueue 当核心线程都在处理任务时,会将任务暂时放入任务队列中,等待被处理,可使用以下几种任务队列:
SynchronousQueue:没有容量的队列
LinkedBlockingQueue:无线容量的队列
ArrayBlockingQueue:固定容量的队列
Handler RejectedExecutionHandler 当新任务提交关闭时、任务队列满且最大线程数也满时,新任务会被拒绝,有以下几种拒绝策略:
AbortPolicy:抛出异常
DiscardPolicy:默默的丢弃新任务
DiscardOldestPolicy:丢弃任务队列中最老的任务,将新任务放入任务队列中
CallerRunsPollicy:那个线程提交的任务,那个线程就自己执行该任务
threadFactory ThreadFactory 线程池的线程是由该线程工厂来创建的,默认使用Executors.defaultThreadFactory(),创建出来的线程都在同一个线程组,拥有相同优先级,不是守护线程,若自己指定线程工厂就可以改变这些新建线程属性

线程添加规则

  1. 线程数小于核心线程数,即使其他工作线程处于空闲状态,也会创建新线程来执行任务
  2. 若线程数大于等于核心线程数,但小于最大线程数,则将任务放入队列
  3. 若任务队列已满,并且线程数小于最大线程数,则创建一个新线程来运行任务
  4. 若队列已满,并且线程数大于等于最大线程数量,则拒绝该任务

线程池的创建

public static void main(String[] args) throws InterruptedException {
  ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,  //核心线程和最大线程数都是1
      0, TimeUnit.SECONDS,  //保持存活时间为0秒
      new ArrayBlockingQueue<>(1),  //任务队列容量为1
      Executors.defaultThreadFactory(),  //使用默认线程工厂
      new ThreadPoolExecutor.CallerRunsPolicy());  //采用CallerRunsPolicy拒绝策略
  for (int i = 0; i < 3; i++) {
    threadPool.execute(() -> {  //提交3个任务
      System.out.println(Thread.currentThread().getName());
    });
  }
}

停止线程池

  • shutdown():关闭新的任务提交
  • shutdownNow():向所有正在执行的线程发出中断信号,清空并返回未执行的任务队列
  • isShutdown():判断新任务提交是否被关闭
  • isTerminated():判断所有任务是否执行完毕
  • awaitTermination(long time):等待指定时间后判断所有任务是否执行完毕,若线程池被关闭则会提前返回true

内置线程池

  • Executors.newFixedThreadPool(int nThread):返回固定线程数的线程池,核心线程数与最大线程数相等,无线容量任务队列,不会拒绝任务,可能会被撑爆
  • Executors.newSingleThreadExecutor():返回固定线程数量为1的线程池,核心线程数与最大线程数相等,无线容量任务队列,不会拒绝任务,可能会被撑爆
  • Executors.newCachedThreadPool():返回可缓存的线程池,核心线程数为0,最大线程数为整形最大值,没有容量的任务队列,每一个新任务都会创建一个线程去执行,线程都保持存活时间为60秒,当任务过多时可能会被撑爆
  • Executors.newScheduledThreadPool(int corePoolSize):返回执行定时以及周期性任务的线程池,最大线程数为整形最大值,没有容量的任务队列,每一个新任务都会创建一个线程去执行,线程都保持存活时间为0秒,当任务过多时可能会被撑爆;延时任务使用schedule()方法,周期任务使用scheduleAtFixedRate()方法
  • Executors.newWorkStealingPool():JDK1.8新增,返回任务中可含有子任务的线程池,每个子任务都会放到各自的任务队列中,空闲线程会窃取非空闲线程的任务,帮助非空闲线程执行,从而提高了效率,但是这样就不能保证任务的执行顺序了

任务的AOP

继承ThreadPoolExecutor类重写构造方法,重写beforeExecute()afterExecute()方法实现任务的AOP

public class MyThreadPoolExecutor extends ThreadPoolExecutor {
  public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
  }

  @Override
  protected void beforeExecute(Thread t, Runnable r) {
    super.beforeExecute(t, r);
    System.out.println("任务执行前");
  }

  @Override
  protected void afterExecute(Runnable r, Throwable t) {
    super.afterExecute(r, t);
    System.out.println("任务执行后");
  }

  public static void main(String[] args) {
    MyThreadPoolExecutor myThreadPoolExecutor = new MyThreadPoolExecutor(1, 1,
        0, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>());
    for (int i = 0; i < 2; i++) {
      myThreadPoolExecutor.execute(() -> {
        System.out.println("任务执行中...");
      });
    }
  }
}

线程池原理

线程池的体系结构

线程池的体系结构

线程池的状态

  • RUNNING:接收新任务,并处理任务队列中的任务
  • SHUTDOWN:不接收新任务,但处理任务队列中的任务
  • STOP:不接收新任务,也不处理任务队列中的任务,并中断正在运行的任务
  • TIDYNG:所有任务都已经完成,即将运行terminate()钩子方法
  • TERMINATEA:terminate()钩子方法运行完毕

线程池的运行原理

线程池的运行原理

线程池注意事项

合适的线程数

  • CPU密集型任务:线程数量设置为CPU核心数量的1到2倍,比如加密解密、计算hash等
  • 耗时IO型任务:线程数量设置为CPU核心数量的很多倍,从而保证线程空闲时可以衔接上,具体可参考使用公式设置线程数=CPU核心数(1+平均等待时间/平均工作时间)设置线程数=CPU核心数*(1+平均等待时间/平均工作时间)

合适的任务

  • 避免任务堆积、线程数过度增加:任务、线程过多可能会导致内存溢出
  • 避免线程泄露:任务执行结束线程却无法回收
此作者没有提供个人介绍
最后更新于 2021-06-05