Java线程与线程池:从入门到“避坑”全攻略

Java线程与线程池:从入门到"避坑"全攻略


一、线程与线程池:基础篇

1. 线程是什么?

线程是程序执行的最小单位,好比快递公司的快递员。一个进程(比如一个Java程序)可以有多个线程,共享进程的堆和方法区,但每个线程有自己的程序计数器、虚拟机栈等"私人行李"。
为什么需要线程?

  • 效率高:线程切换成本低,多核CPU时代可并行处理任务。
  • 资源利用率:比如单核CPU下,一个线程等IO时,其他线程能抢CPU干活。

2. 线程池:快递公司的管理智慧

频繁创建销毁线程就像每天招快递员再开除,太浪费!线程池就是"快递公司",核心线程是正式工,临时线程是兼职,队列是排期表,拒绝策略是"拒单"。
线程池的优势

  • 资源复用:线程用完不销毁,下次直接调用。
  • 可控性:限制线程数量,避免系统被"快递员挤爆"。

二、线程池用法:从"Hello World"到实战

1. 手动创建线程池(拒绝Executors!)

阿里规范警告:Executors快捷方法会埋雷!比如FixedThreadPool用无界队列,任务堆积可能引发OOM。
正确姿势

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    10, // 核心线程数(正式工)
    20, // 最大线程数(正式工+临时工)
    60, TimeUnit.SECONDS, // 临时工摸鱼时间
    new LinkedBlockingQueue<>(100), // 有界队列(排期表容量)
    new ThreadFactoryBuilder().setNameFormat("订单处理-%d").build(), // 给线程起名,方便查户口
    new ThreadPoolExecutor.CallerRunsPolicy() // 队列满时,让老板亲自送货
);

2. 提交任务的三种姿势

  • Runnable:只管干活,不问结果(适合发短信通知)。
  • Callable:干完活还能带个返回值(适合查库存)。
  • CompletableFuture:异步编程神器,支持链式调用(适合组合多个任务)。

三、原理揭秘:线程池的"五脏六腑"

1. 线程池工作流程

  1. 任务来了,先找核心线程(正式工)。
  2. 正式工满了?任务进队列(排期表)。
  3. 队列满了?招临时工(直到最大线程数)。
  4. 连临时工都招满了?拒绝策略出场(比如"老板亲自送货")。

2. Worker线程:打工人的一生

每个Worker是一个"无限循环"的线程,核心逻辑是:

java 复制代码
while (task != null || (task = getTask()) != null) {
    try {
        task.run(); // 干活!
    } finally {
        task = null; // 干完活等下一个任务
    }
}
  • 摸鱼检测 :临时工如果keepAliveTime内没活干,会被开除。

四、避坑指南:别让线程池变"雷池"

1. OOM三大惨案

  • 无界队列LinkedBlockingQueue默认容量是Integer.MAX_VALUE,任务堆积直接内存爆炸。
  • 线程数失控CachedThreadPool允许创建Integer.MAX_VALUE个线程,系统直接卡死。

2. 死锁的"父子相残"

案例:父任务占满线程池,子任务在队列等待,互相等对方释放资源------解决方法是用两个线程池,父子任务分开处理。

3. 参数配置玄学

  • CPU密集型:核心线程数 = CPU核数 + 1(避免上下文切换)。
  • IO密集型 :核心线程数 = CPU核数 * 2(比如处理网络请求)。
    公式:最佳线程数 = CPU核数 * (1 + 平均等待时间 / 平均计算时间)。

五、最佳实践:美团大佬的"骚操作"

1. 线程池命名

默认线程名像"路人甲",用ThreadFactoryBuilder起名,比如"订单处理-1",日志排查时一目了然。

2. 监控线程池状态

定时打印线程数、队列大小等指标,早发现早治疗:

java 复制代码
scheduledExecutorService.scheduleAtFixedRate(() -> {
    log.info("活跃线程: {}", executor.getActiveCount());
    log.info("队列任务: {}", executor.getQueue().size());
}, 0, 1, TimeUnit.SECONDS);

六、面试考点:征服面试官的"灵魂拷问"

1. 线程状态图

  • NEW :刚出生,还没start()
  • RUNNABLE:可运行(可能在等CPU)。
  • BLOCKED :等锁(比如synchronized)。
  • WAITING :等唤醒(wait()join())。
  • TIMED_WAITING :睡一会(sleep(1000))。
  • TERMINATED:领盒饭了。

2. sleep vs wait

  • sleep:抱着锁睡觉(不释放锁),到点自动醒。
  • wait :释放锁,等别人notify()

3. 死锁四条件

  • 互斥、占有且等待、不可剥夺、循环等待。
    破解方法:按顺序申请锁(比如统一先申请锁A,再申请锁B)。

七、总结:线程池的"生存法则"

线程池是并发编程的瑞士军刀,但用不好会变成"自爆按钮"。记住三条黄金法则:

  1. 手动创建 :拒绝Executors,拥抱ThreadPoolExecutor
  2. 监控命名:线程池要有"身份证",运行状态要透明。
  3. 业务隔离:不同业务用不同池,避免"一损俱损"。

最后,线程池不是银弹,合理配置才能让系统"稳如老狗"。愿你的代码永无OOM,线程永不死锁!


参考资料

相关推荐
虾条_花吹雪6 分钟前
Chat Model API
java
双力臂40412 分钟前
MyBatis动态SQL进阶:复杂查询与性能优化实战
java·sql·性能优化·mybatis
六毛的毛43 分钟前
Springboot开发常见注解一览
java·spring boot·后端
程序漫游人1 小时前
centos8.5安装jdk21详细安装教程
java·linux
超级码.里奥.农2 小时前
零基础 “入坑” Java--- 七、数组(二)
java·开发语言
hqxstudying2 小时前
Java创建型模式---单例模式
java·数据结构·设计模式·代码规范
挺菜的2 小时前
【算法刷题记录(简单题)002】字符串字符匹配(java代码实现)
java·开发语言·算法
A__tao2 小时前
一键将 SQL 转为 Java 实体类,全面支持 MySQL / PostgreSQL / Oracle!
java·sql·mysql
一只叫煤球的猫2 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
猴哥源码2 小时前
基于Java+SpringBoot的农事管理系统
java·spring boot