理论与实践:深入理解Java并发基础

207 阅读7分钟

Java 并发编程.png

为什么需要多线程?——让程序"三头六臂"

突破单核性能瓶颈

场景类比:快递仓库分拣流水线
单线程就像1个分拣工在1000㎡仓库奔走,多线程则是10个分拣工分区作业,效率提升10倍

现实开发中的典型需求

场景类型典型案例多线程价值
异步任务处理聊天软件收发消息避免界面卡死
高并发服务12306售票系统每秒处理10万级请求
计算密集型任务视频转码/科学计算利用多核CPU并行计算

线程不安全现场直击——银行账户惨案

事故代码还原

public class UnsafeBank {
    private int balance = 1000;  // 初始金额
    
    // 致命漏洞:非原子操作
    public void withdraw(int amount) {
        if (balance >= amount) {
            // 此处可能发生线程切换!
            balance = balance - amount;
        }
    }
}

事故时间线推演

sequenceDiagram
    线程A->>+余额: 读取balance=1000
    线程B->>+余额: 读取balance=1000
    线程A->>余额: 写入balance=200(扣800)
    线程B->>余额: 写入balance=700(扣300)
    最终余额-->>结果: 实际扣款1100,余额却剩700!

并发问题的三大元凶

可见性问题——"各自为政的备忘录"

现实比喻:办公室团队协作
小明修改了共享文档,但小红看到的还是本地缓存的老版本

// 没有volatile声明
boolean flag = true;
void threadA() {
    while(flag) { /* 可能永远循环 */ }
}
void threadB() {
    flag = false;
}

原子性问题——"被打断的取钱操作"

关键原理
看似简单的balance -= amount在字节码层面是多个操作:

  1. getstatic #2 // 读取balance
  2. iload_1 // 加载amount
  3. isub // 做减法
  4. putstatic #2 // 写回balance

有序性问题——"编译器的善意优化"

// 可能被重排序为:先分配内存,再初始化对象
instance = new Singleton(); 
// 实际期望的执行顺序:
1. 分配内存空间
2. 初始化对象
3. 设置instance指向内存地址

一、线程运行机制揭秘

1.1 线程的微观世界

现代CPU通过时间片轮转实现并发执行:

// 示例:线程上下文切换过程
Thread t1 = new Thread(() -> {
    while(true) {
        // 任务A片段(时间片用完时保存现场)
    }
});
Thread t2 = new Thread(() -> {
    while(true) {
        // 任务B片段(加载上次保存的上下文)
    }
});
  • 每个时间片约5-100ms
  • 上下文切换涉及:程序计数器、寄存器状态、栈指针

1.2 Java内存模型(JMM)

sequenceDiagram
    participant 主内存
    participant 工作内存A
    participant 工作内存B
    
    主内存->>工作内存A: 变量读取(read)
    工作内存A->>主内存: 变量写入(write)
    主内存->>工作内存B: 变量同步(同步延迟导致可见性问题)

关键特性:

  • 原子性:synchronized/Lock保证
  • 可见性:volatile/Memory Barrier保证
  • 有序性:happens-before原则

二、线程的本质:轻量级执行单元

线程是操作系统调度的最小执行单位,可以理解为一条代码执行的路径。Java中的线程通过Thread类和Runnable接口实现,底层依赖操作系统提供的线程模型。

类比理解

想象一个快递仓库:

  • 进程 = 整个仓库(独立资源空间)
  • 线程 = 仓库中的分拣工人(共享仓库资源,各自独立工作)

三、线程同步全景图

3.1 锁的进化史

gantt
    title 锁升级过程
    dateFormat  YYYY-MM-DD
    section 无锁
    初始状态 : 2025-03-01, 1d

    section 偏向锁
    单线程访问 : 2025-03-02, 2d

    section 轻量级锁
    少量竞争 : 2025-03-04, 4d

    section 重量级锁
    激烈竞争 : 2025-03-08, 6d

3.2 锁优化策略

  • 自旋锁(默认10次自旋)
  • 锁消除(逃逸分析)
  • 锁粗化(合并相邻同步块)

四、线程池深度调优

4.1 参数配置矩阵

