【面试突击二】JAVA基础知识-volatile、synchronized与ReentrantLock深度对比

文章目录

  • [Java 中的 `volatile` 与锁机制详解](#Java 中的 volatile 与锁机制详解)
    • [1. `volatile` 是什么?](#1. volatile 是什么?)
    • [2. `volatile` 解决的两个核心问题](#2. volatile 解决的两个核心问题)
      • [2.1 可见性(Visibility)](#2.1 可见性(Visibility))
      • [2.2 有序性(避免部分重排序)](#2.2 有序性(避免部分重排序))
    • [3. `volatile` 做不到什么?(常见误区)](#3. volatile 做不到什么?(常见误区))
      • [3.1 不保证复合操作的原子性](#3.1 不保证复合操作的原子性)
      • [3.2 不是锁,不提供互斥](#3.2 不是锁,不提供互斥)
    • [4. 典型使用场景](#4. 典型使用场景)
      • [4.1 线程停止标志/状态控制](#4.1 线程停止标志/状态控制)
      • [4.2 配置信息 / 对象引用的热更新](#4.2 配置信息 / 对象引用的热更新)
      • [4.3 Double-Checked Locking 单例的 `instance`](#4.3 Double-Checked Locking 单例的 instance)
    • [5. Java 内存模型(JMM)中的 `volatile`](#5. Java 内存模型(JMM)中的 volatile)
    • [6. 对比:`volatile` / `synchronized` / `AtomicXXX`](#6. 对比:volatile / synchronized / AtomicXXX)
    • [7. 面试速记结论](#7. 面试速记结论)
  • [`synchronized` vs `ReentrantLock` 对比](#synchronized vs ReentrantLock 对比)
    • [1. 基本用法和特点](#1. 基本用法和特点)
    • [2. 可中断](#2. 可中断)
    • [3. 超时](#3. 超时)
    • [4. 公平性](#4. 公平性)
    • [5. Condition 多条件队列](#5. Condition 多条件队列)
    • [6. 其他差异](#6. 其他差异)
    • [7. 总结对比表](#7. 总结对比表)
    • [8. 面试归纳话术](#8. 面试归纳话术)

Java 中的 volatile 与锁机制详解


1. volatile 是什么?

在 Java 中,volatile 是一种轻量级同步机制 ,用来保证变量的可见性禁止指令重排序的一部分行为 ,但不保证复合操作的原子性

声明方式示例:

java 复制代码
volatile int flag;
volatile boolean running;
volatile Object ref;

2. volatile 解决的两个核心问题

2.1 可见性(Visibility)

多线程下,每个线程有自己的工作内存(寄存器、CPU 缓存等),对共享变量的修改可能只在本线程可见。

使用 volatile 后:

  • 一个线程对 volatile 变量的 ,会立刻刷新到主内存
  • 其他线程读同一个 volatile 变量时,会直接从主内存读取最新值。

示例:线程退出标志

java 复制代码
class Worker implements Runnable {
    private volatile boolean running = true;

    @Override
    public void run() {
        while (running) {
            // do work
        }
        System.out.println("Stopped.");
    }

    public void stop() {
        running = false;
    }
}

如果没有 volatile,某些情况下 while (running) 可能一直读到缓存中的旧值,导致线程无法退出。


2.2 有序性(避免部分重排序)

Java 内存模型(JMM)允许编译器和 CPU 对指令进行重排序,可能导致线程 B 看到线程 A 的某些操作乱序。

  • volatile 变量的 :之前对共享变量的写不会被重排序到这次写之后(写屏障)。
  • volatile 变量的 :之后的读/写操作不会被重排序到这次读之前(读屏障)。

happens-before 关系

线程 A 对某个 volatile 变量的写 happens-before 线程 B 之后对这个变量的读。


3. volatile 做不到什么?(常见误区)

3.1 不保证复合操作的原子性

i++,本质是"读 -> 加一 -> 写",volatile 只保证每次都去主内存读/写,不保证操作整体不可分割

错误示例:

java 复制代码
volatile int counter = 0;

public void inc() {
    counter++; // 非原子操作
}

多个线程并发调用时,counter 仍会丢失更新,不是线程安全

正确做法:

  • 用原子类
  • 或加锁
java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

class Counter {
    private final AtomicInteger counter = new AtomicInteger(0);

    public void inc() {
        counter.incrementAndGet();
    }
}

3.2 不是锁,不提供互斥

volatile 只保证可见性,不保证同一时刻只有一个线程操作。需要互斥时还是要用锁:

  • synchronized
  • ReentrantLock

4. 典型使用场景

4.1 线程停止标志/状态控制

适用于一个线程写标志,多线程读,不涉及复合操作。

java 复制代码
class Task implements Runnable {
    private volatile boolean running = true;

    @Override
    public void run() {
        while (running) {
            // do something
        }
    }

    public void shutdown() {
        running = false;
    }
}

实际项目更推荐 Thread.interrupt(),但 volatile boolean 仍常见。


4.2 配置信息 / 对象引用的热更新

用于保证对象引用被替换的可见性

java 复制代码
class ConfigManager {
    private volatile Config config = loadFromFile();

    public Config getConfig() {
        return config;
    }

    public void reload() {
        this.config = loadFromFile();
    }
}

4.3 Double-Checked Locking 单例的 instance

必须加 volatile,否则可能返回构造未完成的对象。

java 复制代码
public class 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;
    }
}

5. Java 内存模型(JMM)中的 volatile

  1. volatile 前的所有写操作"先行发生于"这次 volatile 写。
  2. 若线程 B 读取到线程 A 写入的 volatile 新值,也能看到 A 在此之前对其他共享变量的修改。

6. 对比:volatile / synchronized / AtomicXXX

特性 volatile synchronized AtomicXXX
可见性
原子性 单步读/写有,复合无 具体方法有(如自增)
互斥 无(CAS自旋)
重排序约束 更强
阻塞 不会 可能(线程挂起) 一般不会
典型场景 状态标志、热更新指针 临界区、多步逻辑 计数器、自增、自减

7. 面试速记结论

  • volatile 只保证可见性/有序性,不保证原子性。大家常用它做状态标志、配置热更新、DCL 单例指针等。
  • 多步逻辑或需要原子性的操作就要用 synchronizedAtomicInteger
  • 记住 DCL 单例必须加 volatile,否则会踩坑(构造重排序)。

synchronized vs ReentrantLock 对比


1. 基本用法和特点

一、synchronized

  • 关键字,自动加解锁
  • 锁对象是任意 Object
  • 语法简单,不易死锁
java 复制代码
synchronized (lock) {
    // 临界区
}

二、ReentrantLock

  • 类,需手动 lock/unlock
  • 支持可中断、超时、公平锁、多条件队列等高级功能
java 复制代码
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区
} finally {
    lock.unlock();
}

2. 可中断

  • synchronized 等待锁不可中断,即使线程被 interrupt,只是设置标志,不会抛异常或停止等待。
  • ReentrantLock.lockInterruptibly() 可中断 ,等待锁时收到 interrupt 会抛 InterruptedException,线程可以及时退出。

3. 超时

  • synchronized 不支持超时,只能无限等待。
  • ReentrantLock.tryLock(timeout, unit) 支持超时+可中断,时间内没拿到锁返回 false,期间可被 interrupt。

4. 公平性

  • synchronized 语义上不保证公平(老线程可能被饿死)。
  • ReentrantLock 构造参数可指定公平锁 (new ReentrantLock(true)),尽量按请求顺序获取锁。

5. Condition 多条件队列

  • synchronized 每把锁只有一个隐式等待队列 wait/notify
  • ReentrantLock 可通过 newCondition() 创建多个条件队列,每队唤醒/等待单独管理,复杂场景易用。

6. 其他差异

  • 释放锁:synchronized 自动、异常时也会释放;ReentrantLock 必须手动 unlock(常用 finally)。
  • 可重入性:两者都支持。
  • 调试/监控:ReentrantLock 有状态查询方法(如 isLocked),synchronized 没有直接 API。
  • 性能:JDK1.6 后 synchronized 性能很好,ReentrantLock 高并发/复杂要求下更灵活。

7. 总结对比表

维度 synchronized ReentrantLock
类型 语言关键字 (JVM级) 类 (java.util.concurrent.locks)
获取/释放方式 自动 手动 lock()/unlock()
可重入
可中断获取锁 是 (lockInterruptibly)
超时获取锁 是 (tryLock(timeout))
公平锁 不保证 支持构造参数
多条件队列 不支持 支持 (Condition)
性能 简单场景足够 高并发/复杂需求更强

8. 面试归纳话术

问:synchronizedReentrantLock 如何选择?

  • 普通同步优先 synchronized,语法简单安全。
  • 需要可中断/超时/公平锁/多条件队列/复杂锁逻辑 时,用 ReentrantLock 更佳。
  • 对于最细致的定制场景(如限时/可取消业务)优选 ReentrantLock

相关推荐
zero.cyx8 小时前
javaweb(AI)-----后端
java·开发语言
鹿角片ljp8 小时前
Java深入理解MySQL数据库操作
java·mysql·adb
NE_STOP8 小时前
SpringBoot集成shiro
java
RemainderTime8 小时前
从零搭建Spring Boot3.x生产级单体脚手架项目(JDK17 + Nacos + JWT + Docker)
java·spring boot·架构
黯叶8 小时前
基于 Docker+Docker-Compose 的 SpringBoot 项目标准化部署(外置 application-prod.yml 配置方案)
java·spring boot·redis·docker
say_fall8 小时前
泛型编程基石:C++ 模板从入门到熟练
java·开发语言·c++·编辑器·visual studio
代码笔耕8 小时前
写了几年 Java,我发现很多人其实一直在用“高级 C 语言”写代码
java·后端·架构
txinyu的博客9 小时前
结合游戏场景解析UDP可靠性问题
java·开发语言·c++·网络协议·游戏·udp
一路向北North9 小时前
springboot基础(85): validator验证器
java·spring boot·后端
1.14(java)9 小时前
掌握数据库约束:确保数据精准可靠
java·数据库·mysql·数据库约束