​ 面试被问Synchornized和Volatile?听到我的回答后,面试官端着瑞幸的手颤抖了.

​ 面试被问Synchornized和Volatile?当我回答这些以后,面试官端着瑞幸的手颤抖了......

不想被拷打?带你速通 Synchronized 和Volatile核心考点

    彦祖亦菲们面试的时候经常被问到 “Synchronized 和Volatile有啥区别”、“Volatile为啥不保证原子性” 等等,是不是总卡壳?别慌!这俩 Java 并发里的 “基础王”,看似简单却藏着高频考点,今天用几分钟帮你捋清核心,下次再被问直接 “反拷打”!

首先我们要先弄清楚:Java为啥需要这俩关键字?

在Java多线程中,影响线程安全的三大"刺客"就是:原子性可见性有序性。当多线程访问操作我们的共享变量时,这三者有一个没有处理好就会引起一系列的麻烦。而我们今天要讲的 Synchronized 和Volatile,它们本质上都是帮我们解决这些问题的"良药",但它们之间也有着各自的优缺点和不同。

Synchronized,“全能锁” 但不笨重

Synchronized被称为 “内置锁”,是一种内置的同步机制,用来解决多线程访问共享资源时的线程安全问题。它的主要作用是确保同一时刻只有一个线程能够访问某些特定的代码片段或方法。

Synchronized最核心的能力是同时保证原子性、可见性、有序性,是并发里的 “万能选手”

原子性:一旦某个线程拿到锁,其他线程就得等着,直到它释放锁。这意味着被 Synchronized 修饰的代码块或方法,会被 “打包” 成一个不可分割的操作(比如多个步骤的计算、赋值,不会被其他线程打断)。举个例子:synchronized void add() { count++; },即使 有10 个线程同时调用,count++(拆成 “读 - 改 - 写” 三步)也不会被拆分,最终结果一定正确。下面代码通过两线程各累加1000次的对比,显示不加锁的addWithoutSync()结果易错,加synchronizedaddWithSync()结果必为2000,印证其原子性保障。

public class SynchronizedDemo {
    // 共享变量:记录累加结果
    private static int count = 0;
    // 循环次数:让每个线程累加1000次
    private static final int LOOP = 1000;
    public static void main(String[] args) throws InterruptedException {
        // 创建两个线程,都执行累加操作
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < LOOP; i++) {
                // 不加锁的累加(可能出问题)
                // addWithoutSync();
                // 加锁的累加(保证正确)
                addWithSync();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < LOOP; i++) {
                // 不加锁的累加(可能出问题)
                // addWithoutSync();
                // 加锁的累加(保证正确)
                addWithSync();
            }
        });
        // 启动线程
        thread1.start();
        thread2.start();
        // 等待两个线程都执行完
        thread1.join();
        thread2.join();
        // 打印最终结果(预期2000)
        System.out.println("最终count值:" + count);
    }
    // 不加锁的累加方法
    private static void addWithoutSync() {
        count++;
    }
    // 加Synchronized的累加方法
    private static synchronized void addWithSync() {
        count++;
    }
}

这个例子能明显看出:Synchronized通过 “独占锁” 的机制,把多步操作 “打包” 成不可分割的原子操作,从而解决了多线程并发时的 “数据错乱” 问题。

可见性:线程释放锁时,会把本地内存里的变量 “同步” 到主内存;其他线程获取锁时,会从主内存重新读取变量。相当于强制刷新了共享变量的 “最新值”,避免线程拿旧数据干活。

有序性:Synchronized 会隐式禁止指令重排序(编译器 / CPU 为了提速,可能打乱代码执行顺序,但保证结果不变;但并发时可能出问题)。被它修饰的代码块,会按 “代码顺序” 执行,不用怕指令乱序导致的逻辑错。

用法:锁对象还是锁类?

Synchronized 的 “锁” 分两种,用的时候别搞混:

  • 对象锁:修饰普通方法(锁的是当前对象实例)、修饰代码块(synchronized(obj) { ... },锁的是括号里的 obj 对象)。 比如两个线程用同一个对象调用加锁的普通方法,会互斥;但用不同对象调用,各走各的,不互斥。
  • 类锁:修饰静态方法(锁的是类的 Class 对象)、修饰代码块时锁 “类名.class”(synchronized(A.class) { ... })。 整个类只有一个 Class 对象,所以不管用哪个实例调用,只要锁的是类锁,线程都会互斥。

冷知识:Synchronized 早不是 “重量级锁” 了!

很多人觉得 Synchronized “笨重”,其实 JVM 早就对它进行了优化!它会按 “竞争激烈程度” 自动切换锁状态,从 “轻” 到 “重”:

  • 偏向锁:无竞争时,谁先拿锁就 “偏向” 谁,下次直接用,不用再申请(省资源)。
  • 轻量级锁:轻微竞争时,用 CAS( Compare - and - Swap,比较并交换)自旋尝试拿锁,不用阻塞线程。
  • 重量级锁:竞争激烈时,才会升级成传统的 “阻塞锁”(依赖操作系统,开销大)。

Volatile,“轻量标记” 但有短板

核心作用:保可见保有序,唯独缺原子性

