对比 synchronized 和 volatile

简介: `synchronized` 和 `volatile` 是 Java 并发编程中的两个关键机制,各有侧重。`synchronized` 用于实现线程的互斥访问,保证原子性、可见性和有序性,适用于需要锁的场景;而 `volatile` 更轻量,仅确保变量的可见性和有序性,适用于状态标志等无需复合操作的场景。两者可互补使用,如双重检查单例中结合二者优势。合理选择有助于提升并发性能与代码安全性。

synchronizedvolatile 是 Java 中用于处理并发编程的两个重要关键字,它们的作用和适用场景有明显区别,但也有一定的互补性。以下是两者的详细对比:

核心区别

特性 synchronized volatile
原子性 保证代码块或方法在同一时刻只能被一个线程访问,从而保证操作的原子性。例如:
synchronized (this) { count++; }
仅保证变量的单次读/写操作是原子的(如 volatile int count),但复合操作(如 count++)不保证原子性。
可见性 通过解锁时刷新本地内存到主内存,加锁时从主内存读取最新值,保证可见性。 通过内存屏障(Memory Barrier)强制刷新变量到主内存,并使其他线程的本地缓存失效,保证可见性。
有序性 通过锁的获取和释放建立 Happens-Before 关系,禁止指令重排序。 禁止特定类型的指令重排序(如写操作前的指令不能重排序到写操作之后)。
使用范围 可修饰方法、代码块。 只能修饰变量。
性能开销 涉及锁的获取和释放,可能导致线程阻塞,性能开销较大。 仅通过内存屏障实现,无锁竞争,性能开销较小。
线程阻塞 会导致未获取锁的线程阻塞。 不会导致线程阻塞。

底层实现

  • synchronized
    • 依赖对象头中的 Monitor(监视器)实现,通过锁升级(偏向锁 → 轻量级锁 → 重量级锁)优化性能。
    • 涉及用户态与内核态的切换(重量级锁),成本较高。
  • volatile
    • 通过 JVM 插入内存屏障(如 StoreLoad 屏障)实现:
      • 写操作后插入屏障,确保写操作前的所有操作都已完成,并刷新到主内存。
      • 读操作前插入屏障,确保其他线程的写操作已刷新到主内存。

典型应用场景

场景 synchronized 示例 volatile 示例
状态标志 java<br>public synchronized void setFlag(boolean flag) {<br> this.flag = flag;<br>}<br> java<br>private volatile boolean flag = false;<br>
复合操作原子性 java<br>public synchronized int increment() {<br> return count++;<br>}<br> 不适用(count++ 非原子)。
单例模式 java<br>public static synchronized Singleton getInstance() { ... }<br> java<br>private static volatile Singleton instance;<br>
发布不可变对象 java<br>public synchronized void publish() {<br> this.obj = new ImmutableObject(...);<br>}<br> java<br>private volatile ImmutableObject obj;<br>

结合使用示例

双重检查锁定(Double-Checked Locking)单例模式

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;
    }
}
  • 关键点
    • volatile 防止 new Singleton() 的指令重排序(如先分配内存再初始化对象),避免其他线程看到半初始化的对象。
    • synchronized 保证只有一个线程能创建实例。

选择建议

  1. 仅需可见性:使用 volatile,如状态标志位。

    private volatile boolean shutdown = false;
    
    public void shutdown() {
          shutdown = true; }
    
    public void run() {
         
        while (!shutdown) {
          ... }  // 保证立即看到其他线程修改的 shutdown 值
    }
    
  2. 需原子性+可见性:使用 synchronizedReentrantLock,如计数器。
    public synchronized void increment() {
          count++; }
    
  3. 复合操作volatile 不适用,需使用锁或原子类(如 AtomicInteger)。
    private final AtomicInteger count = new AtomicInteger(0);
    public void increment() {
          count.incrementAndGet(); }  // 原子操作
    

总结

需求 synchronized volatile Atomic*
原子性
可见性
有序性
无锁实现
低延迟(高并发场景)
  • 优先使用 volatile:当仅需保证变量可见性且不涉及复合操作时。
  • 使用 synchronized:当需要保证原子性、可见性和有序性,且并发度不高时。
  • 考虑原子类:在高并发场景下,对单个变量的复合操作优先使用 Atomic* 类(如 AtomicInteger)。
