Java锁 从乐观锁和悲观锁开始讲 面试复盘

目录

面试复盘

Java 中的锁 大全

悲观锁

专业解释

自我理解

乐观锁

专业解释

自我理解

悲观锁的调用

乐观锁的调用

synchronized和 ReentrantLock的区别

相同点

区别

详细对比

总结


面试复盘

Java 中的锁 大全

悲观锁

专业解释

适合写操作多的场景

先加锁可以保证写操作时数据正确

显式的锁定之后再操作同步资源

自我理解

悲观锁认为自己使用数据的时候一定有其他线程来修改数据

因此在获取数据的时候会选择先加锁

确保数据不会被别的线程修改

synchronized 和 Lock 的实现类都是悲观锁

乐观锁

专业解释

适合读操作多的场景

不加锁的特点能够使其读操作的性能大幅提升

乐观锁则能直接去操作同步资源

是一种无锁算法

自我理解

乐观锁认为自己在操作数据的时候不会有别的线程修改数据,所以不会加锁,所以他只会在自己操作数据的时候检查是否有其他线程修改更新的这个数据。

如果乐观锁去操作数据,这个数据没有更新的话。当前线程会直接将修改成功的数据写入,如果数据已经被其他线程更新了。要通过不同的实现方式进行不同操作。乐观锁在Java中是通过使用无锁编程来实现的,常用的是CAS算法。

Java原子类中的递增操作就是用CAS 自旋完成的

悲观锁的调用

import java.util.concurrent.locks.ReentrantLock;

public class OptiPessLockDemo {
    // 悲观锁的调用方式
    public  synchronized void m1(){
        // 加锁后的业务逻辑...
    }

    // 保证多个线程使用的是同一个lock对象的前提下
    ReentrantLock lock=new ReentrantLock();

    public void m2(){
        lock.lock();
        try {
            // 操作同步资源
        }finally {
            lock.unlock();
        }
    }
    // 两个都是悲观锁
}

假设在任何时候都可能发生冲突,因此,线程必须显式地获取锁以确保数据一致性和线程安全,直到它执行完毕并释放锁。

对于 synchronized,在方法级别加锁时,锁是针对该对象的,保证同一时刻只有一个线程能够执行该方法。因此,当一个线程在执行 m1() 方法时,其他线程不能同时执行同一个对象上的 m1() 方法。

  • synchronized 关键字的锁粒度是方法级别的,锁住的是整个方法。在方法执行期间,其他线程不能进入这个方法。
  • synchronized 实现的是隐式加锁和解锁,不需要显式地调用 lock()unlock()
  • ReentrantLock 是 Java 中的一个显式锁,它提供了比 synchronized 更细粒度的锁控制和更多功能。
  • ReentrantLock 通过 lock.lock() 显式地加锁,调用 lock.unlock() 来释放锁。通常,这两个操作会放在 try...finally 代码块中,以确保即使出现异常,锁也能得到释放。

乐观锁的调用

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

public class OptiPessLockDemo2 {
    // 乐观锁的调用方式
    private AtomicInteger atomicInteger = new AtomicInteger();
    atomicInteger.incrementAndGet();

    public static void main(String[] args) {
        int oldValue = atomicInteger.get();
        int newValue = oldValue + 1;
        // 如果值没有被修改,原子性操作成功
        return atomicInteger.compareAndSet(oldValue, newValue);
    }
}


这种方式在判断和更新之间,确保了只有一个线程能够成功更新值,其他线程则会重试或失败,从而保证了乐观锁的行为。

总之,乐观锁的核心是希望在操作时不加锁,直到最后验证冲突发生与否。如果有冲突,则可以通过重试、回滚等方式处理。

synchronizedReentrantLock的区别

synchronizedReentrantLock 都用于实现 悲观锁(Pessimistic Locking),即在多线程环境中对共享资源进行加锁,以保证线程安全。尽管两者实现的目标相同,它们在使用方式、灵活性、性能等方面有一些不同。下面我们来详细分析它们的 相同点区别

