万普插件库

jQuery插件大全与特效教程

java线程池《打工人的福报调度中心》

我们在实际项目开发中,线程池的正确使用直接影响系统稳定性和吞吐量。以下是结合代码对比的典型场景分析:

一、FixedThreadPool场景对比

错误写法:未控制任务提交速率导致内存溢出

// 错误示例:无界队列堆积 
ExecutorService pool = Executors.newFixedThreadPool(4); while(true) {
  // 高并发场景下队列无限增长
  pool.submit(() -> processHttpRequest());
}

问题定位:LinkedBlockingQueue默认无界,请求突增时导致OOM

正确实现:自定义有界队列+拒绝策略

// 正确示例:防御性编程
BlockingQueue queue = new ArrayBlockingQueue<>(100);
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    4, 8, 30, TimeUnit.SECONDS,
    queue,
    new ThreadPoolExecutor.AbortPolicy() // 队列满时抛异常
);

for(int i=0; i<1000; i++) {
    try {
        pool.submit(task);
    } catch (RejectedExecutionException e) {
        log.warn("请求被拒绝,当前队列深度:{}", queue.size());
        // 触发降级策略
    }
}


二、CachedThreadPool场景对比

错误写法:未限制任务数量导致线程爆炸

// 错误示例:大规模并行处理
List files = getAllFiles(); // 假设5000个文件
ExecutorService pool = Executors.newCachedThreadPool();
files.forEach(f -> 
    pool.execute(() -> parseLargeFile(f)) // 每个文件开一个线程
);

问题定位:默认最大线程数Integer.MAX_VALUE,导致系统资源耗尽

正确实现:限制并发数+超时控制

// 正确示例:使用信号量限流
Semaphore semaphore = new Semaphore(50); // 最大并行50个文件
ExecutorService pool = Executors.newFixedThreadPool(50);

files.forEach(f -> pool.execute(() -> {
    try {
        semaphore.acquire();
        parseLargeFile(f);
    } finally {
        semaphore.release();
    }
}));


三、ScheduledThreadPool场景对比

错误写法:忽略异常导致定时中断

// 错误示例:未捕获异常
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
    syncThirdPartyAPI(); // 第三方服务可能超时
}, 0, 1, TimeUnit.HOURS);

问题定位:任务抛异常后后续调度自动终止

正确实现:包裹异常处理层

// 正确示例:添加异常防护
scheduler.scheduleAtFixedRate(() -> {
    try {
        syncThirdPartyAPI();
    } catch (Exception e) {
        log.error("定时任务执行异常", e);
        // 发送告警通知
        monitor.alert("SYNC_TASK_FAILED");
    }
}, 0, 1, TimeUnit.HOURS);

四、SingleThreadExecutor场景对比

错误写法:阻塞调用导致死锁

// 错误示例:任务相互依赖
ExecutorService singlePool = Executors.newSingleThreadExecutor();
Future step1 = singlePool.submit(() -> "InitData");
Future step2 = singlePool.submit(() -> {
    return step1.get() + "_Process"; // 在同一个线程等待结果
});

问题定位:单线程内任务互相等待形成死锁

正确实现:分离执行上下文

// 正确示例:拆分线程池
ExecutorService ioPool = Executors.newCachedThreadPool();
ExecutorService computePool = Executors.newSingleThreadExecutor();

Future step1 = ioPool.submit(() -> loadFromDatabase());
computePool.execute(() -> {
    try {
        String data = step1.get();
        processData(data);
    } catch (Exception e) {
        // 异常处理
    }
});


五、关键配置原则(结合生产经验)

  1. 队列选择公式
  2. CPU密集型:队列容量 = 核心线程数 * 2
  3. IO密集型:队列容量 = 核心线程数 / (1 - 目标CPU使用率)
  4. 拒绝策略对照表
  5. 实时系统:AbortPolicy(抛异常快速失败)
  6. 允许延迟:CallerRunsPolicy(降级到调用线程执行)
  7. 日志采集:DiscardOldestPolicy(丢弃旧任务)
  8. 监控埋点示例
ThreadPoolExecutor pool = (ThreadPoolExecutor) executor;
Metrics.gauge("thread.active", pool.getActiveCount());
Metrics.gauge("queue.size", pool.getQueue().size());
// 设置报警阈值
if(pool.getQueue().size() > 1000) {
    AlarmCenter.notify("线程池拥堵警告");
}

血泪教训:曾在订单系统中因未设置队列上限,导致促销期间JVM堆内存撑满(年轻代GC频繁),最终通过调整为有界队列+动态扩容策略解决。建议在流量预估基础上,通过压测确定合理参数。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言