并发基础(四):线程池

news2025/7/5 3:35:57

尺有所短,寸有所长;不忘初心,方得始终。

请关注公众号:星河之码

线程池技术是一种多线程处理形式,将任务添加到队列中,通过线程池中创建出来的现成执行这些任务,省去了创建线程和销毁线程的消耗,我认为有点类似JAVA中Spring托管对象的思想在里面

一、为啥要用线程池

使用线程池可以根据系统的需求和硬件环境灵活的控制线程的数量,对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行运行压力。总的来说有三点:

  • 降低资源消耗

    通过系统的需求和硬件环境创建线程数,重复利用已创建的线程,降低线程创建和销毁造成的消耗。

  • 提高响应速度

    当任务到提交时,不需要创建线程,立即可以执行,执行完不需要销毁线程,线程池回收,最大限度的提高效率。

  • 提高线程的可管理性

    线程由线程池进行统一分配、调优和监控,提高系统的稳定性。

二、线程池应该怎么设计

理解了上面为啥要用线程池,有了目的也就有了方向,有了方向就知道设计一个线程池思路了

  • 先要有一个队列,存储要执行的任务

    线程数有限,多余任务放在队列中等待执行

  • 线程池中放多少个线程,同一时间线程池中可以执行多少个线程,得有一个大小限制

    线程池也不能无限大小

  • 啥时候创建线程放在线程池中呢

  • 所有的线程都是一直存活不销毁的吗

  • 队列满了以后,后面的任务怎么处理

  • 线程池本身的生命周期时什么

当我们理解了这些问题之后,设计一个线程就会有方向跟思路了,同样的,我们也能围绕这些问题更好的去理解线程池的原理实现了。

三、线程池工作流程

3.1 线程池的创建

在Java中,通过ThreadPoolExecutor 实现线程池技术,线程池的工作原理机其实主要就是在说ThreadPoolExecutor的工作流程

ThreadPoolExecutor是jdk自带的,在java.util.concurrent包中

ThreadPoolExecutor有四个构造方法,可以提供给我们创建线程池实例。


实际上四个构造方法中真正被使用的是第四个,也就是有7个参数的那个,其他三个都是通过默认的参数调用的第四个,源码如下

在第四个构造方法中才是真正创建ThreadPoolExecutor对象的地方

3.2 线程池参数

通过上述的源码中的构造函数可以看到,创建线程池一共有7个 参数

  • corePoolSize

    线程池核心线程数最大值

  • maximumPoolSize

    线程池最大线程数大小

  • keepAliveTime

    线程池中非核心线程空闲的存活时间大小

  • unit

    线程空闲存活时间单位

  • workQueue

    存放任务的阻塞队列

  • threadFactory

    用于设置创建线程的工厂,可以给创建的线程设置名字,可方便排查问题。

    一般业务中会有多个线程池,可以根据线程池的名字去定位是哪一个线程出问题

  • handler

    线程池的饱和策略事件,主要有四种类型。

3.3 线程池执行流程

  • 当提交一个新的任务到线程池时,首先判断线程池中的存活线程数是否小于corePoolSize,如果小于说明有空闲的核心线程,线程池会创建一个核心线程去处理提交的任务。

  • 如果线程池中的存活线程数是已经等于corePoolSize,新提交的任务会被存放进任务队列workQueue排队等待执行。

  • 如果存活线程数已满,并且任务队列workQueue也满了,线程池会判断存活线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没满,则创建一个非核心线程执行提交的任务。

  • 如果当前存活的线程数达到了maximumPoolSize,则针对新的任务直接采用拒绝策略处理

四、阻塞队列

当线程池中的活跃线程数达到【核心线程数】的时候,新的任务就会别放在阻塞队列中等待执行