Volatile是 “轻量级同步” 关键字,只能修饰变量,能力比 Synchronized 弱:保证可见性、有序性,但不保证原子性

  • 可见性:和 Synchronized 类似,Volatile变量被修改后,会立刻 “刷” 到主内存;其他线程读的时候,会直接从主内存拿最新值,不会用本地缓存的旧值。 比如volatileboolean flag = false;,线程 A 改flag=true,线程 B 能立刻读到 “true”,不会一直卡在 “false” 的死循环里。
  • 有序性:通过 “内存屏障” 禁止指令重排序。比如修饰单例模式的instance时,能避免 “对象还没初始化完,就被其他线程拿到半成品” 的问题经典的 “双重检查锁” 里必须用Volatile
public class Singleton {
    // 必须用volatile修饰实例变量
    private static volatile Singleton instance;

    private Singleton() {} // 私有构造,防止外部实例化

    public static Singleton getInstance() {
        // 第一次检查:避免已创建实例后仍加锁
        if (instance == null) {
            // 加锁:保证同一时刻只有一个线程进入初始化逻辑
            synchronized (Singleton.class) {
                // 第二次检查:防止多线程等待锁时重复创建实例
                if (instance == null) {
                    // 若instance不加volatile,此处可能发生指令重排序
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 不保证原子性:这是高频坑!Volatile只能保证 “读、写单个变量” 是原子的,但如果是 “多步操作”(比如i++,拆成 “读 i→i+1→写 i”),它管不了。

适用场景:别拿它当锁用!

Volatile轻量(不用加锁解锁,开销小),但能力有限,适合这两种场景:

  • 状态标记:比如用volatile标记线程状态控制线程启停,线程 A 改状态,线程 B 立刻感知,不用频繁加锁。
  • 单例模式的 “安全发布”:双重检查锁里,volatileSingleton instance能避免指令重排序导致的 “半初始化对象” 问题。

关键对比:别再混淆这俩!

对照表格,一眼分清核心区别不同

​​​​​

速记技巧:面试直接说这 3 点

被问 “Synchronized 和Volatile的区别”,抓这 3 个核心就行:

  1. 作用范围:Synchronized 是 “全能选手”(原子、可见、有序全保),Volatile是 “半能选手”(不能保证原子性);
  2. 用法场景:复杂操作(多步)用 Synchronized,简单状态标记用Volatile(注意:这里说的是相对volatile而言,需要更高级锁功能、性能优化或复杂同步逻辑的情况可以使用ReentrantLock);
  3. 开销差异:Volatile更轻量(无锁),Synchronized 有锁机制(但 JVM 优化后不笨重)。

最后划重点

  • 关于线程安全问题。先想 “原子、可见、有序”;多步操作找 Synchronized,简单标记找Volatile;
  • 牢记:Volatile不保证原子性,一些操作需要加锁,比如i++;

​​​

搞定文章的这些内容,下次面试官再拿它们 “拷打”,直接把这些点抛回去 —— 既清晰又全面。​

#秋招##java##面试##互联网行业现在还值得去吗#
技术干货. 文章被收录于专栏

用简单的语言,带你掌握技术干货

全部评论

相关推荐

09-10 11:41
门头沟学院 Java
时间:41min岗位:软件开发问题:1.自我介绍2.我看你实习是测试开发,那么你想选择什么岗位呢?3.==和equals比较的是什么(==地址,equals不重写是地址,重写才是值)4.如果我有两个person类,用==比较的是啥?(地址)5.那么如果我里面有两个属性age和name,我比较的时候是比较值还是地址?(值啊,还反问我?)6.hashcode方法了解吗?在哪里用到?(hashmap用到)7.如果不主动去指定一个hashcode,那么将来会如何生成hashcode?(调用object的hashcode方法,具体由jvm提供)8.基本数据类型有哪些?9.平时写类里面的话用Integer还是int,如果就要写成int,能够编译通过吗?(不懂要问什么?)10.float和long,int占用内存大小?(有些犹豫,4,8,4)11.abstract关键字,平时如何使用?12.不是还有Interface吗?两者的选择如何考量?13.interface里可以有变量吗?(不能,只能是常量)14.Spring的生命周期?15.注入bean的时候,你有哪些注入的方法?16.具体底层的源码有了解过吗?怎么就能凭借一个注释就能注入?(我说的反射)17.具体有哪些反射?了解过Invoke方法吗?18.那在反射的那一步会注入呢?19.有哪些定时任务的框架?你项目为什么使用xxl-job,有哪些考量的地方?(还真没准备过)20.消息队列为什么使用rabbitmq,为什么不使用kafka?21.消息队列的数据一致性问题?多台机器怎么保证消费消息是一致性的呢?(不重复、按序消费、数据不丢失都说了说)22.CAP理论知道吗?具体消息队列是如何实现的?23.注册中心nacos具体是如何运作的?(服务注册、健康检测、发布订阅等)24.看过nacos的源码吗?没有看过的话,你作为一个实现者,你会如何实现这些基本功能?25.如果是一个集群,我怎么保证它不重复注册?(hash分片,然后结合set?)26.场景:如果我持续上传一些数据,如果离线了,如何把这些离线的数据重新上传系统?(持久化、断点续传、多个实例保证可用性?)27.算法题:力扣59螺旋矩阵28.反问?说这一轮面试通过了
查看27道真题和解析
点赞 评论 收藏
分享
评论
6
17
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务