1. Java多线程基础概念解析
第一次接触Java多线程时,我完全被各种术语和概念搞晕了。经过多年实际项目开发,现在回头看,理解多线程其实可以从一个生活场景开始:想象你在厨房做饭,一边煮汤一边炒菜,这就是典型的多任务场景。在Java中,每个"烹饪动作"就是一个线程。
1.1 进程与线程的本质区别
操作系统中最小的执行单位是线程,而进程则是资源的集合。用公司来比喻:
- 进程就像一家公司,拥有办公场地、资金等资源
- 线程就像公司里的员工,真正干活的是他们
- 单线程进程就是个体户,老板员工都是一个人
在Java中,每个运行的程序都是一个JVM进程,main()方法就是主线程。我们通过代码验证这个特性:
public class MainThreadDemo { public static void main(String[] args) { System.out.println("当前线程名称:" + Thread.currentThread().getName()); System.out.println("当前线程ID:" + Thread.currentThread().getId()); } }运行后会输出类似:
当前线程名称:main 当前线程ID:11.2 多线程的适用场景
不是所有情况都适合用多线程。根据我的经验,这些场景特别适合:
- IO密集型操作:如文件读写、网络请求
- 计算密集型任务:需要多核CPU并行计算
- 异步处理:日志记录、消息推送等后台任务
- GUI应用:保持界面响应同时处理任务
重要提示:在单核CPU上,多线程并不会真正并行执行,只是通过时间片轮转模拟并发。真正的并行需要多核CPU支持。
2. Java线程创建与生命周期
2.1 三种创建线程的方式
方式一:继承Thread类
class MyThread extends Thread { @Override public void run() { System.out.println("线程运行中:" + getName()); } } // 使用示例 MyThread thread = new MyThread(); thread.start();方式二:实现Runnable接口
class MyRunnable implements Runnable { @Override public void run() { System.out.println("线程运行中:" + Thread.currentThread().getName()); } } // 使用示例 Thread thread = new Thread(new MyRunnable()); thread.start();方式三:使用Lambda表达式(Java8+)
new Thread(() -> { System.out.println("Lambda线程:" + Thread.currentThread().getName()); }).start();开发建议:优先选择实现Runnable接口的方式,因为Java不支持多重继承,使用接口更灵活。
2.2 线程状态转换详解
Java线程的生命周期包含6种状态:
- NEW:刚创建未启动
- RUNNABLE:可运行状态(包括正在运行和就绪)
- BLOCKED:等待监视器锁
- WAITING:无限期等待
- TIMED_WAITING:有限期等待
- TERMINATED:线程终止
状态转换示意图:
NEW → start() → RUNNABLE RUNNABLE → wait() → WAITING WAITING → notify() → RUNNABLE RUNNABLE → sleep(100) → TIMED_WAITING RUNNABLE → synchronized → BLOCKED RUNNABLE → run()结束 → TERMINATED查看线程状态的代码示例:
Thread thread = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println("创建后状态:" + thread.getState()); // NEW thread.start(); System.out.println("启动后状态:" + thread.getState()); // RUNNABLE Thread.sleep(500); System.out.println("睡眠中状态:" + thread.getState()); // TIMED_WAITING thread.join(); System.out.println("结束后状态:" + thread.getState()); // TERMINATED3. 线程同步与锁机制
3.1 同步问题的根源
多线程并发时会出现问题的根本原因是:共享变量的可见性和操作原子性。看这个典型例子:
class Counter { private int count = 0; public void add() { count++; } public int get() { return count; } }当多个线程同时调用add()时,最终结果往往会小于预期。因为count++实际上包含三个操作:
- 读取count值
- 值+1
- 写回count
3.2 synchronized关键字
解决上述问题的最基本方式是使用synchronized:
public synchronized void add() { count++; }或者使用同步代码块:
public void add() { synchronized(this) { count++; } }注意事项:synchronized是重量级锁,在JDK1.6后进行了优化,但在高并发场景下仍需谨慎使用。
3.3 volatile关键字
volatile解决的是可见性问题,但不保证原子性:
class Shared { private volatile boolean flag = false; public void setFlag() { flag = true; } public boolean getFlag() { return flag; } }适用场景:
- 状态标志位(如开关控制)
- 单例模式的双重检查锁定
4. 线程间通信
4.1 wait/notify机制
典型的生产者-消费者模式实现:
class MessageQueue { private String message; private boolean empty = true; public synchronized String take() { while(empty) { try { wait(); } catch (InterruptedException e) {} } empty = true; notifyAll(); return message; } public synchronized void put(String message) { while(!empty) { try { wait(); } catch (InterruptedException e) {} } empty = false; this.message = message; notifyAll(); } }4.2 使用BlockingQueue
更现代的实现方式是使用java.util.concurrent包中的BlockingQueue:
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10); // 生产者 new Thread(() -> { try { queue.put("消息1"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); // 消费者 new Thread(() -> { try { String message = queue.take(); System.out.println("收到:" + message); } catch (InterruptedException e) { e.printStackTrace(); } }).start();5. 线程池最佳实践
5.1 为什么需要线程池
直接创建线程的问题:
- 创建销毁线程开销大
- 线程数量不受控可能导致资源耗尽
- 缺乏统一管理
5.2 ThreadPoolExecutor详解
核心参数:
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:空闲线程存活时间
- workQueue:任务队列
- threadFactory:线程工厂
- handler:拒绝策略
创建示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // 核心线程数 4, // 最大线程数 60, // 空闲时间 TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), // 任务队列 Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() );5.3 四种预定义线程池
- FixedThreadPool:固定大小
- CachedThreadPool:可缓存线程池
- SingleThreadExecutor:单线程
- ScheduledThreadPool:定时任务
实际经验:阿里巴巴开发规范建议使用ThreadPoolExecutor构造函数创建,避免使用Executors工厂方法,因为后者可能隐藏参数设置问题。
6. 常见问题排查
6.1 线程死锁
死锁产生的四个必要条件:
- 互斥条件
- 请求与保持
- 不剥夺条件
- 循环等待
诊断死锁的方法:
// 1. 使用jstack命令 // 2. 代码检测 ThreadMXBean bean = ManagementFactory.getThreadMXBean(); long[] threadIds = bean.findDeadlockedThreads(); if (threadIds != null) { ThreadInfo[] infos = bean.getThreadInfo(threadIds); for (ThreadInfo info : infos) { System.out.println("死锁线程:" + info.getThreadName()); } }6.2 内存泄漏
线程相关的内存泄漏常见原因:
- 静态集合持有线程引用
- 线程局部变量未清理
- 线程池未正确关闭
排查工具:
- VisualVM
- MAT内存分析工具
- JProfiler
7. Java内存模型(JMM)深入
7.1 主内存与工作内存
JMM规定:
- 所有变量存储在主内存
- 每个线程有自己的工作内存
- 线程不能直接读写主内存变量
7.2 happens-before原则
重要的happens-before规则:
- 程序顺序规则
- 锁规则
- volatile规则
- 线程启动规则
- 线程终止规则
- 中断规则
- 终结器规则
- 传递性
8. 并发工具类实战
8.1 CountDownLatch
典型应用场景:多任务并行执行,等待所有任务完成
CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { new Thread(() -> { try { // 模拟任务执行 Thread.sleep((long)(Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + " 完成"); latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } latch.await(); System.out.println("所有任务已完成");8.2 CyclicBarrier
与CountDownLatch的区别:
- 可重复使用
- 所有线程互相等待
CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("所有线程已到达屏障点"); }); for (int i = 0; i < 3; i++) { new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + " 到达屏障"); barrier.await(); System.out.println(Thread.currentThread().getName() + " 继续执行"); } catch (Exception e) { e.printStackTrace(); } }).start(); }9. 性能优化技巧
9.1 减少锁竞争
优化策略:
- 缩小同步代码块范围
- 使用读写锁(ReentrantReadWriteLock)
- 使用无锁数据结构(Atomic类)
- 锁分离技术
9.2 避免虚假共享
典型例子:
class FalseSharing { volatile long x; // 可能和下一个变量在同一个缓存行 volatile long y; }解决方案:
- 填充(Padding)
- 使用@Contended注解(Java8+)
10. Java并发编程发展趋势
10.1 虚拟线程(Project Loom)
Java19引入的轻量级线程特性:
- 由JVM管理调度
- 上下文切换开销小
- 创建数量可达百万级
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { executor.submit(() -> { System.out.println("虚拟线程运行中"); }); }10.2 结构化并发(Java21预览)
新的并发编程范式:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Future<String> user = scope.fork(() -> findUser()); Future<Integer> order = scope.fork(() -> fetchOrder()); scope.join(); scope.throwIfFailed(); return new Response(user.resultNow(), order.resultNow()); }