Java 线程与多线程从实战到避坑一篇彻底打通任督二脉多线程是 Java 高并发编程的核心基石。用好了系统如虎添翼用错了轻则死锁崩溃重则内存溢出、生产事故。本文从实战出发带你从入门到精通再到避坑一站式搞定。一、先把地基打牢核心概念厘清概念一句话定义类比进程操作系统资源分配的最小单位拥有独立内存空间一个自给自足的工厂线程CPU 调度的最小单位共享进程资源工厂里的工人并发单 CPU 上多任务交替执行时间片轮转一个厨师快速切换炒三道菜并行多 CPU 上多任务同时执行三个厨师同时炒三道菜关键认知Java 线程采用1:1 映射模型每个 Java 线程直接对应一个操作系统原生线程。线程创建、切换的开销远小于进程这正是多线程比多进程更高效的根本原因。二、四种创建线程的方式从青铜到王者 方式一继承 Thread 类青铜级javaclass MyThread extends Thread { Override public void run() { for (int i 0; i 5; i) { System.out.println(Thread.currentThread().getName() : i); } } } // 启动 new MyThread(线程A).start();⚠️致命缺陷Java 单继承继承了 Thread 就不能再继承其他类扩展性极差。不推荐在生产环境使用。 方式二实现 Runnable 接口黄金级·最推荐javaclass MyRunnable implements Runnable { private final String name; public MyRunnable(String name) { this.name name; } Override public void run() { for (int i 0; i 5; i) { System.out.println(name 执行: i); } } } // 启动 —— 解耦了线程逻辑与线程对象本身 new Thread(new MyRunnable(线程A), 窗口1).start(); new Thread(new MyRunnable(线程B), 窗口2).start();优势避免单继承限制同一个 Runnable 实例可被多个线程共享是日常开发的主流写法。 方式三实现 Callable FutureTask钻石级javaclass MyCallable implements CallableInteger { Override public Integer call() throws Exception { int sum 0; for (int i 1; i 100; i) sum i; return sum; } } // 启动并获取结果 FutureTaskInteger futureTask new FutureTask(new MyCallable()); new Thread(futureTask).start(); Integer result futureTask.get(); // 阻塞直到计算完成 System.out.println(结果: result);核心价值支持返回值和抛出异常弥补了 Runnable 的短板。方式返回值抛异常推荐场景Runnable❌❌火了就忘型任务Callable✅✅需要结果的计算任务 方式四线程池王者级·生产必用javaExecutorService executor Executors.newFixedThreadPool(3); for (int i 0; i 10; i) { final int taskId i; executor.execute(() - { System.out.println(Thread.currentThread().getName() 执行任务 taskId); }); } executor.shutdown(); executor.awaitTermination(60, TimeUnit.SECONDS);为什么线程池是王道优势说明降低资源消耗复用已创建线程避免反复创建销毁提高响应速度任务到达时线程已就绪防止资源耗尽限制线程数量避免无节制创建统一管理监控生命周期可控便于排查问题阿里巴巴开发手册明确规定禁止使用 Executors 创建线程池必须用ThreadPoolExecutor构造器因为Executors.newFixedThreadPool()使用的是无界队列任务激增时会导致OOM。java// ✅ 推荐写法 ThreadPoolExecutor pool new ThreadPoolExecutor( 5, // 核心线程数 10, // 最大线程数 60L, TimeUnit.SECONDS, // 空闲线程存活时间 new LinkedBlockingQueue(100), // 有界队列 new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 );三、线程的六种状态一张图看透生死状态含义典型触发NEW新建尚未 start()new Thread()RUNNABLE就绪/运行中等待 CPU 调度start()之后BLOCKED等待获取监视器锁synchronized竞争失败WAITING无限期等待object.wait()/join()TIMED_WAITING限时等待sleep()/wait(timeout)TERMINATED执行完毕已死亡run()执行完 / 异常终止四、线程同步不加锁就是在裸奔 经典问题100 人抢 10 张票javaclass TicketService { private int ticket 10; // ❌ 非线程安全 —— 会超卖 public void sell() { if (ticket 0) { try { Thread.sleep(300); } catch (Exception e) {} System.out.println(卖出: ticket--); } } }✅ 方案一synchronized 同步方法javapublic synchronized void sell() { if (ticket 0) { try { Thread.sleep(300); } catch (Exception e) {} System.out.println(卖出: ticket--); } }✅ 方案二synchronized 代码块更精细javapublic void sell() { synchronized (this) { // 只锁核心逻辑性能更优 if (ticket 0) { try { Thread.sleep(300); } catch (Exception e) {} System.out.println(卖出: ticket--); } } }✅ 方案三ReentrantLock高并发推荐javaprivate final ReentrantLock lock new ReentrantLock(); public void sell() { lock.lock(); try { if (ticket 0) { try { Thread.sleep(300); } catch (Exception e) {} System.out.println(卖出: ticket--); } } finally { lock.unlock(); // ⚠️ 必须在 finally 中释放 } }方案适用场景synchronized简单场景代码简洁ReentrantLock高并发、需要尝试锁/超时锁AtomicInteger简单计数如count⚠️volatile 只能保证可见性不能保证原子性count是读-改-写三步操作必须用AtomicInteger。五、五大深坑踩一个就够你喝一壶️ 坑一线程池参数不当 → OOMExecutors.newFixedThreadPool()使用无界队列任务堆积时内存直接爆炸。解法用有界队列 拒绝策略CallerRunsPolicy适合限流降级。️ 坑二ThreadLocal 内存泄漏java// ❌ 忘记清理 threadLocal.set(value); // 线程被回收后value 永远驻留内存 // ✅ 必须在 finally 中清除 try { threadLocal.set(value); // ... 业务逻辑 } finally { threadLocal.remove(); }️ 坑三死锁 —— 程序假死无声无息java// ❌ 经典死锁A 锁 X 等 YB 锁 Y 等 X Object lockA new Object(); Object lockB new Object(); Thread t1 new Thread(() - { synchronized (lockA) { synchronized (lockB) { /* ... */ } } }); Thread t2 new Thread(() - { synchronized (lockB) { // 顺序反了 synchronized (lockA) { /* ... */ } } });解法固定加锁顺序所有线程按同一顺序获取锁使用tryLock(timeout)设置超时用jstack定位死锁线程️ 坑四吞掉 InterruptedExceptionjava// ❌ 最常见的错误 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); // 吞掉了线程中断状态被清除 } // ✅ 正确做法恢复中断状态 catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断标志 throw new RuntimeException(e); }️ 坑五误以为 start() run()javaMyThread t new MyThread(); t.run(); // ❌ 这只是普通方法调用不会启动新线程 t.start(); // ✅ 这才是真正启动新线程一个线程对象只能 start() 一次第二次调用会抛出IllegalThreadStateException。六、调试神器jstack 抓线程快照当线上出现 CPU 飙高、程序假死时bash# 1. 找到 Java 进程 PID jps -l # 2. 生成线程快照重点看 BLOCKED 状态 jstack -l 12345 thread_dump.txt重点关注java.lang.Thread.State: BLOCKED→ 锁竞争waiting to lock 0x...→ 正在等哪把锁死锁信息会明确标出Found one Java-level deadlockJVMWAITING 状态显示HotSpotWAITING (on object monitor)IBM J9Waiting for monitor entry七、最佳实践清单建议收藏✅ 要做❌ 别做用线程池别手动 new Thread用Executors创建线程池有界队列 拒绝策略无界队列赌不会满finally中释放锁和资源在 try 里释放锁volatile保可见Atomic保原子用volatile替代原子类join()/CountDownLatch做线程协作wait()不加循环条件判断CompletableFuture替代回调地狱嵌套Future.get()阻塞主线程用jstack/VisualVM排查问题靠System.out.println调多线程写在最后多线程编程的本质是在充分利用资源和保证数据安全之间走钢丝。它不是银弹而是一把双刃剑——用对了系统吞吐量翻倍用错了死锁、OOM、数据错乱接踵而至。记住一句话对多线程保持敬畏之心。在代码评审时紧盯同步边界在生产环境做好线程池监控在测试阶段用并发测试工具系统性验证。只有经过充分验证的代码才配得上生产环境的信任。 终极建议能不用多线程就别用能用线程池就别手写 Thread能用CompletableFuture就别用Future.get()阻塞。简单才是最高级的并发。
Java 线程与多线程:从实战到避坑,一篇彻底打通任督二脉
Java 线程与多线程从实战到避坑一篇彻底打通任督二脉多线程是 Java 高并发编程的核心基石。用好了系统如虎添翼用错了轻则死锁崩溃重则内存溢出、生产事故。本文从实战出发带你从入门到精通再到避坑一站式搞定。一、先把地基打牢核心概念厘清概念一句话定义类比进程操作系统资源分配的最小单位拥有独立内存空间一个自给自足的工厂线程CPU 调度的最小单位共享进程资源工厂里的工人并发单 CPU 上多任务交替执行时间片轮转一个厨师快速切换炒三道菜并行多 CPU 上多任务同时执行三个厨师同时炒三道菜关键认知Java 线程采用1:1 映射模型每个 Java 线程直接对应一个操作系统原生线程。线程创建、切换的开销远小于进程这正是多线程比多进程更高效的根本原因。二、四种创建线程的方式从青铜到王者 方式一继承 Thread 类青铜级javaclass MyThread extends Thread { Override public void run() { for (int i 0; i 5; i) { System.out.println(Thread.currentThread().getName() : i); } } } // 启动 new MyThread(线程A).start();⚠️致命缺陷Java 单继承继承了 Thread 就不能再继承其他类扩展性极差。不推荐在生产环境使用。 方式二实现 Runnable 接口黄金级·最推荐javaclass MyRunnable implements Runnable { private final String name; public MyRunnable(String name) { this.name name; } Override public void run() { for (int i 0; i 5; i) { System.out.println(name 执行: i); } } } // 启动 —— 解耦了线程逻辑与线程对象本身 new Thread(new MyRunnable(线程A), 窗口1).start(); new Thread(new MyRunnable(线程B), 窗口2).start();优势避免单继承限制同一个 Runnable 实例可被多个线程共享是日常开发的主流写法。 方式三实现 Callable FutureTask钻石级javaclass MyCallable implements CallableInteger { Override public Integer call() throws Exception { int sum 0; for (int i 1; i 100; i) sum i; return sum; } } // 启动并获取结果 FutureTaskInteger futureTask new FutureTask(new MyCallable()); new Thread(futureTask).start(); Integer result futureTask.get(); // 阻塞直到计算完成 System.out.println(结果: result);核心价值支持返回值和抛出异常弥补了 Runnable 的短板。方式返回值抛异常推荐场景Runnable❌❌火了就忘型任务Callable✅✅需要结果的计算任务 方式四线程池王者级·生产必用javaExecutorService executor Executors.newFixedThreadPool(3); for (int i 0; i 10; i) { final int taskId i; executor.execute(() - { System.out.println(Thread.currentThread().getName() 执行任务 taskId); }); } executor.shutdown(); executor.awaitTermination(60, TimeUnit.SECONDS);为什么线程池是王道优势说明降低资源消耗复用已创建线程避免反复创建销毁提高响应速度任务到达时线程已就绪防止资源耗尽限制线程数量避免无节制创建统一管理监控生命周期可控便于排查问题阿里巴巴开发手册明确规定禁止使用 Executors 创建线程池必须用ThreadPoolExecutor构造器因为Executors.newFixedThreadPool()使用的是无界队列任务激增时会导致OOM。java// ✅ 推荐写法 ThreadPoolExecutor pool new ThreadPoolExecutor( 5, // 核心线程数 10, // 最大线程数 60L, TimeUnit.SECONDS, // 空闲线程存活时间 new LinkedBlockingQueue(100), // 有界队列 new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 );三、线程的六种状态一张图看透生死状态含义典型触发NEW新建尚未 start()new Thread()RUNNABLE就绪/运行中等待 CPU 调度start()之后BLOCKED等待获取监视器锁synchronized竞争失败WAITING无限期等待object.wait()/join()TIMED_WAITING限时等待sleep()/wait(timeout)TERMINATED执行完毕已死亡run()执行完 / 异常终止四、线程同步不加锁就是在裸奔 经典问题100 人抢 10 张票javaclass TicketService { private int ticket 10; // ❌ 非线程安全 —— 会超卖 public void sell() { if (ticket 0) { try { Thread.sleep(300); } catch (Exception e) {} System.out.println(卖出: ticket--); } } }✅ 方案一synchronized 同步方法javapublic synchronized void sell() { if (ticket 0) { try { Thread.sleep(300); } catch (Exception e) {} System.out.println(卖出: ticket--); } }✅ 方案二synchronized 代码块更精细javapublic void sell() { synchronized (this) { // 只锁核心逻辑性能更优 if (ticket 0) { try { Thread.sleep(300); } catch (Exception e) {} System.out.println(卖出: ticket--); } } }✅ 方案三ReentrantLock高并发推荐javaprivate final ReentrantLock lock new ReentrantLock(); public void sell() { lock.lock(); try { if (ticket 0) { try { Thread.sleep(300); } catch (Exception e) {} System.out.println(卖出: ticket--); } } finally { lock.unlock(); // ⚠️ 必须在 finally 中释放 } }方案适用场景synchronized简单场景代码简洁ReentrantLock高并发、需要尝试锁/超时锁AtomicInteger简单计数如count⚠️volatile 只能保证可见性不能保证原子性count是读-改-写三步操作必须用AtomicInteger。五、五大深坑踩一个就够你喝一壶️ 坑一线程池参数不当 → OOMExecutors.newFixedThreadPool()使用无界队列任务堆积时内存直接爆炸。解法用有界队列 拒绝策略CallerRunsPolicy适合限流降级。️ 坑二ThreadLocal 内存泄漏java// ❌ 忘记清理 threadLocal.set(value); // 线程被回收后value 永远驻留内存 // ✅ 必须在 finally 中清除 try { threadLocal.set(value); // ... 业务逻辑 } finally { threadLocal.remove(); }️ 坑三死锁 —— 程序假死无声无息java// ❌ 经典死锁A 锁 X 等 YB 锁 Y 等 X Object lockA new Object(); Object lockB new Object(); Thread t1 new Thread(() - { synchronized (lockA) { synchronized (lockB) { /* ... */ } } }); Thread t2 new Thread(() - { synchronized (lockB) { // 顺序反了 synchronized (lockA) { /* ... */ } } });解法固定加锁顺序所有线程按同一顺序获取锁使用tryLock(timeout)设置超时用jstack定位死锁线程️ 坑四吞掉 InterruptedExceptionjava// ❌ 最常见的错误 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); // 吞掉了线程中断状态被清除 } // ✅ 正确做法恢复中断状态 catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断标志 throw new RuntimeException(e); }️ 坑五误以为 start() run()javaMyThread t new MyThread(); t.run(); // ❌ 这只是普通方法调用不会启动新线程 t.start(); // ✅ 这才是真正启动新线程一个线程对象只能 start() 一次第二次调用会抛出IllegalThreadStateException。六、调试神器jstack 抓线程快照当线上出现 CPU 飙高、程序假死时bash# 1. 找到 Java 进程 PID jps -l # 2. 生成线程快照重点看 BLOCKED 状态 jstack -l 12345 thread_dump.txt重点关注java.lang.Thread.State: BLOCKED→ 锁竞争waiting to lock 0x...→ 正在等哪把锁死锁信息会明确标出Found one Java-level deadlockJVMWAITING 状态显示HotSpotWAITING (on object monitor)IBM J9Waiting for monitor entry七、最佳实践清单建议收藏✅ 要做❌ 别做用线程池别手动 new Thread用Executors创建线程池有界队列 拒绝策略无界队列赌不会满finally中释放锁和资源在 try 里释放锁volatile保可见Atomic保原子用volatile替代原子类join()/CountDownLatch做线程协作wait()不加循环条件判断CompletableFuture替代回调地狱嵌套Future.get()阻塞主线程用jstack/VisualVM排查问题靠System.out.println调多线程写在最后多线程编程的本质是在充分利用资源和保证数据安全之间走钢丝。它不是银弹而是一把双刃剑——用对了系统吞吐量翻倍用错了死锁、OOM、数据错乱接踵而至。记住一句话对多线程保持敬畏之心。在代码评审时紧盯同步边界在生产环境做好线程池监控在测试阶段用并发测试工具系统性验证。只有经过充分验证的代码才配得上生产环境的信任。 终极建议能不用多线程就别用能用线程池就别手写 Thread能用CompletableFuture就别用Future.get()阻塞。简单才是最高级的并发。