一次说清,Java 中的各种锁和 经典面试题

Java中有关锁的面试题(部分) 死锁发生时,多个线程相互等待,导致程序无法继续执行。,并附带详细解答,希望能帮助你准备面试!这些是 Java 并发锁的常见面试题,涉及。是指线程在获取锁时不会立即阻塞,而是。Java 中关于锁的面试题经常涉及。JVM 默认自旋 10 次(可通过。:若竞争激烈,会浪费 CPU 资源。,熟练掌握后能应对大多数面试!:减少线程切换,提高性能。 阅读详情
AI摘要

本文深入探讨Java中各种锁机制,包括乐观锁、悲观锁、公平锁、非公平锁、独占锁、共享锁、可重入锁、自旋锁及CAS,通过实例演示共享锁的使用,并解析ABA问题及其解决方案。

如果说快速理解多线程有什么捷径的话,那本文介绍的各种锁无疑是其中之一,它不但为我们开发多线程程序提供理论支持,还是面试中经常被问到的核心面试题之一。因此下面就让我们一起深入地学习一下这些锁吧。

乐观锁和悲观锁

悲观锁和乐观锁并不是某个具体的“锁”而是一种并发编程的基本概念。乐观锁和悲观锁最早出现在数据库的设计当中,后来逐渐被 Java 的并发包所引入。

悲观锁

悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观地认为,不加锁的并发操作一定会出问题。

乐观锁

乐观锁正好和悲观锁相反,它获取数据的时候,并不担心数据被修改,每次获取数据的时候也不会加锁,只是在更新数据的时候,通过判断现有的数据是否和原数据一致来判断数据是否被其他线程操作,如果没被其他线程修改则进行数据更新,如果被其他线程修改则不进行数据更新。

公平锁和非公平锁

根据线程获取锁的抢占机制,锁又可以分为公平锁和非公平锁。

公平锁

公平锁是指多个线程按照申请锁的顺序来获取锁。

非公平锁

非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。ReentrantLock 提供了公平锁和非公平锁的实现。

  • 公平锁:new ReentrantLock(true)

  • 非公平锁:new ReentrantLock(false)

如果构造函数不传任何参数的时候,默认提供的是非公平锁。

独占锁和共享锁

根据锁能否被多个线程持有,可以把锁分为独占锁和共享锁。

独占锁

独占锁是指任何时候都只有一个线程能执行资源操作。

共享锁

共享锁指定是可以同时被多个线程读取,但只能被一个线程修改。比如 Java 中的 ReentrantReadWriteLock 就是共享锁的实现方式,它允许一个线程进行写操作,允许多个线程读操作。

ReentrantReadWriteLock 共享锁演示代码如下:

public class ReadWriteLockTest {
    public static void main(String[] args) throws InterruptedException {
        final MyReadWriteLock rwLock = new MyReadWriteLock();
        // 创建读锁 r1 和 r2
        Thread r1 = new Thread(new Runnable() {
            @Override
            public void run() {
                rwLock.read();
            }
        }, "r1");
        Thread r2 = new Thread(new Runnable() {
            @Override
            public void run() {
                rwLock.read();
            }
        }, "r2");
        r1.start();
        r2.start();
        // 等待同时读取线程执行完成
        r1.join();
        r2.join();
        // 开启写锁的操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                rwLock.write();
            }
        }, "w1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                rwLock.write();
            }
        }, "w2").start();
    }
    static class MyReadWriteLock {
        ReadWriteLock lock = new ReentrantReadWriteLock();
        public void read() {
            try {
                lock.readLock().lock();
                System.out.println("读操作,进入 | 线程:" + Thread.currentThread().getName());
                Thread.sleep(3000);
                System.out.println("读操作,退出 | 线程:" + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
            }
        }
        public void write() {
            try {
                lock.writeLock().lock();
                System.out.println("写操作,进入 | 线程:" + Thread.currentThread().getName());
                Thread.sleep(3000);
                System.out.println("写操作,退出 | 线程:" + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
            }
        }
    }
}

以上程序执行结果如下:

读操作,进入 | 线程:r1

读操作,进入 | 线程:r2

读操作,退出 | 线程:r1

读操作,退出 | 线程:r2

写操作,进入 | 线程:w1

写操作,退出 | 线程:w1

写操作,进入 | 线程:w2

写操作,退出 | 线程:w2

可重入锁

可重入锁指的是该线程获取了该锁之后,可以无限次的进入该锁锁住的代码。

自旋锁

自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗 CPU。

CAS 与 ABA

CAS(Compare and Swap)比较并交换,是一种乐观锁的实现,是用非阻塞算法来代替锁定,其中 java.util.concurrent 包下的 AtomicInteger 就是借助 CAS 来实现的。

但 CAS 也不是没有任何副作用,比如著名的 ABA 问题就是 CAS 引起的。

ABA 问题描述

老王去银行取钱,余额有 200 元,老王取 100 元,但因为程序的问题,启动了两个线程,线程一和线程二进行比对扣款,线程一获取原本有 200 元,扣除 100 元,余额等于 100 元,此时阿里给老王转账 100 元,于是启动了线程三抢先在线程二之前执行了转账操作,把 100 元又变成了 200 元,而此时线程二对比自己事先拿到的 200 元和此时经过改动的 200 元值一样,就进行了减法操作,把余额又变成了 100 元。这显然不是我们要的正确结果,我们想要的结果是余额减少了 100 元,又增加了 100 元,余额还是 200 元,而此时余额变成了 100 元,显然有悖常理,这就是著名的 ABA 的问题。

执行流程如下。

  • 线程一:取款,获取原值 200 元,与 200 元比对成功,减去 100 元,修改结果为 100 元。

  • 线程二:取款,获取原值 200 元,阻塞等待修改。

  • 线程三:转账,获取原值 100 元,与 100 元比对成功,加上 100 元,修改结果为 200 元。

  • 线程二:取款,恢复执行,原值为 200 元,与 200 元对比成功,减去 100 元,修改结果为 100 元。

最终的结果是 100 元。

ABA 问题的解决

常见解决 ABA 问题的方案加版本号,来区分值是否有变动。以老王取钱的例子为例,如果加上版本号,执行流程如下。

  • 线程一:取款,获取原值 200_V1,与 200_V1 比对成功,减去 100 元,修改结果为 100_V2。

  • 线程二:取款,获取原值 200_V1 阻塞等待修改。

  • 线程三:转账,获取原值 100_V2,与 100_V2 对比成功,加 100 元,修改结果为 200_V3。

  • 线程二:取款,恢复执行,原值 200_V1 与现值 200_V3 对比不相等,退出修改。

最终的结果为 200 元,这显然是我们需要的结果。

在程序中,要怎么解决 ABA 的问题呢?

在 JDK 1.5 的时候,Java 提供了一个 AtomicStampedReference 原子引用变量,通过添加版本号来解决 ABA 的问题,具体使用示例如下:

String name = "老王";
String newName = "Java";
AtomicStampedReference<String> as = new AtomicStampedReference<String>(name, 1);
System.out.println("值:" + as.getReference() + " | Stamp:" + as.getStamp());
as.compareAndSet(name, newName, as.getStamp(), as.getStamp() + 1);
System.out.println("值:" + as.getReference() + " | Stamp:" + as.getStamp());

以上程序执行结果如下:

值:老王 | Stamp:1

值:Java | Stamp:2

相关面试题

1. synchronized 是哪种锁的实现?为什么?

答:synchronized 是悲观锁的实现,因为 synchronized 修饰的代码,每次执行时会进行加锁操作,同时只允许一个线程进行操作,所以它是悲观锁的实现。

2. new ReentrantLock() 创建的是公平锁还是非公平锁?

答:非公平锁,查看 ReentrantLock 的实现源码可知。

/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
public ReentrantLock() {
    sync = new NonfairSync();
}
3. synchronized 使用的是公平锁还是非公平锁?

答:synchronized 使用的是非公平锁,并且是不可设置的。这是因为非公平锁的吞吐量大于公平锁,并且是主流操作系统线程调度的基本选择,所以这也是 synchronized 使用非公平锁原由。

4. 为什么非公平锁吞吐量大于公平锁?

答:比如 A 占用锁的时候,B 请求获取锁,发现被 A 占用之后,堵塞等待被唤醒,这个时候 C 同时来获取 A 占用的锁,如果是公平锁 C 后来者发现不可用之后一定排在 B 之后等待被唤醒,而非公平锁则可以让 C 先用,在 B 被唤醒之前 C 已经使用完成,从而节省了 C 等待和唤醒之间的性能消耗,这就是非公平锁比公平锁吞吐量大的原因。

5. volatile 的作用是什么?

答:volatile 是 Java 虚拟机提供的最轻量级的同步机制。当变量被定义成 volatile 之后,具备两种特性:

  • 保证此变量对所有线程的可见性,当一条线程修改了这个变量的值,修改的新值对于其他线程是可见的(可以立即得知的);

  • 禁止指令重排序优化,普通变量仅仅能保证在该方法执行过程中,得到正确结果,但是不保证程序代码的执行顺序。

6. volatile 对比 synchronized 有什么区别?

答:synchronized 既能保证可见性,又能保证原子性,而 volatile 只能保证可见性,无法保证原子性。比如,i++ 如果使用 synchronized 修饰是线程安全的,而 volatile 会有线程安全的问题。

7. CAS 是如何实现的?

答:CAS(Compare and Swap)比较并交换,CAS 是通过调用 JNI(Java Native Interface)的代码实现的,比如,在 Windows 系统 CAS 就是借助 C 语言来调用 CPU 底层指令实现的。

8. CAS 会产生什么问题?应该怎么解决?

答:CAS 是标准的乐观锁的实现,会产生 ABA 的问题(详见正文)。ABA 通常的解决办法是添加版本号,每次修改操作时版本号加一,这样数据对比的时候就不会出现 ABA 的问题了。

9. 以下说法错误的是?

A:独占锁是指任何时候都只有一个线程能执行资源操作 B:共享锁指定是可以同时被多个线程读取和修改 C:公平锁是指多个线程按照申请锁的顺序来获取锁 D:非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁

答:B

题目解析:共享锁指定是可以同时被多个线程读取,但只能被一个线程修改。

总结

本文介绍了 Java 中各种锁,明白了 Java 程序中比较常用的为非公平锁而非公平锁,原因在于非公平锁的吞吐量要更大,并且发生线程“饥饿”的情况很少,是风险远小于收益的事所以可以广而用之。又重点介绍了 CAS 和著名的 ABA 的问题,以及解决 ABA 的常见手段:添加版本号,可以通过 Java 自身提供的 AtomicStampedReference(原子引用变量)来解决 ABA 的问题,至此我们对 Java 多线程的了解又向前迈了一大步。


即日起至 3 月 16 日,热销专栏《Java 面试全解析:核心知识点与典型面试题》返场 限时特惠!10w 字核心知识精讲、500+ 面试题目精练,现在订阅专栏,开始收割大厂 Offer!

订阅专栏,即可进群与作者交流
java中锁的面试题 synchronized关键字:表示“同步”的。它可以对“多行代码”进行“同步”——将多行代码当成是一个完整的整体,一个线程如果进入到这个代码块中,会全部执行完毕,执行结束后,其它线程才会执行。这样可以保证这多行的代码作为完整的整体,被一个线程完整的执行完毕。synchronized被称为“重量级的锁”方式,也是“悲观锁”——效率比较低。b.同步方法:Lock锁也称同步锁,加锁与释放锁方法化了,如下::加同步锁。:释放同步锁。synchronized 是JVM层面的锁,是Java关键字,通过monitor对 阅读详情

相关推荐

【Java面试题汇总】多线程、JUC、锁篇(2026版)

线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC

种一棵树最好的时间是十年前,其次是现在 1万+

「JUC并发编程」初识CAS锁(概述、底层原理、原子引用、自旋锁、缺点)

CAS的全称为,直译就是对比交换。是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值。经过调查发现,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口。简单解释:CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。...

frozenpenguin的博客 7426

5分钟看懂LTE注网全流程:从PLMN选择到小区驻留的log分析技巧

本文深入解析了LTE终端从开机到成功驻留小区的完整流程,重点分享了基于QXDM和QCAT工具的log分析实战技巧。文章详细拆解了PLMN选择、System/Band扫频、小区搜索与同步、系统消息解析及驻留等关键步骤,并提供了高频问题场景的排查思路,帮助网络优化工程师快速定位终端侧、射频环境或网络配置问题。

app77的博客 434

CAS锁机制(无锁、自旋锁、乐观锁、轻量级锁)

CAS锁机制(无锁、自旋锁、乐观锁、轻量级锁)

user2025的博客 2万+

瑞芯微RK3576开发板实战:6TOPS NPU如何加速你的AIoT项目(附性能测试)

本文以瑞芯微RK3576开发板为核心,深入探讨其6TOPS NPU在AIoT项目中的实战应用。通过详细的性能测试对比,展示了NPU如何将AI模型推理速度提升15倍以上,并大幅降低CPU负载。文章涵盖从环境搭建、模型转换优化到多屏异显与系统级调优的全流程,为开发者提供了将硬件算力转化为实际项目加速效果的完整指南。

oauth7security的博客 821

使用RepeatModeler和RepeatMasker注释基因组重复序列

重复序列注释有两种常用策略,基于同源序列相似性和基于重复序列结构特征。RepeatMasker是基于同源序列相似性注释序列的常用工具, RepeatModeler可用来从头对基因组的重复序列家族进行建模注释,它的核心组件是RECON和RepatScout。 这篇教程介绍如何使用RepeatModeler从头鉴定基因组的重复序列,之后用RepeatMasker根据自...

xuzhougeng blog 8854

一次说清,Java 中的各种锁和 CAS 经典面试题

如果说快速理解多线程有什么捷径的话,那本文介绍的各种锁无疑是其中之一,它不但为我们开发多线程程序提供理论支持,还是面试中经常被问到的核心面试题之一。因此...

形势所逼,不进则退 241

ORB-SLAM3在windows11下的编译使用

近期在学习SLAM,想部署一下ORB-SLAM3,但是自己电脑是win11系统,因此就想着在win11上部署一下。但是网上看了一些教程,有一些博客,但是可能不适合我这种情况把,就很纠结。先说下结果,配置成功,过程极其痛苦。

weixin_44457768的博客 6097

为什么synchronized是非公平锁_一次说清,Java 中的各种锁和 CAS 经典面试题

如果说快速理解多线程有什么捷径的话,那本文介绍的各种锁无疑是其中之一,它不但为我们开发多线程程序提供理论支持,还是面试中经常被问到的核心面试题之一。因此下面就让我们一起深入地学习一下这些锁吧。乐观锁和悲观锁悲观锁和乐观锁并不是某个具体的“锁”而是一种并发编程的基本概念。乐观锁和悲观锁最早出现在数据库的设计当中,后来逐渐被 Java 的并发包所引入。悲观锁悲观锁认为对于同一个数据的并发操作...

weixin_39873208的博客 1811

Ubuntu系统下USB无线网卡无法识别的解决方案

Realtek的USB设备可能是无线网卡、有线网卡、读卡器等,系统仅知道“这是Realtek的USB设备”,但不知道“如何将其作为无线网卡驱动”。通过以上步骤,RTL8812AU 芯片的 Edimax 无线网卡应能被 Ubuntu 识别并正常工作。Realtek等品牌的无线网卡可能被系统简单识别为"USB设备",但未匹配到对应的无线驱动,导致设备无法发挥网络功能。是系统识别了USB设备,却未正确加载无线网卡驱动。(前4位是厂商ID,后4位是产品ID),显示设备已识别,但网络管理工具无反应,

极地星辰 3466

Python+Selenium实现抖音博主批量监控:300+账号实时更新通知(附完整代码)

本文详细介绍了如何使用Python和Selenium构建一个高可用的抖音博主批量监控系统。通过架构设计、Cookie管理、高效轮询与差异检测等核心模块,实现300+账号的实时更新追踪与通知。文章提供了完整的代码实现,并重点探讨了性能优化、异常处理及服务器部署等实战经验,帮助开发者打造稳定、可控的自动化监控解决方案。

weixin_29274969的博客 196

cas修改数据源之后404_一次说清,Java 中的各种锁和 CAS 经典面试题

如果说快速理解多线程有什么捷径的话,那本文介绍的各种锁无疑是其中之一,它不但为我们开发多线程程序提供理论支持,还是面试中经常被问到的核心面试题之一。因此下面就让我们一起深入地学习一下这些锁吧。乐观锁和悲观锁悲观锁和乐观锁并不是某个具体的“锁”而是一种并发编程的基本概念。乐观锁和悲观锁最早出现在数据库的设计当中,后来逐渐被 Java 的并发包所引入。悲观锁悲观锁认为对于同一个数据的并发操作...

weixin_39895167的博客 82

程序进入hardfault之后查找原因的方法

如果在MCU运行程序的时候,进入了hardfault,该怎么办?

smart_boy__的博客 761

Java 中的各种锁和 CAS + 面试题

Java 中的各种锁和 CAS + 面试题 如果说快速理解多线程有什么捷径的话,那本文介绍的各种锁无疑是其中之一,它不但为我们开发多线程程序提供理论支持,还是面试中经常被问到的核心面试题之一。因此下面就让我们一起深入地学习一下这些锁吧。 乐观锁和悲观锁 悲观锁和乐观锁并不是某个具体的“锁”而是一种并发编程的基本概念。乐观锁和悲观锁最早出现在数据库的设计当中,后来逐渐被 Java 的并发包所引入。 ...

快乐的工程师的博客 4292

论文研读 Corundum: An Open-Source 100-Gbps NIC

Corundum 通过在 FPGA 上构建并行 pipeline、高效 DMA 通道、无阻塞队列结构、低延迟调度器,实现了在 PCIe Gen3 x16 接口上稳定跑满 100Gbps 吞吐量。

Double Galaxy 489

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

java中的锁面试题

synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。锁的升

libaowenBlog的博客 1万+

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

java中的各种锁和CAS经典面试题

java中的各种锁和CAS经典面试题 为什么需要了解锁知识呢?因为在高并发下带来的线程安全性问题,需要以锁的方式解决,保障数据的安全准确,理解各种锁知识,为我们开发多线程程序提供理论支持。同时也是面试中常问的知识点。 乐观锁和悲观锁 乐观锁和悲观锁并不是某个具体的锁,而是一种并发编程的概念。乐观锁最早出现在数据库的设计当中,后来逐渐被java并发包所引入。 悲观锁:悲观锁认为同一个数据的并发操作,...

weixin_40375656的博客 434

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

面试题——Java中的锁

文章目录1、synchronized 关键字是怎么用的?1.1 构造方法可以使用 synchronized 关键字修饰么?1.2 使用 String 作为锁对象,会有什么问题?1.3 synchronized 的底层原理有了解吗?1.4 还使用过其他锁吗?2、volatile 有什么作用?2.1 原理是什么?2.2 和 synchronized 有什么区别?3. 写一个单例模式 1、synchronized 关键字是怎么用的? synchronized 关键字解决的是多个线程之间访问资源的同步性,sync

Q_P777的博客 3364

Java中的21种锁

本篇主要内容如下:本篇主要内容本篇文章已收纳到我的Java在线文档、 Github我的SpringCloud实战项目持续更新中。

tangjieqing的博客 2505

java面试题-mysql中的 锁 你知道多少?

但是既然是锁,就会导致效率低下,所以共享锁的实现就是事务A读取order表的时候,把order表进行锁表,这时候别人都无法修改表中的任何数据。当然这些是平时项目中最常遇到的锁,还有一些比如:页级锁、间隙锁、自旋锁等等,不怎么常见,甚至不能显式的用SQL表达,后面会单独开篇文章记录其他几种锁,平时的面试如果能回答上来上述的也基本可以了。乐观锁的上面也说了他只是一种思想,msyql中的锁实现都是悲观锁。悲观锁也是一种思想,与乐观锁不同的是他的事务一开始就会加了锁,而乐观锁实际上是没有锁的概念,只有版本号。

qq_36178165的博客 1360
上一篇: Python 全栈工程师必备面试题 300 道(2020 版)
下一篇: Python 又㕛叒叕拿第一,凭啥? | 12月编程语言排行
软件供应链安全
软件供应链安全 领域专家: 后端开发技术领域 领域专家: 后端开发技术领域
博客等级 码龄9年 1万+粉丝 347原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值