首页 > Java > Java面试题 > 正文

synchronized 和 volatile 的区别是什么?

畫卷琴夢
发布: 2025-09-15 08:04:01
原创
758人浏览过
synchronized保证原子性和可见性,通过锁对象的monitor实现,同一时刻仅一个线程可执行同步代码块;volatile仅保证可见性,禁止指令重排序,适用于状态标志等场景。两者性能与适用场景不同,需根据需求选择。此外,Java还提供Lock、原子类、并发集合等更灵活的同步机制。

synchronized 和 volatile 的区别是什么?

synchronized 和 volatile,这两个 Java 关键字,就像武林中的两门绝学,虽然都能提升多线程环境下的数据安全,但修炼心法和适用场景却大相径庭。简单来说,

synchronized
登录后复制
是重量级锁,保证原子性和可见性,而
volatile
登录后复制
是轻量级同步机制,只保证可见性。

synchronized 和 volatile 的区别

synchronized 到底锁了什么?

synchronized
登录后复制
可不是简单地给变量加个锁这么简单。它锁的是对象。更准确地说,是对象的 monitor(监视器)。每个 Java 对象都关联一个 monitor,当线程进入
synchronized
登录后复制
修饰的代码块或方法时,它会尝试获取该对象的 monitor。如果 monitor 已经被其他线程持有,那么该线程就会进入阻塞状态,直到 monitor 被释放。

synchronized
登录后复制
保证原子性的方式是,同一时刻只有一个线程可以持有对象的 monitor,因此对该对象的任何操作都相当于串行执行,避免了竞态条件。

synchronized
登录后复制
保证可见性的方式,则是在线程释放 monitor 时,会将工作内存中的变量值刷新到主内存中,并且在线程获取 monitor 时,会从主内存中重新加载变量值到工作内存中。这样就保证了不同线程之间对共享变量的可见性。

举个例子,假设我们有一个计数器类:

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}
登录后复制

在这个例子中,

increment()
登录后复制
getCount()
登录后复制
方法都使用了
synchronized
登录后复制
关键字。这意味着,当一个线程调用
increment()
登录后复制
方法时,它会获取
Counter
登录后复制
对象的 monitor。其他线程如果也想调用
increment()
登录后复制
getCount()
登录后复制
方法,就必须等待第一个线程释放 monitor。这样就保证了
count
登录后复制
变量的原子性和可见性。

但是,

synchronized
登录后复制
也有缺点,那就是性能开销比较大。线程在获取 monitor 时,需要进行上下文切换,这会消耗大量的 CPU 时间。此外,
synchronized
登录后复制
还会导致线程阻塞,降低程序的并发性。

volatile 只能保证可见性,那它有什么用?

volatile
登录后复制
关键字告诉 JVM,该变量的值可能被多个线程同时修改,因此每次使用该变量时,都应该从主内存中重新加载。这样就保证了不同线程之间对该变量的可见性。

但需要注意的是,

volatile
登录后复制
只能保证可见性,不能保证原子性。也就是说,如果多个线程同时修改一个
volatile
登录后复制
变量,仍然可能出现竞态条件。

例如:

魔乐社区
魔乐社区

天翼云和华为联合打造的AI开发者社区,支持AI模型评测训练、全流程开发应用

魔乐社区102
查看详情 魔乐社区
public class VolatileCounter {
    private volatile int count = 0;

    public void increment() {
        count++; // 这是一个复合操作,不是原子性的
    }

    public int getCount() {
        return count;
    }
}
登录后复制

在这个例子中,

count
登录后复制
变量使用了
volatile
登录后复制
关键字。虽然
volatile
登录后复制
保证了
count
登录后复制
变量的可见性,但
increment()
登录后复制
方法中的
count++
登录后复制
操作并不是原子性的。它实际上包含了三个步骤:

  1. 读取
    count
    登录后复制
    的值。
  2. count
    登录后复制
    的值加 1。
  3. 将结果写回
    count
    登录后复制

