java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java  synchronized 和 volatile

Java中 synchronized 和 volatile的核心区别解析

作者:木易 士心

本文给大家介绍Java中synchronized和volatile的核心区别,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

概述

Java 并发编程中的两个核心关键字:synchronized 和 volatile。它们都是为了解决多线程环境下的数据一致性问题,但在作用机制、保证的特性以及适用场景上有着本质的区别。

简单来说:
synchronized 是一把“重量级的锁”,它通过互斥访问来保证原子性、可见性和有序性。
volatile 是一个“轻量级的同步机制”,它主要保证可见性和有序性,但不保证原子性。

1. synchronized 关键字详解

synchronized 是 Java 中最基础、最常用的同步机制,它通过获取和释放对象的“监视器锁”(Monitor Lock)来实现线程间的互斥访问。

1.1 作用与核心特性

1.2. 使用方式

synchronized 可以修饰方法或代码块,锁定的对象不同,其作用范围也不同。

1.2.1 修饰实例方法 (非静态方法)

public class Counter {
    private int count = 0;
    // 锁定的是当前对象实例 (this)
    public synchronized void increment() {
        count++; // 这个操作是原子的
    }
    public synchronized int getCount() {
        return count;
    }
}

锁对象 当前对象实例 (this)。
作用范围 同一个对象实例的多个 synchronized 实例方法之间是互斥的。不同对象实例的 synchronized 方法可以并发执行。

1.2.2 修饰静态方法

public class GlobalCounter {
    private static int globalCount = 0;
    // 锁定的是当前类的 Class 对象 (GlobalCounter.class)
    public static synchronized void incrementGlobal() {
        globalCount++;
    }
    public static synchronized int getGlobalCount() {
        return globalCount;
    }
}

锁对象 该类的 Class 对象。
作用范围 无论创建多少个类的实例,所有线程在调用该类的 synchronized 静态方法时,都会竞争同一把锁,实现全局互斥。

1.2.3 修饰代码块 (Synchronized Block)

public class FineGrainedCounter {
    private int countA = 0;
    private int countB = 0;
    private final Object lockA = new Object();
    private final Object lockB = new Object();
    // 只锁定操作 countA 的部分,提高并发度
    public void incrementA() {
        synchronized (lockA) { // 锁定指定的对象 lockA
            countA++;
        }
    }
    // 只锁定操作 countB 的部分
    public void incrementB() {
        synchronized (lockB) { // 锁定指定的对象 lockB
            countB++;
        }
    }
    // 锁定当前对象实例
    public void doSomething() {
        synchronized (this) {
            // ... 临界区代码
        }
    }
}

锁对象 synchronized 括号内指定的任意对象。
作用范围 灵活性最高。可以精确控制需要同步的代码范围,避免将整个方法都锁定,从而减少锁的竞争,提高并发性能。

1.3. 实现原理

JVM 通过对象内部的“监视器锁”(Monitor)来实现 synchronized。在字节码层面:

为了优化性能,JDK 1.6 引入了锁升级机制:

1.4. 优缺点

1.5. 适用场景

适用于需要对共享资源进行复杂操作、保证操作原子性的场景,例如:

2. volatile 关键字详解

volatile 是一个变量修饰符,它不提供任何互斥机制,而是通过内存屏障(Memory Barrier)来保证变量的可见性和禁止指令重排序。

2.1 作用与核心特性

2.2. 使用方式

volatile 只能用来修饰变量。

public class VolatileExample {
    // 修饰一个布尔标志位,用于线程间通信
    private volatile boolean shutdownRequested = false;
    // 修饰一个对象引用
    private volatile Config config;
    // 线程A:设置标志位
    public void shutdown() {
        shutdownRequested = true; // 写操作,会立即刷新到主内存
    }
    // 线程B:检查标志位
    public void doWork() {
        while (!shutdownRequested) { // 读操作,每次都从主内存读取最新值
            // ... 执行任务
        }
        // 收到关闭请求,优雅退出
    }
    // 注意:以下操作不是原子的!
    private volatile int counter = 0;
    public void unsafeIncrement() {
        counter++; // 读-改-写,非原子操作,多线程下结果可能错误
    }
}

2.3 实现原理

volatile 的实现主要依赖于 CPU 的缓存一致性协议(如 MESI)和 JVM 插入的内存屏障指令。它告诉 JVM 和 CPU,这个变量是“易变的”,不能对其进行激进的优化(如缓存、重排序)。

2.4. 优缺点

2.5. 适用场景

适用于“一个线程写,多个线程读”,且写操作是原子的(通常是直接赋值)的场景:

public class Singleton {
    // volatile 防止 instance = new Singleton() 指令重排序
    private static volatile Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new Singleton(); // 可能发生重排序
                }
            }
        }
        return instance;
    }
}

3 总结

3.1 synchronized 与 volatile 的核心区别

特性synchronizedvolatile
作用对象方法、代码块变量
核心机制互斥锁 (Monitor)内存屏障 (Memory Barrier)
原子性保证 (通过互斥实现)不保证 (仅保证单次读/写原子)
可见性保证 (进出同步块时刷新主内存)保证 (强制读写主内存)
有序性保证 (通过互斥和禁止重排序)保证 (通过内存屏障禁止重排序)
线程阻塞会阻塞 (未获取锁的线程进入阻塞状态)不会阻塞 (线程可以继续执行)
性能开销较大 (涉及操作系统,可能上下文切换)较小 (主要是内存屏障开销)
适用场景复杂的原子操作、临界区保护 简单的状态标志、一次性安全发布、DCL单例模式

3.2 适用场景

3.2.1 状态标志控制 使用volatile

仅需保证可见性进需要操作是原子的 (如 flag = true): 优先使用 volatile,因为它更轻量。

class TaskRunner {
    private volatile boolean stopped = false; // 线程安全的状态标志
    public void run() {
        while (!stopped) { /* 执行任务 */ }
    }
    public void stop() { stopped = true; } // 修改立即可见
}

3.2.2 单例模式(双重检查锁定)synchronized+volatile

volatile防止new Singleton()的分解步骤重排序,避免返回未初始化的对象

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;
    }
}

3.2.3 临界区保护 使用synchronized

强制原子性,适合需要互斥访问的复合操作(如读写共享变量)。

class BankAccount {
    private double balance;
    public synchronized void deposit(double amount) { // 整个方法同步
        balance += amount;
    }
    public void withdraw(double amount) {
        synchronized (this) { // 代码块同步
            balance -= amount;
        }
    }
}

3.2.4 线程协作(等待/通知机制)

synchronized提供锁的获取/释放机制,配合wait()/notifyAll()实现线程间协作。

class ProducerConsumer {
    private final Object lock = new Object();
    private boolean isProduced = false;
    public void produce() {
        synchronized (lock) {
            while (isProduced) { lock.wait(); } // 等待消费
            // 生产数据...
            isProduced = true;
            lock.notifyAll(); // 通知消费者
        }
    }
    public void consume() {
        synchronized (lock) {
            while (!isProduced) { lock.wait(); } // 等待生产
            // 消费数据...
            isProduced = false;
            lock.notifyAll(); // 通知生产者
        }
    }
}

到此这篇关于Java中 synchronized 和 volatile的核心区别解析的文章就介绍到这了,更多相关Java synchronized 和 volatile内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文