相同点

  1. 线程安全
    • 两者都能确保在多线程环境下,访问共享资源时,只有一个线程能够持有锁,防止多个线程同时修改共享数据,避免数据不一致。
  1. 互斥性
    • 两者都保证了同一时刻只有一个线程能够执行被保护的代码块或方法。其他线程必须等待当前线程释放锁后才能执行。
  1. 支持重入性
    • synchronizedReentrantLock 都是可重入的(Reentrant),即同一线程可以多次获取同一个锁,而不会发生死锁。
  1. 作用范围
    • 两者都可以应用于同步方法或同步代码块,保护共享资源。

区别

特性

synchronized

ReentrantLock

实现方式

隐式加锁,Java 编译器在编译时自动处理。

显式加锁,需要手动调用 lock()

unlock()

锁粒度

锁定整个方法或代码块,无法精确控制。

可以精确控制锁定的范围,允许更灵活的锁定操作。

性能

相对较低,特别是在高并发环境下,由于 JVM 的锁优化不足,可能导致性能瓶颈。

在高并发时,ReentrantLock

性能优于 synchronized

,尤其在锁竞争激烈时。

中断支持

不支持中断,线程获取锁时无法响应中断。

支持中断,可以使用 lockInterruptibly()

来在等待锁时响应中断。

公平性

非公平锁,线程不一定按照请求的顺序获取锁。

可以选择公平锁或非公平锁。使用构造函数 new ReentrantLock(true)

来创建公平锁。

死锁避免

需要小心死锁问题,synchronized

无法避免死锁。

通过 ReentrantLock

提供的 tryLock()

方法和超时机制可以更灵活地避免死锁。

锁释放机制

锁由 JVM 自动管理,方法执行完后自动释放。

必须手动调用 unlock()

释放锁,通常与 try...finally

语句配合使用。

可重入性

支持,可同一线程多次获取同一锁。

支持,可同一线程多次获取同一锁。

性能监控

无法直接获取锁的状态。

可以通过 getHoldCount()

获取当前线程持有锁的次数,进行监控。

锁升级

不支持锁的升级(无法从轻量级锁升级为重量级锁)。

可以通过锁的竞争情况动态升级为不同的锁类型(如偏向锁、轻量锁、重量锁)。


详细对比

  1. 锁的获取与释放
    • synchronized隐式加锁,锁的获取和释放是自动完成的。线程在执行被 synchronized 修饰的代码时,会自动获取该对象的锁,方法或代码块执行完后自动释放锁。
    • ReentrantLock显式加锁,必须手动调用 lock() 获取锁,必须手动调用 unlock() 释放锁。通常会配合 try...finally 语句使用,以保证在执行完业务逻辑后无论是否发生异常都能够释放锁。
  1. 中断响应
    • synchronized 在获取锁时无法响应中断。如果线程在等待锁的过程中被中断,它会继续等待,直到获取到锁。
    • ReentrantLock 提供了 lockInterruptibly() 方法,它允许在等待锁的过程中响应中断。线程在等待锁时,如果被中断,能够及时退出等待。
  1. 公平性
    • synchronized非公平锁,即任何线程在请求锁时,获取锁的顺序并不一定按照请求的顺序。可能先被调用的线程后获得锁,后调用的线程反而先获取到锁。
    • ReentrantLock 可以通过构造函数指定是否为 公平锁。如果设置为公平锁,锁会按照请求顺序分配给线程,先请求的线程会先获得锁。公平锁相对会增加一些性能开销,因此通常默认是非公平锁。
  1. 性能
    • 在低并发环境下,synchronized 的性能和 ReentrantLock 差不多,但在高并发环境下,ReentrantLock 会因为其灵活性和优化而表现得更好。
    • synchronized 锁的实现较为简单,但在高并发下可能存在 锁竞争 的问题,导致性能瓶颈。
    • ReentrantLock锁竞争 情况下提供了更多的优化方式,如通过自旋锁、CAS 等机制来减少线程的阻塞,从而提高性能。
  1. 死锁避免
    • synchronized 并没有提供直接的 API 来避免死锁。需要开发者自己通过代码设计来避免死锁问题。
    • ReentrantLock 提供了 tryLock() 方法,可以尝试获取锁,如果获取失败,可以选择放弃或重试。这种机制可以帮助开发者避免死锁或减少锁的等待时间。
  1. 锁的升级和监控
    • synchronized 锁的状态无法直接监控,只能通过 JVM 的内部调试工具进行查看。
    • ReentrantLock 提供了诸如 getHoldCount() 等方法来获取当前线程持有锁的次数,此外,还可以通过 getQueueLength() 来获取等待锁的线程数量,便于性能监控和分析。