目录
相关文章
|
2月前
|
Java Spring
Spring Boot配置的优先级?
在Spring Boot项目中,配置可通过配置文件和外部配置实现。支持的配置文件包括application.properties、application.yml和application.yaml,优先级依次降低。外部配置常用方式有Java系统属性(如-Dserver.port=9001)和命令行参数(如--server.port=10010),其中命令行参数优先级高于系统属性。整体优先级顺序为:命令行参数 &gt; Java系统属性 &gt; application.properties &gt; application.yml &gt; application.yaml。
528 0
|
2月前
|
Java 应用服务中间件 Maven
SpringBoot使用汇总
本节介绍了Spring Boot项目工程结构,包含src/main/java(业务代码)、src/main/resources(静态与配置文件)和src/test/java(测试代码)。通过@SpringBootApplication注解的启动类运行main方法即可快速启动应用。Spring Boot内置Tomcat,简化配置流程。示例展示了创建Controller、访问接口及修改默认端口的方法,帮助开发者快速上手Spring Boot开发。
100 2
|
2月前
|
存储 安全 Java
synchronized 原理
`synchronized` 是 Java 中实现线程同步的关键字,通过对象头中的 Monitor 和锁机制确保同一时间只有一个线程执行同步代码。其底层依赖 Mark Word 和 Monitor 控制锁状态,支持偏向锁、轻量级锁和重量级锁的升级过程,以优化性能。同步方法和同步块在实现方式上有所不同,前者通过 `ACC_SYNCHRONIZED` 标志隐式加锁,后者通过 `monitorenter` 和 `monitorexit` 指令显式控制。此外,`synchronized` 还保证内存可见性和 Happens-Before 关系,使共享变量在多线程间正确同步。
375 0
|
2月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
729 0
|
2月前
|
Java Spring
聊聊你对SpringBoot框架的理解 ?
SpringBoot是Spring家族中流行的子项目,旨在简化Spring框架开发的繁琐配置。它主要提供三大功能:starter起步依赖简化依赖管理,自动配置根据条件创建Bean,以及内嵌Web服务器支持Jar包运行,极大提升了开发效率。
125 0
|
2月前
|
存储 安全 Java
synchronized 锁升级
JDK 6 引入的 synchronized 锁升级机制,通过偏向锁、轻量级锁和重量级锁的动态切换,优化了多线程同步性能。该机制根据竞争情况逐步升级锁状态,减少线程阻塞和系统调用开销,从而提升并发效率。
123 0
|
2月前
HTTP协议中常见的状态码 ?
HTTP协议状态码分为1xx、2xx、3xx、4xx、5xx五类。常见状态码包括:101(切换协议)、200(请求成功)、302(重定向)、401(未认证)、404(资源未找到)、500(服务器错误)。
271 0
|
2月前
|
消息中间件 NoSQL Java
SpringBoot框架常见的starter你都用过哪些 ?
本节介绍常见的Spring Boot Starter,分为官方(如Web、AOP、Redis等)与第三方(如MyBatis、MyBatis Plus)两类,用于快速集成Web开发、数据库、消息队列等功能。
204 0
|
2月前
|
JSON Java 数据格式
Spring Boot返回Json数据及数据封装
在Spring Boot中,接口间及前后端的数据传输通常使用JSON格式。通过@RestController注解,可轻松实现Controller返回JSON数据。该注解是Spring Boot新增的组合注解,结合了@Controller和@ResponseBody的功能,默认将返回值转换为JSON格式。Spring Boot底层默认采用Jackson作为JSON解析框架,并通过spring-boot-starter-json依赖集成了相关库,包括jackson-databind、jackson-datatype-jdk8等常用模块,简化了开发者对依赖的手动管理。
356 3
|
2月前
|
搜索推荐 Python
为啥说选择排序是不稳定的
选择排序是一种简单但不稳定的排序算法。它通过每轮选择最小元素并交换位置来实现排序,但这种交换可能破坏相同值元素的相对顺序。例如对数组 `[5, 8, 5, 2]` 排序后,两个 `5` 的顺序会发生变化,从而证明其不稳定性。
145 0

热门文章

最新文章