乐趣区

关于多线程:线程池基本介绍与使用

线程池根本介绍与应用

咱们晓得,在 Java 中,创建对象,仅仅是在 JVM 的堆里调配一块内存而已;而创立一个线程,却须要调用操作系统内核的 API,而后操作系统要为线程调配一系列的资源,这个老本就很高了,所以线程是一个重量级的对象,应该防止频繁创立和销毁。

所以 Java 中提供了线程池,其本质就是一个包容多个线程的容器,其中的线程能够重复应用,省去了频繁创立线程对象的操作,无需重复创立线程而耗费过多资源。

JDK 线程池相干类

JDK 中提供的无关线程池的相干类及其关系如下图:

  • Executor 接口:线程池的形象接口,只蕴含一个 execute 办法。
  • ExecutorService 子接口:提供了无关终止线程池和 Future 返回值的一些办法。
  • AbstractExecutorService 抽象类:提供了 ExecutorService 的一些默认实现。
  • ThreadPoolExecutor 类:JDK 提供的线程池的实现类。
  • Executors 类:线程池工厂类,提供了几种线程池的工厂办法。

上面次要介绍 JDK 提供的线程池实现类 – ThreadPoolExecutor 类。

ThreadPoolExecutor 类

Java 提供的线程池相干的工具类中,最外围的是 ThreadPoolExecutor。

构造方法

ThreadPoolExecutor 的构造函数非常复杂,如上面代码所示,这个最齐备的构造函数有 7 个参数。

ThreadPoolExecutor(
  int corePoolSize,
  int maximumPoolSize,
  long keepAliveTime,
  TimeUnit unit,
  BlockingQueue<Runnable> workQueue,
  ThreadFactory threadFactory,
  RejectedExecutionHandler handler) 

上面一一介绍下参数的含意。

corePoolSize
  • 默认状况下,在创立了线程池后,线程池中的线程数为 0,当有工作来之后,才会去创立一个线程来执行工作
  • 当线程池中的线程数目达到 corePoolSize 后,就进行线程创立,转而会把工作放到工作队列当中期待。
  • 调用 prestartAllCoreThreads() 或者 prestartCoreThread() 办法,能够预创立线程,即在没有工作到来之前就创立 corePoolSize 个线程或者一个线程
maxPoolSize
  • 当线程数大于或等于外围线程,且工作队列已满时,线程池会创立新的线程,直到线程数量达到 maxPoolSize。
  • 如果线程数已等于 maxPoolSize,且工作队列已满,则已超出线程池的解决能力,线程池会回绝解决工作而抛出异样。
keepAliveTime & unit

当线程闲暇工夫达到 keepAliveTime,单位 unit 时,该线程会退出,直到线程数量等于 corePoolSize。

workQueue

工作队列,一个阻塞队列,用来存储期待执行的工作,倡议 workQueue 不要应用无界队列,尽量应用有界队列。防止大量工作期待,造成 OOM。反对有界的阻塞队列有 ArrayBlockingQueue 和 LinkedBlockingQueue。

threadFactory

线程工厂,通过这个参数你能够自定义如何创立线程,例如你能够给线程指定一个有意义的名字。

handler

回绝策略。如果线程池中所有的线程都在繁忙,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交工作,线程池就会回绝接管,能够通过 handler 这个参数来指定回绝的策略。ThreadPoolExecutor 曾经提供了以下 4 种策略:

  • ThreadPoolExecutor.AbortPolicy:默认的回绝策略。抛弃工作并抛出 RejectedExecutionException 异样。
  • ThreadPoolExecutor.DiscardPolicy:抛弃工作,然而不抛出异样。
  • ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列期待最久的工作,而后从新尝试执行工作(反复此过程)
  • ThreadPoolExecutor.CallerRunsPolicy:间接在 execute 办法的调用线程中运行被回绝的工作。

罕用办法

提交工作办法

