Java高频面试之并发编程-17


volatile 和 synchronized 的区别

在 Java 并发编程中,volatilesynchronized 是两种常用的同步机制,但它们的适用场景和底层原理有显著差异。以下是两者的详细对比:


1. 核心功能对比

特性 volatile synchronized
原子性 不保证复合操作的原子性(如 i++)。 保证代码块或方法的原子性。
可见性 强制变量读写直接操作主内存,保证可见性。 通过锁的释放/获取强制同步内存,保证可见性。
有序性 禁止指令重排序(内存屏障)。 通过锁规则保证临界区内的操作有序性。
线程阻塞 无阻塞,轻量级。 可能引发线程阻塞(锁竞争时)。
适用场景 单写多读、状态标志。 复合操作、临界区资源保护。

2. 原子性

  • volatile

    • 不保证原子性,仅确保单次读/写操作的原子性。

    • 示例

      java 复制代码
      volatile int count = 0;
      count++; // 非原子操作(实际是 read-modify-write)

      多线程并发时,count++ 可能导致数据竞争。

  • synchronized

    • 保证原子性,锁内的所有操作作为一个整体执行。

    • 示例

      java 复制代码
      synchronized (lock) {
          count++; // 原子操作
      }

3. 可见性

  • volatile

    • 通过内存屏障强制线程每次读写 volatile 变量时直接操作主内存,跳过工作内存(CPU 缓存)。

    • 示例

      java 复制代码
      volatile boolean flag = true;
      // 线程1修改 flag 后,线程2立即可见
  • synchronized

    • 线程进入同步块时清空工作内存,从主内存重新加载变量;退出同步块时将变量写回主内存。

    • 示例

      java 复制代码
      synchronized (lock) {
          // 操作共享变量
      }

4. 有序性

  • volatile

    • 通过插入内存屏障(StoreStoreStoreLoad 等)禁止编译器和处理器对 volatile 变量的读写操作进行重排序。

    • 示例 :双重检查锁定(DCL)中防止对象未初始化就被引用。

      java 复制代码
      private static volatile Singleton instance;
  • synchronized

    • 通过"锁规则"实现有序性:同一时刻只有一个线程能执行同步块,天然保证操作按代码顺序执行。

    • 示例

      java 复制代码
      synchronized (lock) {
          // 临界区内的操作不会重排序到锁外
      }

5. 性能开销

  • volatile

    • 轻量级:仅通过内存屏障限制重排序,无线程阻塞和上下文切换开销。
    • 适用场景:适合低竞争或单写多读场景(如状态标志)。
  • synchronized

    • 重量级:涉及锁竞争、内核态切换(重量级锁)和线程阻塞,开销较大。
    • 优化机制:锁升级(偏向锁 → 轻量级锁 → 重量级锁)减少无竞争时的开销。
    • 适用场景:高竞争或需要复合原子操作的场景(如计数器、资源池)。

6. 底层实现

  • volatile

    • 内存屏障 :JVM 在字节码层面插入屏障指令(如 StoreStoreLoadLoad)。
    • 硬件支持:依赖 CPU 的缓存一致性协议(如 MESI)强制刷新内存。
  • synchronized

    • Monitor 机制:基于对象头的 Mark Word 实现锁状态管理(无锁、偏向锁、轻量级锁、重量级锁)。
    • 内核态操作:重量级锁依赖操作系统的互斥量(Mutex)实现线程阻塞。

7. 实际应用示例

(1) volatile 适用场景
  • 状态标志

    java 复制代码
    volatile boolean shutdownRequested = false;
    public void shutdown() { shutdownRequested = true; }
    public void doWork() { while (!shutdownRequested) { /* ... */ } }
  • 单例模式(DCL)

    java 复制代码
    public class Singleton {
        private static volatile Singleton instance;
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
(2) synchronized 适用场景
  • 线程安全的计数器

    java 复制代码
    public class Counter {
        private int count = 0;
        public synchronized void increment() { count++; }
        public synchronized int getCount() { return count; }
    }
  • 资源池管理

    java 复制代码
    public class ConnectionPool {
        private final LinkedList<Connection> pool = new LinkedList<>();
        public synchronized Connection getConnection() {
            while (pool.isEmpty()) wait();
            return pool.removeFirst();
        }
        public synchronized void release(Connection conn) {
            pool.addLast(conn);
            notifyAll();
        }
    }

8. 总结对比表

维度 volatile synchronized
原子性 仅单次读/写原子,不保证复合操作。 保证代码块或方法的原子性。
可见性 直接操作主内存,强制刷新。 通过锁同步内存。
有序性 禁止指令重排序。 通过锁规则保证临界区有序。
线程阻塞 无阻塞。 可能阻塞(锁竞争时)。
性能开销 低。 高(尤其是重量级锁)。
适用场景 状态标志、单例模式(DCL)。 复合操作、资源竞争、线程间协作。

选择建议

  • 使用 volatile 的情况

    • 变量被多个线程访问,但只有一个线程修改。
    • 需要禁止指令重排序(如 DCL 单例模式)。
  • 使用 synchronized 的情况

    • 需要原子性操作(如 i++、复合逻辑)。
    • 多线程竞争同一资源(如连接池、计数器)。
相关推荐
LayZhangStrive32 分钟前
融360 一面
java·面试·后端开发
城管不管1 小时前
重生——第十次面试之开源中国一面挂
java·linux·开发语言·算法·面试·职场和发展·开源
Java小白笔记1 小时前
Windows系统免软件命令激活
java·网络·人工智能·windows·ai·ai编程
爱敲键盘的猴子1 小时前
深入理解 Java HashMap
java·hashmap
霸道流氓气质1 小时前
Java开发者AI编程常用MCP、Skills、Rules全景汇总
java·开发语言·ai编程
天涯醉青楼1 小时前
Spring Boot 接口返回 Bean 时字段名“变形”的深度剖析与解决方案
java
zoujiahui_20182 小时前
Python 包与环境管理工具 uv
开发语言·python·uv
步行cgn2 小时前
MyBatis 高级映射与延迟加载完全指南
java·tomcat·mybatis
thefool1122662 小时前
从中序与后序遍历序列构造二叉树
java
吴声子夜歌2 小时前
Java面试题——JVM(二)
java·开发语言·jvm