了解线程池

线程池:线程池就是管理一系列线程的资源池,当有任务时,直接从线程池中拿出已经创建好的线程来处理,处理完后的线程被放回线程池中,并不会被销毁。

优点:线程池能够降低重复创建和销毁线程所来带的资源消耗,由于能在需要时就能获取已经创建的线程,响应速度会更快。

我们可以通过ThreadPoolExecutor类创建一个线程池执行器,通过该执行器调用execute/submit方法执行线程。

image-20230609110810815

参数介绍:

  • corePoolSize(核心线程大小):线程池中维护的一个最小的线程数量,即使这些线程处于空闲状态,也不会被销毁。当任务提交到线程池时,如果发现当前线程数超过了此参数,池子中就会创建新的线程
  • maximumPoolSize(最大线程大小):线程池中的最大线程数量限制,当线程数达到corePoolSize后,如果继续有任务被提交到线程池,会将任务缓存到工作队列。如果队列也满了,就会创建新的线程。
  • workQueue(工作队列):新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。
  • handler(拒绝策略):当工作队列中的任务以及达到最大限度,并且池子中的线程数也到达了maximumPoolSize,如果此时有新来的任务,这时候就会触发设置好的拒绝策略。

线程池处理任务的流程:

  1. 起初如果当前运行的线程数<核心线程数,就会创建一个线程来执行任务
  2. 如果这时运行的线程数>=核心线程数,但是<最大线程数,任务会被放入工作队列中等待执行
  3. 如果此时运行的线程数核心在2的基础上,工作队列也满了,那么就会创建新的线程来执行任务
  4. 如果此时运行的线程数超出了最大线程数并且工作队列也满了,就会触发设置好拒绝策略,当前的任务会被拒绝

jdk中提供了4中拒绝策略:

  • AbortPolicy:直接丢弃任务,并抛出RejectedExecutionException异常
  • DiscardPolicy:直接丢弃任务,之后什么都不做
  • DiscardOldestPolicy:丢弃最早进入的任务,尝试把当前的任务加进工作队列
  • CallerRunsPolicy:在调用者线程中直接执行被拒绝任务的run方法

jdk提供的线程池类型有以下4种:

  • FixedThreadPool(定长线程池):只有核心线程,线程数固定。执行完立即回收,任务队列为链表结构的有界队列。
  • ScheduledThreadPool(定时线程池):核心线程数量固定,非核心线程数量无限,任务队列为延时阻塞队列。提交任务后可以延迟指定延迟时间后执行
  • CachedThreadPool(可缓存线程池):无核心线程,非核心线程数量无限,任务队列为不存储元素的阻塞队列。适合执行大量但耗时小的任务。
  • SingleThreadExecutor(单线程执行器):只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列

常用的工作队列(阻塞队列):

  • LinkedBlockingQueue:由链表组成的有界队列,默认大小为Integer.MAX_VALUE,可能OOM
  • SynchronousQueue:一个不存储元素的队列,目的是为了进来一个任务就创建一个新的线程来执行
  • DelayQueue:可以从此队列中延迟获取元素,创建元素时可以指定多久时间后才能从队列中获得当前元素

线程池中线程的名称如何修改?

默认线程工厂(DefaultThreadFactory)中创建出的线程名称是:pool-m-thread-n形式的。其中m是创建的工厂编号,n是执行任务的线程编号

image-20230610014403105

默认的线程命名形式没有业务含义,有时为了能定位到出现问题的线程,需要我们自定义线程工厂。

class MyThreadFactory implements ThreadFactory{
    private final String factoryName;
    private final ThreadFactory factory;
    // 创建这个自定义线程工厂类的有参构造,用于线程名称赋值
    public MyThreadFactory(ThreadFactory factory,String factoryName) {
        this.factoryName = factoryName;
        this.factory = factory;
    }

    // 实现ThreadFactory接口的方法
    @Override 
    public Thread newThread(Runnable r) {
        Thread thread = factory.newThread(r);
        thread.setName(factoryName);
        return thread;
    }
}

**除此之外,还可以利用 guava 的 ThreadFactoryBuilder **

ThreadFactory threadFactory = new ThreadFactoryBuilder()
                        .setNameFormat(threadNamePrefix + "-%d")
                        .setDaemon(true).build();
ExecutorService threadPool = 
    new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory)

线程池大小设置过大会怎么样?

过大主要会增加上下文切换的成本

什么是上下位切换:

我们知道,CPU的一个核心在任意一时刻只能被一个线程使用,但在多线程场景下,线程数量是会大于CPU的核心数的。

为了让每个线程都能正常执行,CPU采用时间片轮用的方式执行线程,当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。

切换当前任务时会中断,然后保存此时的状态信息,再转去执行其他线程。任务从保存到再加载的过程就是一次上下文切换

所以如果线程数设置过大,上下文切换的次数就会变多,导致消耗大量的CPU时间。


  转载请注明: 流浪秃球计划 了解线程池

 上一篇
如何解决幂等性问题 如何解决幂等性问题
什么是幂等性? 定义:对某个资源进行一次或多次请求后,产生的结果都是一样的。 常见的例子就是前端的表单提交时进行的POST请求,正常预想就是用户填写资料,点击了一次按钮,后端接收后数据库进行一个insert操作。 但试想一下,如果这时我们没
2023-07-15 Touko
下一篇 
JavaSE的易遗漏知识点补充 JavaSE的易遗漏知识点补充
1.HashMap和ConcurrentHashMap的区别ConcurrentHashMap相比前者,它是能够保证线程安全的。在1.7时,它在hashmap上加了个维度,其实是个二维的哈希表,它把一个个hashmap视为一个个Segmen
2023-05-22
  目录