Java多线程之线程池

103 阅读3分钟

线程初始化的几种方式

  • 继承Thread基类
  • 实现Runnable接口
    • 定义Runnable接口的实现类,重写该接口的run()方法
    • 匿名内部类
    • lambda表达式:函数式接口,有且仅有一个抽象方法,可以有default、static方法,可以使用@FunctionalInterface注解强制约束
  • 实现Callable接口 + FutureTask
    • 重写Callable接口中的call方法
      • 可以抛出异常
      • 可以有返回结果
    • 使用的过程:
      • 实现一个Callable的实现类
      • 初始化Callable实现类对象
      • 初始化一个FutrueTask对象,把Callable实现类对象作为构造参数
      • 以FutureTask对象作为参数初始化一个Thread对象
    • Future接口的特点
      • 定义一个未来任务,在将来的某个时刻可以获取子任务的执行结果集
      • 可以捕获子任务的异常信息
      • 可以获取子任务的返回结果集 - 阻塞子线程
      • 可以轮询获取子任务的执行状态
      • 复用既有的结果集
  • 线程池

线程池

前三种创建线程的方式,都无法控制线程数。
特点:

  • 控制线程数
  • 复用既有线程
  • 管理线程资源

优势:

  • 性能高
  • 方便管理
  • 避免资源浪费

结构

结构:顶层接口Executor(execute) --> 子接口ExecutorService(submit shutdown) --> 抽象类AbstractExecutorService --> 实现类ThreadPoolExecutor

实现类 Executors

  • newFixedThreadPool(线程数):固定大小的线程池,阻塞队列是最大整型
  • newCachedThreadPool():无限大小的线程池,最大可扩展线程数是最大整型
  • newSingleThreadExecutor():单个大小的线程池,阻塞队列是最大整型
  • newScheduledThreadPool(线程数):定时任务的线程池,最大可扩展线程数是最大整型

不推荐使用Executors工具类初始化线程池,因为他们都可能导致OOM。

自定义线程池

ThreadPoolExecutor七个重要的参数

  • corePoolSize:核心线程数
  • maximumPoolSize:最大可扩展线程数
  • keepAliveTime:扩展线程数的空闲时间
  • unit:时间单位
  • BlockingQueue:阻塞队列
  • threadFactory:线程工厂
  • RejectedExecutionHandler:拒绝策略
    • AbortPolicy:抛出异常
    • CallerRunsPolicy:调用者执行
    • DiscardOldestPolicy:丢弃等待最久的
    • DiscardPolicy:默默的丢弃一个
    • 自定义拒绝策略:实现RejectedExecutionHandler

工作原理

  • 线程池初始化之后,线程数为0
  • 向线程池提交一个任务,此时线程池底层会做出如下判断
  • 判断核心线程数是否已满,未满创建新的线程处理该任务
  • 如果核心线程数已满,判断阻塞队列是否已满,未满则放入阻塞队列
  • 如果阻塞队列已满,判断最大可扩展性线程数是否已满,未满则创建新的线程处理该任务
  • 如果最大可扩展线程数已满,执行拒绝策略
  • 一个任务执行完之后,线程会从阻塞队列中获取下一个任务执行
  • 一个线程如果空闲时间到达生存时间,依然要做出判断:判断当前线程数是否大于核心线程数,则销毁当前线程。直至核心线程数

15de6f26d50534b920717419d2f8def.png