MST

星途 面试题库

面试题:Java中volatile关键字与synchronized关键字在使用场景上有何不同

请从原理、功能、性能以及适用场景等多方面对比分析volatile关键字和synchronized关键字,举例说明在哪些情况下应该优先选择使用volatile,哪些情况下必须使用synchronized。
16.6万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

原理

  • volatile:保证变量在多线程之间的可见性,当一个线程修改了volatile变量的值,新值对其他线程立即可见。它不会引起线程上下文切换和调度,不保证原子性。通过内存屏障实现,禁止指令重排序。
  • synchronized:通过对象的内置锁机制实现线程同步,一个线程进入同步块或方法时,会获取对象的锁,其他线程只能等待锁的释放。它保证原子性、可见性和有序性,会导致线程上下文切换和调度。

功能

  • volatile:主要解决变量在多线程间的可见性问题,适用于一写多读场景,当一个线程修改变量,其他线程能及时看到变化。
  • synchronized:不仅保证可见性,还能保证原子性和有序性。可用于控制对共享资源的访问,避免并发访问导致的数据不一致问题。

性能

  • volatile:因为不涉及线程上下文切换和调度,性能开销相对较小,适用于读多写少的场景。
  • synchronized:由于线程竞争锁会导致上下文切换和调度,性能开销较大,尤其在高并发且锁竞争激烈的场景下。

适用场景

  • 优先选择volatile的场景
    • 状态标记量,如 boolean running = false,当一个线程修改 runningtrue,其他线程需要立即感知到。
    public class VolatileExample {
        private volatile boolean running = false;
    
        public void start() {
            running = true;
        }
    
        public void doWork() {
            while (!running) {
                // 执行其他操作
            }
            // running变为true后执行的操作
        }
    }
    
    • 读多写少的共享变量,如配置参数等,多个线程频繁读取,偶尔有一个线程修改。
  • 必须使用synchronized的场景
    • 对共享资源进行复杂操作,如读写操作都可能改变共享资源状态的场景,像银行转账操作,需要保证原子性。
    public class BankAccount {
        private int balance;
    
        public BankAccount(int initialBalance) {
            this.balance = initialBalance;
        }
    
        public synchronized void transfer(BankAccount to, int amount) {
            if (this.balance >= amount) {
                this.balance -= amount;
                to.balance += amount;
            }
        }
    }
    
    • 保证操作的原子性、可见性和有序性,比如在多线程环境下对计数器进行自增操作。
    public class Counter {
        private int count = 0;
    
        public synchronized void increment() {
            count++;
        }
    
        public synchronized int getCount() {
            return count;
        }
    }