什么是线程池? 📅 2026/7/1 1:43:02 说说工作中线程池的应用可以讲在拼团中怎么用的。3、说一下线程池的工作流程线程池刚创建时里面没有一个线程。任务队列是作为参数传进来的因为可以选择。当调用excute()方法添加一个任务时线程池会做如下判断若正在运行的线程数量小于corePoolSize则马上创建线程运行这个任务。若正在运行的线程数量大于或等于corePoolSize则将这个任务放入队列。若这时队列满了而且正在运行的线程数量小于maximumPoolSize那么还是要创建非核心线程立刻执行这个任务。若队列满了且正在运行的线程数量大于或等于maximumPoolSize那么线程池会根据拒绝策略来应对处理。当一个线程完成任务时它会从队列中取下一个任务来执行。当一个线程无事可做超过一定的时间keepAliveTime时线程池会判断若当前正在运行的线程数大于corePoolSize那么这个线程会被停掉。所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。为什么不直接填满到maximyumPoolSize内存占用大、上下文切换频繁消耗CPU、同步开销大。动态扩展的方案只在高峰期临时创建更多线程是一种“按需分配”的原则先用少量核心线程处理常规负载队列缓冲短期突发请求实在处理不过来才会额外创建线程空闲时回收多余线程。是为了在响应速度和资源效率之间取得最佳平衡。4、线程池主要参数有哪些corePoolSize初始化线程池中核心线程数当线程池中线程数小于corePoolSize时系统默认是添加一个任务才从创建一个线程。maximumPoolSize表示允许的最大线程数 非核心线程数 核心线程数。当工作队列也满了但线程池中总线程数maximumPoolSize时就会再次创建新的线程。keepAliveTime非核心线程闲置下来不干活最多存活时间。unit非核心线程保持存活的时间的单位。TimeUnit.DAYS; 天TimeUnit.HOURS; ⼩时TimeUnit.MINUTES; 分钟TimeUnit.SECONDS; 秒TimeUnit.MILLISECONDS; 毫秒TimeUnit.MICROSECONDS; 微秒TimeUnit.NANOSECONDS; 纳秒workQueue线程池等待队列ArrayBlockingQueue有界队列是一个用数组实现的有界阻塞队列按FIFO排序量。LinkedBlockingQueue基于链表结构的阻塞队列按FIFO排序任务容量可以选择进行设置不设置的话就是一个无边界的阻塞队列最大长度为Integer.MAX_VALUE吞吐量通常要高于ArrayBlockingQueuenewFixedThreadPool线程池用了这个队列。DelayQueue延迟队列是一个任务定时周期的执行的队列。单纯的DealyDueue不保证相同延迟任务的FIFO但ScheduledThreadPoolExecutor通过添加序列号的方式确保了FIFO顺序。任务1还有2秒执行任务2还有3秒执行那么任务1排在任务2前面getDelay()返回距离触发还有多久当getDelay() 0时任务才可被取出队列按触发时间排序最早该执行的任务在队首priorityBlockingQueue是具有优先级的无界阻塞队列。SynchronousQueue是一个不存储元素的阻塞队列每个插入操作必须等另一个线程调用移除操作否则插入操作一直处于阻塞吞吐量通常高于LinkedBlockingQuene因为是生产者直接交给消费者无缓冲适合突发性、短时任务newCachedThreadPool线程池使⽤了这个队列。threadFactory创建一个新线程时用的工厂可以用来设定线程名、是否为daemon线程等。1234567891011121314151617181920212223242526// 1. 默认工厂抽象但可用ThreadFactory defaultFactory Executors.defaultThreadFactory();// 2. 自定义工厂 - 可以完全控制线程创建过程ThreadFactory customFactory newThreadFactory() {privatefinalAtomicInteger threadNumber newAtomicInteger(1);OverridepublicThread newThread(Runnable r) {Thread t newThread(r);t.setName(my-pool-worker- threadNumber.getAndIncrement());t.setDaemon(false);// 设置为非守护线程t.setPriority(Thread.NORM_PRIORITY);// 设置优先级returnt;}};// 3. 在线程池中使用ThreadPoolExecutor pool newThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,60L, TimeUnit.SECONDS,newLinkedBlockingQueueRunnable(),customFactory,// 这里传入自定义工厂newThreadPoolExecutor.CallerRunsPolicy());handler拒绝策略。AbortPolicy直接抛出异常默认使用此策略CallerFunsPolicy用调用者所在的线程执行任务DIscartOldestPolicy丢弃阻塞队列里最老的任务也就是队列里最靠前的任务DiscardPolicy当前任务直接丢弃自定义实现RejectExecutionHandler接口即可。5、线程池提交excute和submit有什么区别(1) execute()用法void execute(Runnable command)特点只能提交Runnable 任务没有返回值如果任务抛出异常异常会直接抛到线程中最终由线程的UncaughtExceptionHandler或afterExecute捕获(1) execute() 用法void execute(Runnable command) 特点 只能提交 Runnable 任务 没有返回值 如果任务抛出异常异常会直接抛到线程中最终由线程的 UncaughtExceptionHandler 或 afterExecute 捕获。(2) submit()用法Future? submit(Runnable task)T FutureT submit(CallableT task)特点可以提交Runnable 或 Callable任务返回Future对象可以获取结果、取消任务、捕获异常如果任务抛出异常异常会被封装在Future中只有调用get()时才会抛出ExecutionException。(2) submit() 用法 Future? submit(Runnable task) T FutureT submit(CallableT task) 特点 可以提交 Runnable 或 Callable 任务 返回 Future 对象可以获取结果、取消任务、捕获异常 如果任务抛出异常异常会被封装在 Future 中只有调用 get() 时才会抛出 ExecutionException。6、线程池怎么关闭(1)shutdown()行为停止接收新任务已提交的任务正在执行的 队列中等待的会执行完。特点平滑关闭常用方式。注意调用后线程池状态会变为SHUTDOWN不会立刻终止。(2)shutdownNow()行为停止接收新任务清空队列中的等待任务并尝试中断正在执行的任务。返回值未执行的任务列表。特点强制关闭风险较大可能造成数据不一致。注意线程是否真正中断取决于任务代码是否响应中断。(3)awaitTermination(timeout, unit)用法pool.shutdown(); if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { pool.shutdownNow(); }作用在调用shutdown()后等待一段时间如果线程池没有在指定时间内终止再调用shutdownNow()强制关闭。特点常用于“优雅关闭”“兜底强制关闭”组合。(4)isShutdown() / isTerminated()isShutdown()调用shutdown()或shutdownNow()后返回true。isTerminated()所有任务执行结束线程池完全终止时返回true。可配合轮询检测线程池状态做收尾逻辑。7、线程池的线程数应该怎么配置(1)计算密集型任务特点大部分时间消耗在 CPU 运算上。配置CPU核数 1。理由核心数保证最大并行度额外的1用来应对偶尔的线程挂起页缺失、GC、上下文切换。(2)IO 密集型任务特点大部分时间等待 IO数据库、网络、磁盘等。配置CPU核数 * 2甚至更高取决于 IO 等待比例。理由IO 期间线程空闲更多线程可提升 CPU 利用率。(3)混合型任务特点既包含计算又包含 IO。配置方法比例分解法根据任务中 IO 和计算占比估算最优线程数。分池法将任务拆分为计算型和 IO 型分别放入不同线程池避免资源竞争。(4)经验公式IO 密集型可参考线程数CPU核心数×(1IO时间计算时间)线程数 CPU核心数 \times (1 \frac{IO时间}{计算时间})线程数CPU核心数×(1计算时间IO时间)如果 IO 占比高线程数可比 CPU 数大得多。8、有哪几种常见的线程池(1)newFixedThreadPool核心线程数 最大线程数 固定值。keepAliveTime 0线程不会被回收。阻塞队列 LinkedBlockingQueue无界队列。特点线程数固定任务多了就排队。适用场景适合CPU 密集型任务线程数可设置为CPU 核心数或略大。(2)newCachedThreadPool核心线程数 0最大线程数 Integer.MAX_VALUE相当于无限大。keepAliveTime 60s空闲线程超过时间会回收。阻塞队列 SynchronousQueue没有容量任务直接交给线程执行。特点任务多就创建新线程空闲一段时间自动回收。适用场景适合大量并发的短期小任务但要注意可能导致线程数过多风险较大。(3)newSingleThreadExecutor核心线程数 最大线程数 1。keepAliveTime 0。阻塞队列 LinkedBlockingQueue无界队列。特点单线程串行执行保证任务按照提交顺序FIFO执行。适用场景适合需要顺序执行任务、或全局只需要一个后台线程的场景。(4)newScheduledThreadPool核心线程数 指定值最大线程数 Integer.MAX_VALUE。keepAliveTime 0。阻塞队列 DelayQueue。特点支持定时和周期性任务调度。适用场景适合定时执行任务如心跳检测、周期任务。(5)newWorkStealingPoolJDK 1.8 新增使用ForkJoinPool实现。核心线程数 CPU 核心数Runtime.getRuntime().availableProcessors()。特点基于“工作窃取算法”空闲线程可以从其他线程队列里偷任务执行负载均衡。适用场景适合大批量、小任务并行计算如分治计算。9、线程池异常怎么处理在使用线程池时经常会遇到这样的情况线程池本身没问题但任务Runnable/Callable运行过程中抛出了异常。如果没有额外处理这些异常可能会被“吞掉”导致我们难以及时发现问题。本文总结了常见的几种线程池任务异常处理方式。(1) 在任务内部 try-catchpool.execute(() - { try { int a 1 / 0; } catch (Exception e) { System.err.println(任务异常: e.getMessage()); } });优点简单直观能捕获异常。缺点需要每个任务都写try/catch容易遗漏不够统一。(2)submit()Future.get()Future? future pool.submit(() - { int a 1 / 0; }); try { future.get(); // 会抛 ExecutionException } catch (Exception e) { System.err.println(捕获到异常: e.getCause()); }特点使用submit()提交任务会返回Future如果调用get()会抛出ExecutionException异常可以被捕获但如果不调用get()异常就会被“吞掉”。适合需要拿任务返回值的场景。(3) 设置线程的UncaughtExceptionHandlerThreadFactory factory r - { Thread t new Thread(r); t.setUncaughtExceptionHandler((thread, e) - System.err.println(thread.getName() 出错了: e)); return t; }; ExecutorService pool new ThreadPoolExecutor( 2, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(), factory ); pool.execute(() - { int a 1 / 0; }); //输出示例 pool-1-thread-1 出错了: java.lang.ArithmeticException: / by zero