Java 线程安全与通信详解:synchronized、volatile 及 wait/notify

巷口听风
巷口听风
发布时间:2026-10-16 | 5人浏览过
原创

在 Java 并发编程中,线程安全是保障数据一致性的基石。本文剖析了由原子性缺失、内存不可见及指令重排序导致的线程不安全原因,并通过 synchronized 和 volatile 提供两种不同粒度的解决方案,最后详解利用 wait() 和 notify() 实现线程间的状态通信与资源协作。

线程不安全的成因分析

当多个线程并行执行且共享变量时,若操作包含写行为或条件判断,极易引发安全问题。这主要源于以下三个维度:

  • 原子性破坏:看似单行的操作如 n++ 或对象初始化,在底层涉及多步执行(读取、修改、写入),非单一完整过程,易被中断造成数据错乱。
  • 可见性缺失:各线程拥有独立的工作内存,对共享变量的本地修改若不即时刷新至主存,其他线程将读取到过期脏数据。
  • 代码重排序:JVM 与 CPU 为提升效率会在保证逻辑不变前提下调整指令顺序,导致线程执行的预期步骤与实际发生顺序不符。

Java 内存模型(JMM)旨在屏蔽底层硬件差异,规范内存读写行为。主内存存储共享变量,而每个线程通过工作内存进行交互。读取时先拷贝至工作内存,修改后再同步回主存。

解决策略:synchronized 与 volatile

设计原则应在满足安全的前提下追求高效。针对上述隐患,synchronized 提供了重量级的阻塞锁方案,而 volatile 则是轻量级的信号修饰符。

1. synchronized 关键字的特性

synchronized 作用于对象头,用于获取对象锁以实现同步。其使用分为修饰实例方法与静态方法:

// 修饰普通方法,锁定当前实例对象 this
public class TestDemo {
    public synchronized void method() {
    }
}
// 修饰静态方法,锁定类对象
public class TestDemo {
    public static void method() {
        synchronized(TestDemo.class){
        }
    }
}

该机制具备四大核心特性:互斥性确保同一时刻仅一个线程持有锁;可见性在释放锁时将工作内存变量强制刷入主存;有序性保护临界区逻辑不被外部干扰;以及可重入性,允许同一线程多次获取自身已持有的锁,防止死锁。

2. volatile 关键字的限制

volatile 专用于修饰变量,其作用在于保障数据的可见性(修改后立即刷新)和禁止重排序(插入内存屏障)。但它绝不保证原子性。因此,它适用于常量赋值或独立的读/写操作,若需复合操作(如自增),仍应回归 synchronized 或 Lock。

线程间的通信机制

线程并非孤立存在,通过等待与通知机制可实现资源的有序流转。这种协作依赖于 Object 基类的方法。

1. wait 与 notify 的配合

wait() 方法使当前线程进入阻塞状态并放弃锁,直到被唤醒或超时:

Object object = new Object();
synchronized (object) {
    // 线程在此暂停并释放 object 锁
    object.wait();
}

notify() 随机唤醒单个等待线程,而 notifyAll() 唤醒所有。需注意,notify 执行后当前线程不会立即释放锁,须跳出同步块后才生效。即便 notifyAll 唤醒了全部,线程仍需竞争锁方能继续执行。

2. wait 与 sleep 的本质区别

虽然两者均暂停当前执行流,但 wait 属于 Object 方法,必须在同步块内调用,且主动释放锁以配合通信;而 sleep 是 Thread 的静态方法,无需依赖同步环境,且在休眠期间依旧占有锁资源,通常用于延时而非跨线程协调。

进程与线程的权衡

相较于进程,线程是更细粒度的执行单元。进程的创建开销大且拥有独立内存空间,而线程不仅创建迅速、切换成本低,更能共享所属进程的资源(如文件句柄、内存页),从而实现高效的 I/O 重叠与多核计算利用。

以上就是 Java 深入探索线程安全和线程通信的详细内容,更多关于 Java 并发编程的资料请关注本站其它相关文章!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表