在ThreadPoolExecutor 的构造函数中的有一个BlockingQueue的参数。常用的阻塞队列都是BlockingQueue接口的实现类,常用的主要有以下几种:

  • ArrayBlockingQueue

    有界队列:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序

  • LinkedBlockingQueue

    可设置容量队列:基于链表结构的阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序

    • 容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE
    • 吞吐量通常要高于ArrayBlockingQuene
    • 静态工厂方法Executors.newFixedThreadPool()线程池使用了这个队列
  • DelayQueue

    延迟队列:任务定时周期的延迟执行的队列,根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序

    • newScheduledThreadPool线程池使用了这个队列
  • PriorityBlockingQueue

    优先级队列:支持优先级的无界阻塞队列。默认情况下元素采用自然顺序升序排列

  • SynchronousQueue

    同步队列:不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态

    • 吞吐量通常要高于LinkedBlockingQuene
    • newCachedThreadPool线程池使用了这个队列

五、四种拒绝策略

拒绝策略也叫饱和策略:当线程池中的活跃线程数达到【最大线程数】的时候,线程池接收到新的任务就会执行拒绝策略

在ThreadPoolExecutor 的构造函数中的有一个RejectedExecutionHandler的参数。jdk默认有是个实现类,即四种拒绝策略

  • AbortPolicy

    丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。

    使用这个策略必须处理抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。

  • DiscardPolicy

    直接丢弃任务,不做任何处理

  • DiscardOldestPolicy

    丢弃阻塞队列 workQueue 中最老的一个任务,将当前这个任务继续提交给线程池,加入队列中

  • CallerRunsPolicy(交给线程池调用所在的线程进行处理)

    交给线程池调用所在的线程进行处理,即调用线程池的主线程处理

    由于是主线程自己处理,相当于没有用线程池,一般并发比较小,性能要求不高可以用,否则可能导致程序阻塞。

六、动态调整线程池参数

在日常开发中,我们可能需要根据实际的业务场景调整线程池的参数,ThreadPoolExecutor针对其构造方法为我们提供了几个方法可以调整常用的参数

以上参数也可以通过动态配置中心进行动态修改

线程池中最重要的参数,API以及提供了动态更新的方法。可以配合动态配置中心进行动态修改。

  1. 先对线程池进行监控
  2. 当队列数量过多或过少的时候,以及业务指标的时候,可以动态修改线程池核心参数来调整。

七、监控线程池

当我们需要对线程池参数进行调整,而又很难确定 corePoolSize, workQueue,maximumPoolSize等参数达到什么样的大小时才符合业务指标的时候,我们就需要对线程池进行监控

同样的ThreadPoolExecutor也提供了可以监控线程池的使用情况的几个方法:

方法含义
getActiveCount()线程池中正在执行任务的线程数量
getCompletedTaskCount()线程池已完成的任务数量,该值小于等于taskCount
getCorePoolSize()线程池的核心线程数量
getLargestPoolSize()线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过,也就是达到了maximumPoolSize
getMaximumPoolSize()线程池的最大线程数量
getPoolSize()线程池当前的线程数量
getTaskCount()线程池已经执行的和未执行的任务总数

八、向线程池提交任务

ThreadPoolExecutor提供了两个方法向线程池提交任务,分别为execute()和submit()方法

  • execute

    execute()方法用于提交不需要返回值的任务,无法判断任务是否被线程池执行成功

     public static void main(String[] args) {
            ThreadPoolExecutor threadPoolExecutor =
                    new ThreadPoolExecutor(10,20,5,
                            TimeUnit.MINUTES,new LinkedBlockingQueue<>() );
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("ThreadPoolExecutorTest 测试----->>>>>子线程执行 : "+ Thread.currentThread().getName());
                }
            });
        }
    
  • submit

    submit方法用于提交需要返回值的任务。调用submit方法线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值。

    get()方法会阻塞主线程直到任务完成,而使用get(long timeout,TimeUnit unit),在指定的时间内会等待任务执行,超时则抛出超时异常,等待时候会阻塞当前线程

    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(10,20,5,
                        TimeUnit.MINUTES,new LinkedBlockingQueue<>() );
        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("ThreadPoolExecutorTest 测试----->>>>>子线程执行 : " + Thread.currentThread().getName());
            }
        });
    }
    

    ThreadPoolExecutor中submit有三种实现:

  • submit 为什么能提交任务(Runnable)的同时也能返回任务(Future)的执行结果

    通过追踪submit 三个先实现的源码发现,submit方法最终也是调用的execute方法,但是在调用execute之前还调用了一个newTaskFor 方法

    继续追踪newTaskFor 方法,发现newTaskFor 将 task 封装成了 RunnableFuture,最终返回的是FutureTask 这个类。而FutureTask的继承关系如下

    到这里就清晰了,submit之所以提交任务(Runnable)的同时也能返回任务(Future)的执行结果,主要分为两步:

    • 通过execute方法提交执行任务
    • 通过FutureTask返回任务(Future)的执行结果