// 最佳实践示例
ExecutorService executor = new ThreadPoolExecutor(
    4, // 核心线程数(CPU密集型建议N+1)
    16, // 最大线程数(IO密集型建议2N)
    30, // 空闲时间(单位需配合时间单位)
    TimeUnit.SECONDS, 
    new LinkedBlockingQueue<>(1000), // 队列容量根据业务设置
    new CustomRejectedPolicy() // 自定义拒绝策略
);

配置公式:

  • CPU密集型:coreSize = CPU核心数 + 1
  • IO密集型:coreSize = CPU核心数 * 2

五、线程生命周期(流程图解析)

stateDiagram-v2
    [*] --> New
    New --> Runnable: start()
    Runnable --> Running: 获取CPU时间片
    Running --> Terminated: 执行完成
    Running --> Runnable: yield()
    Running --> Blocked: wait()/lock
    Blocked --> Runnable: notify()/资源就绪
    Running --> TimedWaiting: sleep(1000)
    TimedWaiting --> Runnable: 时间到期

关键状态说明:

  • 新建new Thread()但未启动
  • 就绪:等待CPU分配时间片
  • 运行:正在执行任务
  • 阻塞:等待I/O、锁等资源
  • 终止:执行完成或被中断

六、线程同步的三层防护体系

6.1 硬件级保障

  • CAS指令:Compare-And-Swap原子操作

CAS魔法——"无锁的原子操作"

AtomicInteger balance = new AtomicInteger(1000);
public void safeWithdraw(int amount) {
    balance.updateAndGet(current -> 
        current >= amount ? current - amount : current
    );
}
  • 内存屏障:禁止指令重排序

内存屏障——"禁止插队的告示牌"

// volatile保证可见性与禁止重排序
class Singleton {
    private volatile static Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {               // 第一次检查
            synchronized(Singleton.class) {   // 加锁
                if (instance == null) {        // 第二次检查
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 缓存一致性协议:MESI协议

同步锁机制——"会议室使用规则"

// 同步方法
public synchronized void safeWithdraw(int amount) { ... }
// 同步代码块
private final Object lock = new Object();
public void safeWithdraw(int amount) {
    synchronized(lock) {
        // ...
    }
}

6.2 JVM级同步

public class SyncExample {
    // 对象头Mark Word结构
    // | 锁标志位 | 偏向锁位 | 锁状态 |
    // |---------|---------|-------|
    // |   01    |    0    | 无锁  |
    
    public synchronized void methodA() {
        // 同步代码块
    }
}

锁升级过程: 无锁 → 偏向锁 → 轻量级锁 → 重量级锁

锁的升级之路:偏向锁、轻量级锁和重量级锁详解

6.3 并发工具三剑客

工具类适用场景特点
CountDownLatch多线程任务汇总一次性屏障,不可重置
CyclicBarrier多阶段任务控制可重复使用,支持回调
Semaphore资源池控制支持公平/非公平模式

七、线程同步三大武器

7-1. synchronized关键字

public class Counter {
    private int count;
    
    public synchronized void increment() {
        count++;
    }
}
  • 通过对象监视器(Monitor)实现
  • 支持方法级和代码块级同步

7-2. Lock接口

Lock lock = new ReentrantLock();
public void doSomething() {
    lock.lock();
    try {
        // 临界区代码
    } finally {
        lock.unlock();
    }
}
  • 更灵活的锁机制
  • 支持公平锁、超时锁等特性

7-3. volatile关键字

private volatile boolean flag = true;
  • 保证变量可见性
  • 禁止指令重排序
  • 不保证原子性(适合状态标志位)

八、线程池工作原理

8.1 核心参数矩阵

mindmap
    root((线程池参数))
        核心配置
            corePoolSize
            maximumPoolSize
            keepAliveTime
        任务队列
            ArrayBlockingQueue
            LinkedBlockingQueue
            SynchronousQueue
        拒绝策略
            AbortPolicy
            CallerRunsPolicy
            DiscardOldestPolicy

8.2 任务处理流程图解

flowchart TD
    A[提交任务] --> B{核心线程<br>未满?}
    B -->|是| C[创建新线程执行]
    B -->|否| D{队列<br>未满?}
    D -->|是| E[加入等待队列]
    D -->|否| F{最大线程<br>未满?}
    F -->|是| G[创建临时线程]
    F -->|否| H[执行拒绝策略]
    E --> I[队列线程等待执行]
    C & G --> J[执行任务]

关键参数:

  • corePoolSize:常驻核心线程数
  • workQueue:任务缓存队列
  • maximumPoolSize:最大线程数
  • RejectedExecutionHandler:拒绝策略

九、高频问题解析

9-1. 线程 vs 进程

进程线程
资源开销大(独立内存空间)小(共享内存)
通信方式管道、信号、Socket等共享内存、wait/notify
上下文切换成本高成本低

9-2. 死锁检测实战

// 死锁示例代码
public class DeadLockDemo {
    static Object lockA = new Object();
    static Object lockB = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lockA) {
                try { Thread.sleep(100); } 
                catch (InterruptedException e) {}
                synchronized (lockB) {}
            }
        }).start();
        new Thread(() -> {
            synchronized (lockB) {
                synchronized (lockA) {}
            }
        }).start();
    }
}

