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,线程永不死锁!


参考资料

相关推荐
橘子青衫2 分钟前
Java并发编程利器:CyclicBarrier与CountDownLatch解析
java·后端·性能优化
天天摸鱼的java工程师13 分钟前
高考放榜夜,系统别崩!聊聊查分系统怎么设计,三张表足以?
java·后端·mysql
天天摸鱼的java工程师22 分钟前
深入理解 Spring 核心:IOC 与 AOP 的原理与实践
java·后端
漫步者TZ22 分钟前
【Netty系列】解决TCP粘包和拆包:LengthFieldBasedFrameDecoder
java·网络协议·tcp/ip·netty
愿你是阳光06071 小时前
Java-redis实现限时在线秒杀功能
java·redis·bootstrap
我爱Jack1 小时前
Spring Boot统一功能处理深度解析
java·spring boot·后端
苦学编程的谢1 小时前
Java网络编程API 1
java·开发语言·网络
寒山李白2 小时前
Java 依赖注入、控制反转与面向切面:面试深度解析
java·开发语言·面试·依赖注入·控制反转·面向切面
casual_clover2 小时前
Android 之 kotlin语言学习笔记三(Kotlin-Java 互操作)
android·java·kotlin
AA-代码批发V哥2 小时前
Java正则表达式完全指南
java·正则表达式