九、关闭线程池

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow()

这两个的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程

  • shutdown()

    不会立即终止线程池,只是将线程池的状态设置成shutdown状态,此时不会接受新的任务,然后等所有任务z都执行完后才终止(包含正在执行和缓存队列中的任务)

  • shutdownNow()

    立即终止线程池,将线程池的状态设置成STOP,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

可以看到上图中还有一个isShutdown的方法,实际上还有其他两个方法

  • isShutDown

    当调用shutdown()或shutdownNow()方法后返回为true

  • isTerminated

    当调用线程池关闭,并且所有提交的任务都执行完成后返回为true

  • isTerminating

    当调用线程池关闭,并且所有提交的任务都执行完成后返回为false

因此,判断线程池所有线程是否执行完成,可以这样写:

while(true){
   if(threadPool.isTerminated()) {
       //当调用线程池关闭,并且所有提交的任务都执行完成  跳出循环
       break;//true停止
   }
   Thread.sleep(500);//休眠500继续循环
}

十、线程池的五种运行状态

线程有六种状态,线程池同样也有状态,在ThreadPoolExecutor的源码中定义了线程池的五种运行状态,分别是:Running、ShutDown、Stop、Tidying、Terminated

private static final int RUNNING    = -1 << COUNT_BITS;	// 运行中
private static final int SHUTDOWN   =  0 << COUNT_BITS;	// 已关闭
private static final int STOP       =  1 << COUNT_BITS; // 已停止
private static final int TIDYING    =  2 << COUNT_BITS; // 清洁中
private static final int TERMINATED =  3 << COUNT_BITS; // 已终止

  • Running

    • 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
    • 状态切换:线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,且线程池中的任务数为0
  • ShutDown

    • 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
    • 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
  • STOP

    • 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且中断正在处理的任务。
    • 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
  • Tidying

    • 状态说明:当所有的任务已终止,任务数量为0,线程池会变为TIDYING状态。

      当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。

    • 状态切换:

      当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。

      当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

  • Terminated

    • 状态说明:线程池彻底终止,就变成TERMINATED状态。
    • 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

十一、 常用线程池的类型

Java通过Executors提供六种线程池,分别为:

  • newFixedThreadPool :固定数目线程的线程池
  • newCachedThreadPool:可缓存线程的线程池
  • newSingleThreadExecutor:单个线程的线程池
  • newScheduledThreadPool:定时及周期执行的线程池
  • newSingleThreadScheduledExecutor
  • newWorkStealingPool:足够大小的线程池(JDK 1.8 后新加)

通过Executors源码可以看到,jdk为我们提供了6种线程池,每种提供了有参和物参两个构造方法。