总结

  • synchronized:适用于简单的同步需求,易于使用,代码简洁,但不够灵活,性能在高并发场景下可能有所下降。
  • ReentrantLock:功能更强大,提供了更多灵活性,如公平锁、中断响应、尝试加锁等,适用于复杂的多线程应用。但需要手动管理锁的获取与释放,使用上稍微复杂一些。

如果你的应用需求比较简单,且对性能要求不高,使用 synchronized 就足够了。如果你需要更多的控制、灵活性和对高并发场景的优化,ReentrantLock 会是更好的选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/955841.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OpenVela——专为AIoT领域打造的开源操作系统

目录 一、系统背景与开源 1.1. 起源 1.2. 开源 二、系统特点 2.1. 轻量化 2.2. 标准兼容性 2.3. 安全性 2.4. 高度可扩展性 三、技术支持与功能 3.1. 架构支持 3.2. 异构计算支持 3.3. 全面的连接套件 3.4. 开发者工具 四、应用场景与优势 4.1. 应用场景 4.2. …

使用 Java 实现基于 DFA 算法的敏感词检测

使用 Java 实现基于 DFA 算法的敏感词检测 1. 引言 敏感词检测在内容审核、信息过滤等领域有着广泛的应用。本文将介绍如何使用 DFA(Deterministic Finite Automaton,确定有限状态自动机) 算法,在 Java 中实现高效的敏感词检测。…

单片机存储器和C程序编译过程

1、 单片机存储器 只读存储器不是并列关系,是从ROM发展到FLASH的过程 RAM ROM 随机存储器 只读存储器 CPU直接存储和访问 只读可访问不可写 临时存数据,存的是CPU正在使用的数据 永久存数据,存的是操作系统启动程序或指令 断电易失 …

UDP报文格式

UDP是传输层的一个重要协议,他的特性有面向数据报、无连接、不可靠传输、全双工。 下面是UDP报文格式: 1,报头 UDP的报头长度位8个字节,包含源端口、目的端口、长度和校验和,其中每个属性均为两个字节。报头格式为二…

2024年我的技术成长之路

2024年我的技术成长之路 大家好,我是小寒。又到年底了,一年过得真快啊!趁着这次活动的机会,和大家聊聊我这一年在技术上的收获和踩过的坑。 说实话,今年工作特别忙,写博客的时间比去年少了不少。不过还是…

HTML5+Canvas实现的鼠标跟随自定义发光线条源码

源码介绍 HTML5Canvas实现的鼠标跟随自定义发光线条特效源码非常炫酷&#xff0c;在黑色的背景中&#xff0c;鼠标滑过即产生彩色变换的发光线条效果&#xff0c;且线条周围散发出火花飞射四溅的粒子光点特效。 效果预览 源码如下 <!DOCTYPE html PUBLIC "-//W3C//D…

爬虫第二篇

太聪明了怎么办&#xff1f;那就&#xff0c;给脑子灌点水&#xff01;&#xff01; 本篇文章我们来简单讲一下如何爬取mv,也就是歌曲视频&#xff0c;那么我们进入正题。 由于上次拿网易云开了刀&#xff0c;那么这次我们拿酷狗开刀。 还是进入上次讲过的页面 注意&#xff…

C#表达式和运算符

本文我们将学习C#的两个重要知识点&#xff1a;表达式和运算符。本章内容会理论性稍微强些&#xff0c;我们会尽量多举例进行说明。建议大家边阅读边思考&#xff0c;如果还能边实践就更好了。 1. 表达式 说到表达式&#xff0c;大家可能感觉有些陌生&#xff0c;我们先来举个…

Jira中bug的流转流程

Jira中bug的状态 1. 处理Bug的流程2. bug状态流转详述bug的状态通常包括 1. 处理Bug的流程 2. bug状态流转详述 bug的状态通常包括 未解决 1. 测试人员创建一个bug&#xff0c;填写bug的详细信息&#xff0c;如概要、bug级别、复现步骤、现状、预期结果等 2. 定位bug&#x…

快手极速版如何查找ip归属地?怎么关掉