诊断步骤:

  1. jps获取进程ID
  2. jstack <pid>分析线程状态
  3. 查找"deadlock"关键词

9-3. 死锁产生条件

  1. 互斥条件
  2. 请求与保持
  3. 不可剥夺
  4. 循环等待 避免方案:
  • 按顺序获取锁
  • 使用tryLock()设置超时
  • 通过ThreadMXBean检测死锁

9-4. 死锁检测四步法

  1. 使用jps获取进程ID
    jps -l
    
  2. 生成线程转储
    jstack <pid> > thread_dump.log
    
  3. 分析死锁信息
    Found one Java-level deadlock:
    "Thread-2":
      waiting to lock monitor 0x00007f8934003f58 (object 0x0000000716c7c6c0)
      which is held by "Thread-1"
    
  4. 使用VisualVM可视化分析

9-5. 内存泄漏排查技巧

graph TD
    A[内存持续增长] --> B[生成堆转储]
    B --> C[使用MAT分析]
    C --> D[查找ThreadLocal引用链]
    D --> E[定位未清理的Entry]
    E --> F[添加remove调用]

9-6. ThreadLocal原理

  • 每个线程独立存储空间
  • 通过ThreadLocalMap实现
  • 典型应用场景:
    • 数据库连接管理
    • 用户会话信息存储

9-7. ThreadLocal内存泄漏防护

sequenceDiagram
    participant App as 应用程序
    participant ThreadLocal
    participant Thread
    participant Entry
    
    App->>ThreadLocal: 创建实例
    ThreadLocal->>Thread: 存储数据
    Thread->>Entry: 维护ThreadLocalMap
    Note over Thread,Entry: Key为弱引用<br>Value为强引用
    App->>ThreadLocal: remove()
    ThreadLocal->>Entry: 删除对应条目

防护建议:

  • 始终在finally块中调用remove()
  • 避免使用static修饰ThreadLocal
  • 定期检查内存使用情况

十、性能优化

10-1. 线程上下文切换优化

  • 案例:某交易系统上下文切换从5000次/秒降到800次/秒
  • 优化手段:
    1. 减少同步代码块粒度
    2. 使用无锁数据结构
    3. 调整线程池大小

10-2. CPU利用率提升方案

// 错误示例:过度使用线程
for(int i=0; i<10000; i++){
    new Thread(() -> {
        // 简单计算任务
    }).start();
}
// 优化方案:使用线程池+批量处理
ExecutorService executor = Executors.newFixedThreadPool(8);
List<Future> futures = new ArrayList<>();
for(int i=0; i<10000; i++){
    futures.add(executor.submit(() -> {
        // 批量处理任务
    }));
}

十一、Java线程新特性

11-1. 虚拟线程(协程)

// 预览版示例(JDK19+)
Thread.startVirtualThread(() -> {
    System.out.println("轻量级虚拟线程");
});

核心优势:

  • 创建成本:传统线程1MB vs 虚拟线程~200B
  • 切换效率:用户态切换,无内核介入

11-2. Structured Concurrency

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<String> user = scope.fork(() -> findUser());
    Future<Integer> order = scope.fork(() -> fetchOrder());
    
    scope.join();
    return new Response(user.get(), order.get());
}

特点:

  • 线程生命周期与代码块绑定
  • 异常传播机制完善

Java线程原理.png