Java中的线程池 ThreadPoolExecutor

Posted 爱敲代码的三毛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中的线程池 ThreadPoolExecutor相关的知识,希望对你有一定的参考价值。

为什么要有线程池?

如果想了解线程池,首先要明白为什么要有线程池这个东西。
多线程是解决并发编程的方案,但是进程有点太重量了(创建和销毁,开销比较大)
因此引入了线程,线程比进程要轻量很对。即便如此,假如在某些场景中,需要频繁创建销毁线程,此时,线程的创建销毁和开销,也就不能忽视了。
为了解决这样的问题:

  1. 可以引入协程(在单线程里实现多任务的调度,并在单线程里维护多个任务间的切换)
  2. 引入线程池

使用线程池的好处

合理的使用线程池能带来3个好处

  1. 降低资源消耗,通过重复利用已经创建的线程降低线程创建和销毁造成的消耗
  2. 提高响应速度,当任务到达时,任务可以不需要等到线程创建就能立即执行
  3. 提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅会消耗资源,还会降低系统稳定性,使用线程池可以统一分配、调优和监控。

如果是真正的创建或者销毁线程那就涉及到用户态和内核态的切换。
如果只是把线程放到线程池,就相当于全在用户态,在用户态效率就会更高。

用户态:应用程序执行的代码
内核态:操作系统执行的代码
操作系统内核是很忙的,如果把进程某个任务交给操作系统去做,此时这个任务什么时候做完就很难把握了

ThreadPoolExecutor

ThreadPoolExecutor是Java标准库提供的线程池,它的构造方法的参数也是比较复杂的,来看一下它的构造方法的参数都是什么意思吧。

这是 ThreadPoolExecutor 的构造方法

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

ThreadPoolExecutor里面包含的线程的数量并不是一成不变的,能够根据任务量自适应,如果任务比较多,就会多创建一些线程,如果任务比较少,就少创建一些线程。

  • corePoolSize :核心线程数(正式工)

当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建新的线程,等到需要执行的任务数量大于线程池的基本大小时就不再创建。如果调用了线程池的 prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程

  • maximumPoolSize:线程池的最大线程数(正式工+临时工)

如果队列满了,并且以创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。需要注意的是,如果使用了无界的任务队列这个参数就没有什么效果了。
如果我们要解决的任务场景任务量比较稳定,就可以设置 corePoolSize 和 maximumPoolSizejing 尽量接近一些(临时工就可以尽量少一些)

相反如果要解决的任务场景,任务量波动比较大,就可以设置 corePoolSize 和 maximumPoolSize相差更大一些(临时工就可以多一些)
这两者的值设置多少并不好说,要根据机器和任务场景通过实验的方式来确定。

  • keepAliveTime:线程活动保持时间(描述临时工可以摸鱼可以摸鱼多长时间)

线程池的工作线程空闲后,保持存活的时间。这个keepAliveTime时间越短,其实就是越希望吃的资源能更少。

  • TimeUnit unit:keepAliveTime的时间单位(ms,s,minute)

可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一秒)和纳秒(千分之一微秒)

  • workQueue:阻塞队列,就组织了线程池要执行的任务

可以选择好几种阻塞队列
ArrayBlockingQueue:基于数组的有界阻塞队列
LinkedBlockingQueue:基于链表的阻塞队列,静态的工厂方法Executors.newFixedThreadPool ()使用了这个队列
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。
PriorityBlockingQueue:一个具有优先级的无界阻塞队列。

  • threadFactory:线程的创建参数,来设定不同的线程的创建方式

用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。

  • RejectedExecutionHandler handler

拒绝策略(当任务队列满了的时候,又来了新任务)丢弃最新或者最老任务,阻塞等待、抛出异常等

Executors

由于ThreadPoolExecutor使用起来比较复杂,标准库又提供了一组其他的类,相当于对ThreadPoolExecutor又进行了一层封装。

标准库中提供了一个 Executors这个类,这个类相当于一个“工厂类”,通过这个类提供的一组工厂方法,就可以创建出不同风格的线程池实例了。

ExecutorService executorService = Executors.newCachedThreadPool();
  1. newFixedThreadPool: 创建固定线程数的线程池 (完全没有临时工的版本)
  2. newCachedThreadPool: 创建线程数目动态增长的线程池 (完全没有正式员工,全是临时工)
  3. newSingleThreadExecutor: 创建只包含单个线程的线程池.(在特定场景会使用)
  4. newScheduledThreadPool: 能够设定延时时间的线程池(插入的任务能够过一会再执行). 相当于进阶版的Timer.

Executors就是ThreadPoolExecutor类的封装,通过工厂模式直接调用工厂方法来创建实例。

ThreadPoolExecutor 和 Executors 一个简单一个复杂,要根据任务场景来决定用哪个。简单问题用简单解法,复杂问题用复杂解法。

以上是关于Java中的线程池 ThreadPoolExecutor的主要内容,如果未能解决你的问题,请参考以下文章

浅理解java中的线程池

浅理解java中的线程池

浅理解java中的线程池

java笔记java中的线程池ThreadPoolExecutor的原理和使用

浅析java中的四种线程池

Java中的线程池