在数字化时代&#xff0c;个人隐私的保护成为了广大用户关注的焦点。快手极速版作为一款备受欢迎的短视频应用&#xff0c;其IP归属地的显示与关闭功能自然也成了用户热议的话题。本文将详细介绍如何在快手极速版中查找IP归属地以及如何关闭IP属地显示&#xff0c;帮助用户更好…

BGP边界网关协议(Border Gateway Protocol)路由引入、路由反射器

一、路由引入背景 BGP协议本身不发现路由&#xff0c;因此需要将其他协议路由&#xff08;如IGP路由等&#xff09;引入到BGP路由表中&#xff0c;从而将这些路由在AS之内和AS之间传播。 BGP协议支持通过以下两种方式引入路由&#xff1a; Import方式&#xff1a;按协议类型将…

Solidity03 Solidity变量简述

文章目录 一、变量简述1.1 状态变量1.2 局部变量1.3 全局变量1.4 注意问题 二、变量可见性2.1 public2.2 private2.3 internal2.4 默认可见性2.5 可见性的用处 三、变量初始值3.1 值类型初始值 一、变量简述 变量是指可以保存数据的内部存储单元&#xff0c;里面的数据可以在程…

数据结构---并查集

目录 一、并查集的概念 二、并查集的实现 三、并查集的应用 一、并查集的概念 在一些实际问题中&#xff0c;需要将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元素集合&#xff0c;然后按一定的规律将归于同一组元素的集合…

STM32 FreeRTOS内存管理简介

在使用 FreeRTOS 创建任务、队列、信号量等对象时&#xff0c;通常都有动态创建和静态创建的方式。动态方式提供了更灵活的内存管理&#xff0c;而静态方式则更注重内存的静态分配和控制。 如果是1的&#xff0c;那么标准 C 库 malloc() 和 free() 函数有时可用于此目的&#…

构建core模块

文章目录 1.环境搭建1.sunrays-common下新建core模块2.引入依赖&#xff0c;并设置打包常规配置 2.测试使用1.启动&#xff01;1.创建模块2.引入依赖3.application.yml 配置MySQL和Minio4.创建启动类5.启动测试 2.common-web-starter1.目录2.WebController.java3.结果 3.common…

【Flink系列】6. Flink中的时间和窗口

6. Flink中的时间和窗口 在批处理统计中&#xff0c;我们可以等待一批数据都到齐后&#xff0c;统一处理。但是在实时处理统计中&#xff0c;我们是来一条就得处理一条&#xff0c;那么我们怎么统计最近一段时间内的数据呢&#xff1f;引入“窗口”。 所谓的“窗口”&#xff…

AIGC与劳动力市场:技术进步与就业结构的重塑

随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;尤其是生成式AI&#xff08;AIGC&#xff09;&#xff0c;劳动力市场正经历前所未有的变革。从内容创作到自动化生产线&#xff0c;几乎每个行业都在经历一场技术的洗礼。然而&#xff0c;这场革命并不是全然…

废品回收小程序,数字化回收时代

随着科技的不断创新发展&#xff0c;废品回收在各种技术的支持下也在不断地创新&#xff0c;提高了市场的发展速度&#xff0c;不仅能够让回收效率更加高效&#xff0c;还能够让居民更加便捷地进行回收&#xff0c;推动废品回收行业的发展。 回收市场机遇 目前&#xff0c;废…

题解 CodeForces 430B Balls Game 栈 C/C++

题目传送门&#xff1a; Problem - B - Codeforceshttps://mirror.codeforces.com/contest/430/problem/B翻译&#xff1a; Iahub正在为国际信息学奥林匹克竞赛&#xff08;IOI&#xff09;做准备。有什么比玩一个类似祖玛的游戏更好的训练方法呢&#xff1f; 一排中有n个球…

【Linux】线程全解:概念、操作、互斥与同步机制、线程池实现

&#x1f3ac; 个人主页&#xff1a;谁在夜里看海. &#x1f4d6; 个人专栏&#xff1a;《C系列》《Linux系列》《算法系列》 ⛰️ 道阻且长&#xff0c;行则将至 目录 &#x1f4da;一、线程概念 &#x1f4d6; 回顾进程 &#x1f4d6; 引入线程 &#x1f4d6; 总结 &a…