新乡seo外包,情感网站seo,广告设计实习内容,网站建设需要哪些网络技术这一篇#xff0c;我们来做一件真正“工程化”的事#xff1a;封装一套统一的“生产级线程池”#xff0c;带线程命名、异常捕获、拒绝策略日志、基础监控与超时控制。文章会以实际代码为主#xff0c;你可以直接拷贝到项目中进一步改造。一、目标#xff1a;为什么要封装…这一篇我们来做一件真正“工程化”的事封装一套统一的“生产级线程池”带线程命名、异常捕获、拒绝策略日志、基础监控与超时控制。文章会以实际代码为主你可以直接拷贝到项目中进一步改造。一、目标为什么要封装线程池先把痛点列清楚禁止直接用 Executors 默认工厂newFixedThreadPool / newCachedThreadPool / newSingleThreadExecutor 都有隐藏坑无界队列、线程无限增长等。线程池要统一管理不要满项目到处散落 new ThreadPoolExecutor定位问题非常难。线程要有“读得懂的名字”日志里看到的是pool-1-thread-3完全不直观。任务异常要统一捕获 打日志默认行为线程执行 Runnable 的异常如果没捕获会直接丢掉。拒绝策略必须有日志/报警默默丢任务或只抛异常很难查。优雅停机 监控服务停止时线程池要正常 shutdown。至少能看到当前线程数、队列长度、拒绝次数等。所以我们需要一个ThreadPoolManager / ThreadPoolFactory来统一创建 管理线程池。二、自定义 ThreadFactory线程命名 异常兜底第一步让每个线程池的线程名字有语义。比如biz-io-1,biz-cpu-2,sched-worker-1。定义一个简单的 ThreadFactoryimport java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; public class NamedThreadFactory implements ThreadFactory { private final AtomicInteger idx new AtomicInteger(1); private final String namePrefix; private final boolean daemon; public NamedThreadFactory(String namePrefix) { this(namePrefix, false); } public NamedThreadFactory(String namePrefix, boolean daemon) { this.namePrefix namePrefix; this.daemon daemon; } Override public Thread newThread(Runnable r) { Thread t new Thread(r, namePrefix - idx.getAndIncrement()); t.setDaemon(daemon); // 兜底异常处理防止异常直接把线程干掉而没人知道 t.setUncaughtExceptionHandler((thread, ex) - { System.err.println([UNCAUGHT] Thread thread.getName()); ex.printStackTrace(); // 实际项目中换成日志/报警 }); return t; } }这样new NamedThreadFactory(biz-io)日志里看到的就是类似biz-io-1、biz-io-2定位问题非常直观。三、自定义 RejectedExecutionHandler拒绝时打日志 可选回压第二步统一处理拒绝策略。生产一般不直接用 JDK 默认的 AbortPolicy而是记录日志 / 打点再选择具体策略比如 CallerRunsPolicy我们可以包装一下import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; public class LoggedCallerRunsPolicy implements RejectedExecutionHandler { private final String poolName; public LoggedCallerRunsPolicy(String poolName) { this.poolName poolName; } Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { // 这里可以打日志 / 上报监控 System.err.println([REJECTED] pool poolName , active e.getActiveCount() , poolSize e.getPoolSize() , queueSize e.getQueue().size()); // 回退到调用线程执行形成反压 if (!e.isShutdown()) { r.run(); } } }这个策略的好处池子爆了 → 有日志可查 有回压调用线程被拖慢 → 上游就自然降速防止雪崩。四、封装 ThreadPoolManager统一出口创建线程池我们可以做一个“线程池管理类”按业务分类暴露几个常用线程池CPU 密集型IO 密集型定时调度线程池示例简单版单例import java.util.concurrent.*; public class ThreadPoolManager { private static final int CPU Runtime.getRuntime().availableProcessors(); // CPU 密集任务线程池 private static final ThreadPoolExecutor CPU_POOL new ThreadPoolExecutor( CPU 1, CPU 1, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(1000), new NamedThreadFactory(biz-cpu), new LoggedCallerRunsPolicy(biz-cpu) ); // IO 密集任务线程池 private static final ThreadPoolExecutor IO_POOL new ThreadPoolExecutor( CPU * 2, CPU * 4, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(2000), new NamedThreadFactory(biz-io), new LoggedCallerRunsPolicy(biz-io) ); // 定时任务线程池 private static final ScheduledThreadPoolExecutor SCHEDULED_POOL new ScheduledThreadPoolExecutor( CPU, new NamedThreadFactory(sched-worker), new LoggedCallerRunsPolicy(sched) ); static { // 设置 Scheduled 线程池的策略定时任务异常不影响后续调度 SCHEDULED_POOL.setRemoveOnCancelPolicy(true); } private ThreadPoolManager() {} public static ExecutorService cpuPool() { return CPU_POOL; } public static ExecutorService ioPool() { return IO_POOL; } public static ScheduledExecutorService scheduledPool() { return SCHEDULED_POOL; } // 优雅停机可以在 Spring 的 Shutdown Hook 或 main 的 finally 中调用 public static void shutdownAll() { shutdownPool(biz-cpu, CPU_POOL); shutdownPool(biz-io, IO_POOL); shutdownPool(sched, SCHEDULED_POOL); } private static void shutdownPool(String name, ExecutorService pool) { System.out.println([SHUTDOWN] name); pool.shutdown(); try { if (!pool.awaitTermination(10, TimeUnit.SECONDS)) { System.out.println([SHUTDOWN-NOW] name); pool.shutdownNow(); } } catch (InterruptedException e) { pool.shutdownNow(); Thread.currentThread().interrupt(); } } }之后项目中统一这样用ThreadPoolManager.ioPool().submit(() - { // IO 任务 }); ThreadPoolManager.cpuPool().submit(() - { // 计算任务 }); ThreadPoolManager.scheduledPool().scheduleAtFixedRate(() - { // 定时任务 }, 0, 1, TimeUnit.MINUTES);这样全项目的线程池都走同一套工厂有统一命名有统一拒绝策略日志shutdown 时可以统一关闭五、封装任务统一异常捕获 打日志 Trace可选默认ThreadPoolExecutor对 Runnable 的异常处理方式是如果run()抛异常而你没 try/catch异常会从线程栈往上冒到线程最终打印一次 uncaught exception或被吞掉不会再抛回 submit/execute 的调用方。为了避免任务里有人忘记 try/catch我们可以封一层public class SafeRunnable implements Runnable { private final Runnable delegate; private final String name; public SafeRunnable(Runnable delegate, String name) { this.delegate delegate; this.name name; } Override public void run() { try { delegate.run(); } catch (Throwable e) { System.err.println([TASK-EXCEPTION] task name , thread Thread.currentThread().getName()); e.printStackTrace(); // 这里可以对接日志系统 / 监控告警 } } public static Runnable wrap(Runnable r, String name) { return new SafeRunnable(r, name); } }使用方式ThreadPoolManager.ioPool().submit( SafeRunnable.wrap(() - { // 业务代码异常不用担心漏日志 int x 1 / 0; }, demo-io-task) );六、加入超时控制Future 超时 降级对于某些关键任务如下游接口调用某个批量处理我们不希望任务无限执行可以加入 Future 超时控制ExecutorService io ThreadPoolManager.ioPool(); FutureString future io.submit(() - { // 模拟调用下游耗时不确定 TimeUnit.SECONDS.sleep(5); return OK; }); try { String result future.get(2, TimeUnit.SECONDS); // 最多等 2 秒 System.out.println(result result); } catch (TimeoutException e) { System.err.println([TIMEOUT] 调用超时进行降级处理); future.cancel(true); // 尝试中断任务 } catch (Exception e) { System.err.println([ERROR] 调用异常); e.printStackTrace(); }这就是最基础的“线程池级超时 降级”。七、简单监控在没有 Prometheus 之前先打印指标可以先提供一个简单的方法用来定时打印线程池的状态后面再接监控系统public static void logState(String name, ThreadPoolExecutor pool) { System.out.println(String.format( [POOL] %s | poolSize%d, active%d, queue%d, completed%d, name, pool.getPoolSize(), pool.getActiveCount(), pool.getQueue().size(), pool.getCompletedTaskCount() )); }然后可以用ScheduledExecutorService定时调用ThreadPoolManager.scheduledPool().scheduleAtFixedRate(() - { ThreadPoolManager.logState(biz-cpu, (ThreadPoolExecutor) ThreadPoolManager.cpuPool()); ThreadPoolManager.logState(biz-io, (ThreadPoolExecutor) ThreadPoolManager.ioPool()); }, 0, 30, TimeUnit.SECONDS);等后期接入MicrometerPrometheus / Grafana自己的监控平台都可以复用这些指标。八、总结生产级线程池封装的关键点总结不要直接用 Executors 默认线程池自己用 ThreadPoolExecutor 有界队列。统一线程池出口ThreadPoolManager避免到处散落 new。用NamedThreadFactory给线程起有意义的名字排查问题一眼就能看出哪个池出的事。用自定义RejectedExecutionHandler如 LoggedCallerRunsPolicy对拒绝任务打日志 回压。用SafeRunnable包装任务统一捕获异常防止任务异常悄悄丢失。对重要任务用Future 超时控制 降级防止线程长期占用。提供定时日志 / 监控方法查看线程池的队列长度、活跃线程数等指标。在应用优雅停机时统一调用shutdownAll()避免线程池悬挂。做到这些你从“会用线程池”升级为“能在生产环境放心地用线程池”。