void execute(Runnable command):提交工作给线程池执行,工作无返回值
Future<?> submit(Runnable task):因为 Runnable 接口没有返回值,所以 Future 返回值执行 get() 办法返回值为 null,作用只是期待,相似于 join
<T> Future<T> submit(Runnable task, T result):因为 Runnable 没有返回值,所以额定提供了一个参数,作为返回值。<T> Future<T> submit(Callable<T> task):提交工作给线程池执行,可能返回执行后果。

其余办法

void allowCoreThreadTimeOut(boolean value):是否容许外围线程超时,默认 false。shutdown():敞开线程池,期待工作都执行完
shutdownNow():敞开线程池,不期待工作执行完,并返回期待执行的工作列表。getTaskCount():线程池已执行和未执行的工作总数
getCompletedTaskCount():已实现的工作数量
getPoolSize():线程池以后的线程数量
getActiveCount():以后线程池中正在执行工作的线程数量 

线程池工作的执行流程

线程池工作的个别执行流程图如下图所示:

线程池初始化示例

上面是一个线程池初始化的示例,仅供参考

// 初始化示例
private static final ThreadPoolExecutor pool;

static {ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("po-detail-pool-%d").build();
    pool = new ThreadPoolExecutor(
            4,
            8, 
            60L, 
            TimeUnit.MILLISECONDS, 
            new LinkedBlockingQueue<>(512),
            threadFactory, new ThreadPoolExecutor.AbortPolicy());
    pool.allowCoreThreadTimeOut(true);
}

初始化参数含意解释:

  • threadFactory:给出带业务语义的线程命名。
  • corePoolSize:疾速启动 4 个线程解决该业务,是足够的。
  • maximumPoolSize:IO 密集型业务,我的服务器是 4C8G 的,所以 4 *2=8。
  • keepAliveTime:服务器资源缓和,让闲暇的线程疾速开释。
  • pool.allowCoreThreadTimeOut(true):也是为了在能够的时候,让线程开释,开释资源。
  • workQueue:一个工作的执行时长在 100~300ms,业务高峰期 8 个线程,依照 10s 超时(曾经很高了)。10s 钟,8 个线程,能够解决 10 1000ms / 200ms 8 = 400 个工作左右,往上再取一点,512 曾经很多了。
  • handler:极其状况下,一些工作只能抛弃,爱护服务端。

线程池应用注意事项

  • 防止应用 Executors 类创立线程池,会有 OOM 危险。
  • 创立线程或线程池时请指定有意义的线程名称,不便出错时回溯。即 threadFactory 参数要结构好。
  • 倡议不同类别的业务用不同的线程池,至于线程池的数量,各自计算各自的,而后去做压测。
  • workQueue 不要应用无界队列,尽量应用有界队列。防止大量工作期待,造成 OOM。反对有界的阻塞队列有 ArrayBlockingQueue 和 LinkedBlockingQueue。
  • 如果是资源缓和的利用,应用 allowsCoreThreadTimeOut 能够进步资源利用率。
  • 尽管应用线程池有多种异样解决的形式,但在工作代码中,应用 try-catch 最通用,也能给不同工作的异样解决做精细化。
  • 线程池默认的回绝策略会 throw RejectedExecutionException 这是个运行时异样,对于运行时异样编译器并不强制 catch 它,所以开发人员很容易疏忽。因而默认回绝策略要谨慎应用。如果线程池解决的工作十分重要,倡议自定义本人的回绝策略;并且在理论工作中,自定义的回绝策略往往和降级策略配合应用。
  • CPU 密集型工作,最大线程数初始值能够配置 N +1。I/ O 密集型工作,最大线程数初始值能够配置 2N。之后再能够依据压测来调整。

参考资料

  • 10 问 10 答:你真的理解线程池吗?
  • 图解 | 你管这破玩意叫线程池?
  • Executor 与线程池:如何创立正确的线程池?- 极客工夫
退出移动版