如果多个线程同时执行

increment()
登录后复制
方法,那么可能会出现以下情况:

  1. 线程 A 读取
    count
    登录后复制
    的值为 10。
  2. 线程 B 读取
    count
    登录后复制
    的值为 10。
  3. 线程 A 将
    count
    登录后复制
    的值加 1,得到 11,然后将 11 写回
    count
    登录后复制
  4. 线程 B 将
    count
    登录后复制
    的值加 1,得到 11,然后将 11 写回
    count
    登录后复制

最终,

count
登录后复制
的值是 11,而不是 12。这就是竞态条件。

那么,

volatile
登录后复制
到底有什么用呢?

volatile
登录后复制
最常用的场景是作为状态标志,例如:

public class Worker {
    private volatile boolean running = true;

    public void start() {
        new Thread(() -> {
            while (running) {
                // 执行一些任务
            }
            System.out.println("Worker stopped.");
        }).start();
    }

    public void stop() {
        running = false;
    }
}
登录后复制

在这个例子中,

running
登录后复制
变量使用了
volatile
登录后复制
关键字。当调用
stop()
登录后复制
方法时,
running
登录后复制
的值会被设置为
false
登录后复制
。由于
running
登录后复制
volatile
登录后复制
变量,所以所有线程都会立即看到这个变化,从而停止执行任务。

何时使用 synchronized,何时使用 volatile?

这其实是一个权衡的问题。

  • 需要保证原子性:必须使用
    synchronized
    登录后复制
    或其他原子类(如
    AtomicInteger
    登录后复制
    )。
    volatile
    登录后复制
    无能为力。
  • 只需要保证可见性,并且对变量的写操作不依赖于当前值:可以使用
    volatile
    登录后复制
    。例如,上述的状态标志。
  • 性能要求较高:如果
    synchronized
    登录后复制
    带来的性能开销过大,可以考虑使用
    volatile
    登录后复制
    ,但前提是必须满足上述条件。

总的来说,

synchronized
登录后复制
是一个更强大的同步机制,可以保证原子性和可见性,但性能开销也比较大。
volatile
登录后复制
是一个轻量级的同步机制,只能保证可见性,但性能开销比较小。在选择使用哪个关键字时,需要根据具体的场景进行权衡。

除了 synchronized 和 volatile,还有哪些同步机制?

Java 并发编程的世界远不止

synchronized
登录后复制
volatile
登录后复制
这么简单。还有很多其他的同步机制,例如:

  • Lock 接口及其实现类
    Lock
    登录后复制
    接口提供了比
    synchronized
    登录后复制
    更灵活的锁机制,例如可重入锁
    ReentrantLock
    登录后复制
    、读写锁
    ReadWriteLock
    登录后复制
    等。
    Lock
    登录后复制
    需要手动释放锁,因此需要放在
    try...finally
    登录后复制
    块中。
  • 原子类
    java.util.concurrent.atomic
    登录后复制
    包下提供了一系列的原子类,例如
    AtomicInteger
    登录后复制
    AtomicLong
    登录后复制
    AtomicReference
    登录后复制
    等。这些类使用 CAS(Compare-and-Swap)算法来实现原子操作,性能通常比
    synchronized
    登录后复制
    更高。
  • 并发集合
    java.util.concurrent
    登录后复制
    包下提供了一系列的并发集合,例如
    ConcurrentHashMap
    登录后复制
    CopyOnWriteArrayList
    登录后复制
    等。这些集合针对并发场景进行了优化,可以提供更高的并发性能。
  • CountDownLatch、CyclicBarrier、Semaphore:这些是 Java 并发包中提供的同步工具类,用于协调多个线程之间的执行。

选择哪种同步机制,取决于具体的并发场景和性能要求。没有银弹,只有最适合的工具。理解每种工具的优缺点,才能在并发编程的世界里游刃有余。

以上就是synchronized 和 volatile 的区别是什么?的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号