11.1 newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。源码如下

   public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  • 特点

    通过以上源码可以看出newFixedThreadPool具有以下特点

    • 核心线程数和最大线程数大小一样
    • 没有所谓的非空闲时间,即keepAliveTime为0
    • 阻塞队列为无界队列LinkedBlockingQueue
  • 工作机制

    • 当有一个新的任务提交到线程池
    • 如果线程数少于核心线程,创建核心线程执行任务
    • 如果线程数等于核心线程,把任务添加到LinkedBlockingQueue阻塞队列
    • 如果线程执行完任务,去阻塞队列取任务,继续执行。
  • 使用场景

    newFixedThreadPool适用于处理CPU密集型的任务,能够保证CPU在工作时尽可能的减少线程分配,即适用执行长期稳定的任务。

  • 案例

    线程池大小为10,每个任务输出index后sleep 1秒,因此每秒会打印10个数字。

    public static void main(String[] args) {
    
        ExecutorService executor = Executors.newFixedThreadPool(10);
    
        for (int i = 0; i < 100; i++) {
            final int index = i;
            executor.execute(() -> {
                try {
                    System.out.println(index);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
    

newFixedThreadPool 是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。同时在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。因此定长线程池的大小一般要根据系统资源进行设置。

11.2 newCachedThreadPool

创建一个可缓存线程池,当线程空闲时可灵活回收空闲线程,当有新任务时,没有空闲线程则新建线程。源码如下

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
  • 特点

    • 核心线程数为0
    • 最大线程数为Integer.MAX_VALUE
    • 阻塞队列是SynchronousQueue
    • 非核心线程空闲存活时间为60秒
  • 工作机制

    • 当有一个新的任务提交到线程池
    • 任务直接加到SynchronousQueue队列(没有核心线程)。
    • 判断是否有空闲线程,若有,则取出空闲线程任务执行。若无,则新建线程
    • 执行完任务的线程可以存活60秒,在这期间如果接到任务,则继续存活,否则被销毁。
  • 使用场景

    newCachedThreadPool一般用于并发大,执行时间短的小任务。

  • 案例

    如下案例中线程执行休眠了秒钟,任务的提交速度会大于线程执行的速度

    public static void main(String[] args) {
    
            ExecutorService executor = Executors.newCachedThreadPool();
    
            for (int i = 0; i < 100; i++) {
                final int index = i;
                executor.execute(() -> {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(index);
                });
            }
        }
    
    

当提交任务的速度大于处理任务的速度时,每次提交一个任务,就会创建一个线程。这种情况下由于大量线程同时运行,很有会耗尽 CPU 和内存资源,造成系统OOM。由于空闲 60 秒的线程会被终止,长时间保持空闲的 CachedThreadPool 不会占用任何资源。

11.3 newSingleThreadExecutor

创建一个单线程化的线程池,即只创建唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行,源码如下:

创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
  • 特点

    • 核心线程数与最大线程数都为1
    • 阻塞队列是LinkedBlockingQueue
    • keepAliveTime为0

    可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活跃的

  • 工作机制

    • 当有一个新的任务提交到线程池
    • 判断线程池是否有一条线程在,若无,则新建线程执行任务,若有,则将任务加入阻塞队列。
    • 当前的唯一线程,从队列取任务,执行完一个再取一个,一个线程串行干所有的活。
  • 使用场景

    适用于串行执行任务的场景,一个任务一个任务地执行。

  • 案例

      public static void main(String[] args) {
    
            ExecutorService executor = Executors.newSingleThreadExecutor();
    
            for (int i = 0; i < 100; i++) {
                final int index = i;
                executor.execute(() -> {
                    System.out.println(Thread.currentThread().getName()+"正在执行输出数字:" +  index);
                });
            }
        }
    

    输出结果如下,从结果可知是同一个线程执行了100个任务

11.4 newScheduledThreadPool

创建一个定长的线程池,支持定时及周期性任务执行。源码如下

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}
  • 特点

    • 最大线程数为Integer.MAX_VALUE
    • 阻塞队列是DelayedWorkQueue
    • keepAliveTime为0
    • scheduleAtFixedRate() :按某种速率周期执行
    • scheduleWithFixedDelay():在某个延迟后执行
  • 工作机制

    • 当有一个新的任务提交到线程池
    • 线程池中的线程从 DelayQueue 中获取 time 大于等于当前时间的task
    • 执行完后修改这个 task 的 time 为下次被执行的时间
    • 将 task 放回DelayQueue队列中
  • 使用场景

    周期性执行任务的场景,

    需要限制线程数量的场景

  • 案例

    这个线程池的案例分为两种:按某种速率周期执行 和 在某个延迟后执行

    • 在某个延迟后执行

        public static void main(String[] args) {
      
              /**
               * 创建一个给定初始延迟的间隔性的任务,案例中是2秒
               * 后面的每次执行时间是上一次任务从执行到结束所需要的时间+给定的间隔时间(2秒)
               */
              ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
              for (int i = 0; i < 10; i++) {
                  executor.schedule(() -> {
                      System.out.println(Thread.currentThread().getName() + "正在执行,delay 2 seconds");
                  }, 2, TimeUnit.SECONDS);
              }
      
          }
      
    • 按某种速率周期执行

        public static void main(String[] args) {
      
              /**
               * 创建一个给定初始延迟的间隔性的任务,.
               * 后面每次任务执行时间为 初始延迟 + N * delay(间隔)
               */
              ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
              for (int i = 0; i < 10; i++) {
                  executor.scheduleAtFixedRate(() -> {
                      System.out.println("延迟 2 秒,每 4 秒执行一次");
                  }, 2, 4, TimeUnit.SECONDS);
      
              }
      
          }
      

11.5 newSingleThreadScheduledExecutor

通过newSingleThreadScheduledExecutor的源码可以知道,其本质上很newScheduledThreadPool一样,也是通过ScheduledThreadPoolExecutor创建线程池,并且核心线程数为1

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1));
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

  • 特点

    • 核心线程数为1
    • 最大线程数为Integer.MAX_VALUE
    • 阻塞队列是DelayedWorkQueue
    • keepAliveTime为0
  • 工作机制

    产生一个线程池大小为1的ScheduledExecutorService对象,当任务多于一个时将按先后顺序执行。

  • 案例

    由于其实现也是ScheduledThreadPoolExecutor对象创建,其案例实现跟newScheduledThreadPool一样的

11.6 newWorkStealingPool

newWorkStealingPool即任务窃取线程池:具有抢占式操作的线程池,每个线程都有一个任务队列存放任务

通过以下源码得知,newWorkStealingPool不是ThreadPoolExecutor的扩展,它是新的线程池类ForkJoinPool的扩展,使用ForkJoinPool的好处是,把1个任务拆分成多个【小任务】,把这些小任务分发到多个线程上执行。这些小任务都执行完成后,再将结果合并

    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

Runtime.getRuntime().availableProcessors()是获取当前系统可以的CPU核心数

ThreadPoolExecutor和ForkJoinPool都是在统一的一个Executors类中实现

  • 特点

    • 可以传入线程的数量,不传入,则默认使用当前计算机中可用的cpu数量

    • 合理的使用CPU进行对任务操作(并行操作),所以newWorkStealingPool适合使用在很耗时的操作。

  • 工作机制

    newWorkStealingPool默认会以当前机器的CPU处理器个数为线程个数,并行处理任务,且不保证顺序,同时并发数能作为参数设置,WorkStealingPool能够做到并行执行与设置的并发数相同的任务数量,多余的任务则会进入等待队列,等待被执行

  • 案例

     public static void main(String[] args) throws InterruptedException {
            // 线程数
            int threads = 10;
            // 用于计数线程是否执行完成
            CountDownLatch countDownLatch = new CountDownLatch(threads);
            
            ExecutorService executorService = Executors.newWorkStealingPool();
            executorService.execute(() -> {
                try {
                    System.out.println(Thread.currentThread().getName());
                } catch (Exception e) {
                    System.out.println(e);
                } finally {
                    countDownLatch.countDown();
                }
            });
            countDownLatch.await();
        }
    

十二、为什么不建议使用 Executors静态工厂构建线程池

Java通过Executors提供六种线程池,我们一般使用线程池也可以直接通过Executors对象new一个线程池,如下

但是在阿里巴巴Java开发手册,明确指出不允许使用Executors静态工厂构建线程池,这又是为什么呢

其主要原因是:【规避资源耗尽,清晰线程池的运行规则】。

  • Executors返回的线程池对象的弊端

    • FixedThreadPool 和 SingleThreadPool

      这两个线程池允许的请求队列(底层实现是LinkedBlockingQueue)长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM

    • CachedThreadPool 和 ScheduledThreadPool

      这两个线程池允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

  • 创建线程池的正确方式

    避免使用Executors创建线程池,主要是避免使用其中的默认实现,我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。在创建的同时,给BlockQueue指定容量,设置最大线程数

    private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
            60L, TimeUnit.SECONDS,
            new ArrayBlockingQueue(10));
    

    创建线程池也可以使用开源类库:开源类库,如apache和guava等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/39718.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

let const var区别

文章目录写在前面1.var关键字1.1 没有块级作用域的概念&#xff0c;有全局作用域、函数作用域的概念1.2 存在变量提升1.3 全局作用域用var声明的变量会挂载到window对象上1.4 同一作用域中允许重复声明1.5 不初始化值默认为undefined2.let关键字2.1 有块级作用域的概念2.2 不存…

HTB靶机:RainyDay

目录介绍主机信息探测网站探测子域名爆破(BurpSuite)目录爆破爆破参数值分析 & 破解hash登录系统反弹shell端口转发内网穿透【很坑】配置socks代理内网扫描换用windows做内网渗透子域名信息收集爆破密钥位置爆破密钥内容JWT攻击进程监控Flag1 & 获取SSH私钥提权python沙…

UE4 通过按键切换不同的HUD

咱们在玩游戏的时候&#xff0c;通常会和界面进行各种各样的互动&#xff0c;而且会动都是在不同的界面上&#xff0c;所以需要在不同的界面上进行切换或者多个HUD重叠显示在一起。 首先创建两个HUD 2.重新定义一个PlayerController类 // Fill out your copyright notice…

被裁后半月面试8家公司无果,凭借这份Java面试指南成功入职阿里

前言 上个月班上的好好的突然被通知"毕业了"&#xff0c;现在工作也确实不好找。之前近一个月面了很多大大小小的公司降薪太严重都没考虑去&#xff0c;最后没办法本来都打算随便去一家了却偶然得到一个阿里的面试机会&#xff0c;足足面了七面&#xff08;我太难了…

前有刘德华,后有腾格尔和光头李进,明星为何都热衷于线上演唱会

2022年11月19日&#xff0c;有着“草原歌神”之称的腾格尔&#xff0c;开启了自己的线上演唱会&#xff0c;并且取得了圆满的成功。其实在此之前&#xff0c;天王刘德华已经在某音平台&#xff0c;两次开启自己的线上演唱会&#xff0c;都受到了非常好的效果。 为什么音乐领域的…

手把手教你定位线上MySQL锁超时问题,包教包会

昨晚我正在床上睡得着着的&#xff0c;突然来了一条短信。 ​什么&#xff1f;线上的订单无法取消&#xff01; 我赶紧登录线上系统&#xff0c;查看业务日志。 ​发现有MySQL锁超时的错误日志。 不用想&#xff0c;肯定有另一个事务正在修改这条订单&#xff0c;持有这条订单的…

【边缘注意:深度多尺度特征】

Learning a Deep Multi-Scale Feature Ensemble and an Edge-Attention Guidance for Image Fusion &#xff08;学习深度多尺度特征集成和图像融合的边缘注意指南&#xff09; 在本文中&#xff0c;我们提出了一种用于红外和可见光图像融合的深度网络&#xff0c;该网络将具…

带你了解什么是Java虚拟机运行时数据区

一、前言 程序都是运行在内存里的&#xff0c;所以对于一门开发语言来说&#xff0c;对于内存的管理都是重中之重的&#xff0c;前有C、C需要开发者管理内存&#xff0c;后有Java的自动内存管理&#xff0c;到如今的内存安全的Rust。 二、运行时数据区概览 Java虚拟机在运行…

PyQt5可视化编程-菜单和工具栏

一、简介 PyQt5 是Digia的一套Qt5与python绑定的应用框架&#xff0c;同时支持2.x和3.x。本教程使用的是3.x。Qt库由Riverbank Computing开发&#xff0c; 是最强大的GUI库之一 &#xff0c;官方网站&#xff1a;www.riverbankcomputing.co.uk/news。 PyQt5是由一系列Python模块…

Allegro上如何让飞线以方框形式显示

Allegro上如何让飞线以方框形式显示 Allegro可以让飞线以方框形式显示,让走线评估更简单,尤其是电源和地,如下图 选择Edit-Property Find选择Nets 选择需要改成方框显示的网络,左边选择Voltage,Value里面输入任意一个数字,比如0或者1,apply 可以看到网络已经显示成…

八.调试的技巧

目录 一.调试 1.何为调试&#xff1f; 2.调试的基本步骤 二.debug和release的介绍 三.Windows环境调试介绍 1.调试环境准备 2.学会快捷键 &#xff08;1&#xff09;F5 &#xff08;2&#xff09;F9 &#xff08;3&#xff09;F10 &#xff08;4&#xff09;F11 &am…

【Java学习】JavaWeb ---- JDBC

文章目录JDBC 快速入门ResultSet数据连接池JDBC 快速入门 下载jar包&#xff08;百度&#xff09;->add as library 代码 package com.ith.jdbc;import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement;public class demo1 {public stati…

HTTPS一定安全吗

https是一种通过计算机网络进行安全通信的传输协议&#xff0c;主要目的是提供对网站服务器的身份认证&#xff0c;保护交换数据的隐私与完整性&#xff0c;但不能说使用htttps就一定绝对的安全。 有一点需要了解的是&#xff0c;使用HTTPS 在内容传输的加密上使用的是对称加密…

使用dreamweaver制作采用DIV+CSS进行布局——美食甜品店铺加盟企业HTML静态网页 ——学生美食网页设计作品静态HTML网页模板源码

&#x1f468;‍&#x1f393;静态网站的编写主要是用HTML DIVCSS JS等来完成页面的排版设计&#x1f469;‍&#x1f393;,常用的网页设计软件有Dreamweaver、EditPlus、HBuilderX、VScode 、Webstorm、Animate等等&#xff0c;用的最多的还是DW&#xff0c;当然不同软件写出的…

C++标准库分析总结(九)——<适配器>

目录 1 适配器简介 2 适配器使用分类 2.1 容器适配器 2.2 函数适配器 2.2.1 常见的函数适配器 2.2.2 bind2nd 2.2.3 not1 2.2.4 bind用法 2.3 迭代器适配器 2.4 X适配器 1.6.1 ostream_iterator 1.6.2 istream_iterator 1 适配器简介 把一个原本存在的东西&#xf…

递归和排序算法的应用

一、递归常见问题和注意事项 1. 堆栈溢出&#xff1b; 2. 警惕重复运算&#xff1a; 可以使用一个数据结构&#xff08;散列表&#xff09;将已经计算过的f(k)保存起来&#xff0c;每当调用到f(k)时&#xff0c;先产看下是否已经求结果&#xff0c;从而避免重复计算。 3. 将递…

【嵌入式Linux】5.Ubuntu 交叉编译工具链安装

前言 交叉编译器中“交叉”的意思就是在一个架构上编译另外一个架构的代码,相当于两种架构“交叉”起来了。 其基本特点是: 1、它肯定是一个 GCC 编译器。 2、这个 GCC 编译器是运行在 X86 架构的 PC 上的。 3、这个 GCC 编译器是编译 ARM 架构代码的,也就是编译出来的可…

MongoDB 的安装、启动与连接

MongoDB 的安装、启动与连接一、MongoDB Community Server1. 安装(1) 下载(2) 安装1) 解压2) 配置环境变量3) 配置数据库存放目录a. 创建b. 赋权2. 启动验证二、MongoDB Compass安装三、连接一、MongoDB Community Server 1. 安装 (1) 下载 官网下载安装 (2) 安装 1) 解压…

three.js之访问几何体数据与几何体的旋转平移

文章目录访问几何体数据简介例子BoxGeometryPlaneBufferGeometry旋转、缩放、平移变换注意专栏目录请点击 访问几何体数据 简介 一般我们在开发项目的时候&#xff0c;可能会加载外部模型&#xff0c;这个时候&#xff0c;我们就需要获取到几何体的顶点数据这个时候&#xff…

Kubernetes入门

文章目录1、K8s快速入门1&#xff09;简介2&#xff09;架构&#xff08;1&#xff09;整体主从方式&#xff08;2&#xff09;master节点架构&#xff08;3&#xff09;Node节点架构3&#xff09;概念4&#xff09;快速体验&#xff08;1&#xff09;安装minikube&#xff08;…