【JavaEE】锁策略

news2024/11/25 4:32:22

在这里插入图片描述

文章目录

  • 前言
  • 1. 乐观锁和悲观锁
  • 2. 重量级锁和轻量级锁
  • 3. 自旋锁和挂起等待锁
  • 4. 公平锁和非公平锁
  • 5. 可重入锁和非可重入锁
  • 6. 读写锁
  • Java synchronized 分别对应哪些锁策略
    • 1. 乐观锁和悲观锁
    • 2. 重量级锁和轻量级锁
    • 3. 自旋锁和挂起等待锁
    • 4. 公平锁和非公平锁
    • 5. 可重入锁和非可重入锁
  • 相关面试题

前言

在前面的多线程中,我们学习了为了解决线程不安全问题,使用 synchronized 为线程进行加锁,但是作为程序员光知道如何使用锁还不行,还需要知道有哪些锁策略。今天我将为大家分享在多线程中有哪些锁策略。

1. 乐观锁和悲观锁

悲观锁是一种基于悲观态度的锁机制,它假定最坏的情况,即在修改数据之前,它会先将数据锁住,阻止任何人对数据进行操作,直到它释放锁。这种锁的机制可以完全保证数据的独占性和正确性,因为每次请求都会先对数据进行加锁,然后进行数据操作,最后再解锁。然而,由于加锁和解锁的过程会造成消耗,所以这种策略的性能不高。

乐观锁则是一种基于乐观态度的锁机制,它假定不会发生数据冲突,只在提交操作的时候才锁定数据。这意味着,在数据提交之前,其他进程可以继续对数据进行操作。乐观锁可以实现并行操作,因此具有较高的性能。但需要注意的是,如果发生数据冲突,需要由数据库系统进行处理。

在这里插入图片描述

乐观锁和悲观锁都是计算机对后面发生事情的预测。悲观锁会觉得后面发生锁冲突的现象比较严重,所以在修改数据之前就会上锁;而乐观锁则觉得后面发生锁冲突的现象不严重,所以在处理数据之前就不会进行加锁。

给大家举个简单的例子:加入我们刚来到一个城市,周末的时候我想去体育馆打篮球,但是因为是刚来,不知道体育馆几点开门。那么这时候,悲观锁的做法就是:我现在家等着吧,问问朋友体育馆啥时候开门,但是朋友可能还在睡觉,所以我就只能在家等着;而乐观锁则是:”现在都8点了,体育馆应该开门了,我先过去,如果开门了就可以直接进去了,就算没开门,我也可以在体育馆外面等一会“。

2. 重量级锁和轻量级锁

重量级锁和轻量级锁是站在工作量的角度来划分的。

重量级锁基于操作系统的互斥量(Mutex Lock)而实现的锁,会导致进程在用户态和内核态之间切换,相对开销较大。例如,synchronized在内部基于监视器锁(monitor)实现,监视器锁基于底层的操作系统的Mutex Lock实现,因此在这种情况下synchronized属于重量级锁,重量级锁需要在用户态和核心态之间做转换。

  • 大量的内核态用户态切换
  • 很容易引发线程的调度

轻量级锁则是相对与重量级锁而言的,轻量级锁的核心设计实在没有多线程竞争的前提下,减少重量级锁的使用以提高系统性能。 轻量级锁适用于线程交替执行同步代码块的情况(既互斥操作),如果同一时刻与多个线程访问同一个锁,则将会导致轻量级锁膨胀为重量级锁。轻量级锁在发生线程竞争时,会让出 CPU 的执行权限,以便其他线程可以继续工作。

  • 少量的内核态用户态切换.
  • 不太容易引发线程调度

为什么重量级锁的用户态和内核态之间的转换效率会更低呢?

用户态的读写速率比内核态更慢。在内核态,CPU可以访问内存的所有数据,包括外围设备如硬盘、网卡等。同时,CPU也可以将自己从一个程序切换到另一个程序。而在用户态,程序只能受限地访问内存,且不允许访问外围设备。这种情况下,如果用户态的程序需要访问外围设备,如硬盘,那么它必须先切换到内核态,再由内核态进行系统调用来读写磁盘,这个过程会导致额外的开销。

为什么内核态操作与用户态的操作相比效率较低呢?

内核态的实现会占用内核稀缺的资源,例如操作系统需要维护线程列表,一旦操作系统装载后就无法动态改变。并且线程的数量远远大于进程的数量,随着线程数的增加,内核将耗尽,从而导致效率降低。此外,每次线程切换到内核态都需要陷入到内核,由操作系统来调度,这个过程也会花费一定的时间。

3. 自旋锁和挂起等待锁

自旋锁是一种轻量级锁,当一个线程尝试获取锁失败时,它会一直循环尝试获取锁,直到成功为止。这种机制消耗大量的CPU资源,因为它会使线程不断地尝试获取锁,无法做其他的工作。自旋锁只在加锁失败时进行忙等待,不会阻塞线程。

//这是一个自旋锁的伪代码
//当getLocker的返回结果为false的时候,表示未获取到锁,那么就继续循环
while(getLocker == false) {  
}

挂起等待锁是重量级锁的一种典型实现。当一个线程尝试获取锁失败时,它会通过内核的机制挂起等待,直到锁被释放。此时,线程会释放CPU资源,让其他线程可以执行。当锁被释放时,挂起的线程会重新尝试获取锁。挂起等待锁在等待期间会阻塞线程,导致线程无法做其他的工作。

//挂起等待锁,未获取到锁则是进入阻塞等待,等待内核态操作获取到锁
synchronized (locker) {
}

自旋锁虽然会消耗 CPU 资源,但换来的是更快的响应速度。

这两种锁的选用取决于具体的应用场景和需求。如果线程间的交互非常频繁,且锁被持有的时间比较短,那么自旋锁可能更合适。如果线程间的交互比较少,且锁被持有的时间比较长,那么挂起等待锁可能更合适。

当线程想要获取锁但是这个锁又被别的线程获取到的时候,挂起等待锁会进入阻塞等待状态,因为进入阻塞的线程什么时候被唤醒是个不确定因素,它是由内核态操作决定的,所以就会导致程序的执行速度下降;而自旋锁则不会进入阻塞等待状态,而是不断循环判断这个锁时候还被占有,如果这个锁还被占有,那么自旋锁还会继续循环,直到这个锁被释放,当这个锁被释放的时候该线程就可以获取到该锁,保证了整个操作都处于用户态的操作。

4. 公平锁和非公平锁

公平锁和非公平锁是两种常用的线程同步机制,用于在多线程环境下保护共享资源。

公平锁是指多个线程按照请求锁的顺序获取锁,即先到先得的原则。在公平锁中,如果有多个线程等待获取锁,那么锁会依次分配给等待时间最长的线程,这样可以避免线程饥饿的情况。公平锁的实现比较复杂,需要维护一个线程等待队列,因此性能会比较低。

非公平锁是指多个线程按照竞争获取锁的顺序获取锁,即先到不一定先得的原则。在非公平锁中,如果有多个线程等待获取锁,那么锁可能会直接分配给等待时间较短的线程,这样可能会导致一些线程一直无法获取锁,出现线程饥饿的情况。非公平锁的实现比较简单,不需要维护一个线程等待队列,因此性能会比较高。

5. 可重入锁和非可重入锁

可重入锁是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提是锁对象得是同一个对象),不会因为之前已经获取过锁还没有释放而阻塞。可重入锁的一个优点是可以避免死锁。

非可重入锁则是相反,线程获取锁后,内部不能再获取锁,由于之前已经获取过还没释放而阻塞,可能会导致线程死锁。

6. 读写锁

我们通常对数据的操作就是读操作和写操作,如果是单线程的话,读和写操作不会发生问题,但是如果发生在多线程当中的话就会出现一些问题,当我一个线程在读数据的时候,另一个线程在写这个数据,那么到最后读取的数据就与数据本身不一样;当两个线程都进行写操作的时候,最终的结果也不是正确的结果,而多个线程同时进行读操作的时候是不会发生问题的。也就是说只有多个线程同时进行读操作的时候才不会发生线程安全的问题,那么该如何解决读操作和写操作在多线程中出现的问题呢?

读写锁,当多个线程同时进行读操作的时候不会发生互斥的问题,如果多个线程同时进行读操作和写操作或者同时进行写操作的时候,读写锁就是互斥的,后面的线程就无法获取到这个锁。

读写锁的特点是:

  1. 读读不互斥:多个线程可以同时读取共享资源,因为读操作本身是线程安全的。
  2. 读写互斥:当有一个线程正在写共享资源时,其他线程不能进行读或写操作,因为读写操作是互斥的,以防止数据不一致或数据竞争。
  3. 写写互斥:当有两个或多个线程同时写共享资源时,会产生互斥现象,以防止数据互相干扰。

Java synchronized 分别对应哪些锁策略

1. 乐观锁和悲观锁

对于乐观锁和悲观锁来说, synchronized 属于自适应锁。synchronized 一开始属于乐观锁,但是如果计算机预测到后面发生锁冲突的现象较严重的话,synchronized 就会转变为悲观锁。

2. 重量级锁和轻量级锁

对于重量级锁和轻量级锁来说,synchronized 也是属于自适应锁。开始由于线程的工作量较小,synchronized 是轻量级锁,但是如果到后面线程需要处理的工作量较大的话,synchronized 又会转变为重量级锁。

3. 自旋锁和挂起等待锁

对于自旋锁和挂起等待锁来说,synchronized 属于自适应锁。当锁冲突的现象不严重的时候,synchronized 为自旋锁,但是如果锁冲突现象较严重的话,synchronized 又会转换为挂起等待锁。

4. 公平锁和非公平锁

synchronized 属于非公平锁,当多个线程尝试获取同一把锁的时候,synchronized 不管你先来后到的顺序,而是所有等待的线程竞争这把锁。

5. 可重入锁和非可重入锁

synchronized 属于可重入锁,当一个线程在外面获取到这个锁的时候,在内部也会自动获取到这个锁,而不会陷入死锁的状态。

synchronized 不属于读写锁。

相关面试题

1) 你是怎么理解乐观锁和悲观锁的,具体怎么实现呢?

悲观锁认为多个线程访问同一个共享变量冲突的概率较大, 会在每次访问共享变量之前都去真正加锁。

乐观锁认为多个线程访问同一个共享变量冲突的概率不大. 并不会真的加锁, 而是直接尝试访问数据. 在访问的同时识别当前的数据是否出现访问冲突.

悲观锁的实现就是先加锁(比如借助操作系统提供的 mutex), 获取到锁再操作数据. 获取不到锁就等待.

乐观锁的实现可以引入一个版本号. 借助版本号识别出当前的数据访问是否冲突.

2) 介绍下读写锁?

  • 读写锁就是把读操作和写操作分别进行加锁.
  • 读锁和读锁之间不互斥.
  • 写锁和写锁之间互斥.
  • 写锁和读锁之间互斥.
  • 读写锁最主要用在 “频繁读, 不频繁写” 的场景中.

3) 什么是自旋锁,为什么要使用自旋锁策略呢,缺点是什么?

如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止. 第一次获取锁失败, 第二次的尝试会在极短的时间内到来. 一旦锁被其他线程释放, 就能第一时间获取到锁.

相比于挂起等待锁,

优点: 没有放弃 CPU 资源, 一旦锁被释放就能第一时间获取到锁, 更高效. 在锁持有时间比较短的场景下非常有用.

缺点: 如果锁的持有时间较长, 就会浪费 CPU 资源

4) synchronized 是可重入锁么?

是可重入锁.

可重入锁指的就是连续两次加锁不会导致死锁.

实现的方式是在锁中记录该锁持有的线程身份, 以及一个计数器(记录加锁次数). 如果发现当前加锁的线程就是持有锁的线程, 则直接计数自增.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Scala第八章节

Scala第八章节 scala总目录 章节目标 能够使用trait独立完成适配器, 模板方法, 职责链设计模式能够独立叙述trait的构造机制能够了解trait继承class的写法能够独立完成程序员案例 1. 特质入门 1.1 概述 有些时候, 我们会遇到一些特定的需求, 即: 在不影响当前继承体系的情…

基于Java的图书管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

JavaScript系列从入门到精通系列第十篇:JavaScript中的相等运算符与条件运算符

文章目录 一:相等运算符 1: 2:! 3:与! (一): (二):! 二:条件运算符 1:语法 2:使用 3:容易挨打的写法 一:相等运算符 用于比较两个值是…

前端框架介绍

一、node.js 配置淘宝镜像源 npm config set registry https://registry.npm.taobao.org可以使用npm config list命令来确认镜像地址是否已成功更改。 如果需要将配置的镜像恢复为默认的npm官方源,可以执行以下命令: npm config delete registry二、vue 1、创建Vue项目 …

算法的时间复杂度分析习题专题

之前写了一篇重点是讲理论,今天重点在于对于题目的分析 题目难度不分先后,有题目来源会直接给出链接或者位置 第一题:消失的数字 题目来源:LeetCode消失的数字 分析 第一种思路分析: 参考代码: #include …

RTSP协议抓包及讲解

文章目录 前言一、RTSP 亲手搭建直播点播1、数据源为视频文件2、数据源为摄像头①、搭建 RTSP 流媒体服务器②、客户端拉流 二、RTSP 协议简介三、手撕 RTSP 协议1、Wireshark 抓包①、搭建环境②、wireshark 抓包 2、RTSP 交互流程①、OPTIONS②、DESCRIBE③、SETUP④、PLAY⑤…

全面横扫:dlib Python API在Linux和Windows的配置方案

前言 在计算机视觉和人工智能领域,dlib是一个备受推崇的工具库。它为开发者提供了强大的图像处理、机器学习和深度学习功能。在计算机视觉项目中,配置dlib Python API是一个重要的初始步骤。本文将引导读者详细了解在Linux和Windows系统上安装和配置dli…

【算法分析与设计】动态规划(下)

目录 一、最长公共子序列1.1 最长公共子序列的结构1.2 子问题的递归结构1.3 计算最优值1.4 举例说明1.5 算法的改进 二、最大子段和2.1 代码2.2 最大子段和问题的分治算法2.3 代码2.4 分治算法的时间复杂度2.5 最大子段和问题的动态规划算法 三、凸多边形最优三角剖分3.1 三角剖…

Flutter笔记:关于应用程序中提交图片作为头像

Flutter笔记 关于应用程序中提交图片作为头像 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_28550263/article/details/133418554…

raw智能照片处理工具DxO PureRAW mac介绍

DxO PureRAW Mac版是一款raw智能照片处理工具,该软件采用了智能技术,以解决影响所有RAW文件的七个问题:去马赛克,降噪,波纹,变形,色差,不想要的渐晕,以及缺乏清晰度。 Dx…

软件测试之单元测试自动化入门基础

单元测试自动化 所谓的单元测试(Unit Test)是根据特定的输入数据,针对程序代码中的最小实体单元的输入输出的正确性进行验证测试的过程。所谓的最小实体单元就是组织项目代码的最基本代码结构:函数,类,模块等。在Python中比较知名…

picoctf_2018_got_shell

picoctf_2018_got_shell Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)32位,只开了NX int __cdecl __noreturn main(int argc, const char **argv, const char **envp) {_DWOR…

DeepFace【部署 02】轻量级人脸识别和面部属性分析框架(实时分析+API+Docker部署+命令行接口)

轻量级人脸识别和面部属性分析框架 2.10 Real Time Analysis2.11 API2.12 Dockerized Service2.13 Command Line Interface 2.10 Real Time Analysis 你也可以运行deepface实时视频。流功能将访问您的网络摄像头,并应用面部识别和面部属性分析。如果能连续聚焦5帧&…

2023-9-29 LCR 083 全排列

题目链接&#xff1a;全排列 class Solution {int [] nums;List<List<Integer>> res new ArrayList<>();List<Integer> path;boolean[] st;public List<List<Integer>> permute(int[] nums) {this.nums nums;path Arrays.asList(new In…

DAMA-DMBOK2重点知识整理CDGA/CDGP——第14章 大数据与数据科学

目录 一、分值分布 二、重点知识梳理 1、引言 1.1 业务驱动因素 1.2 原则 1.3 基本理念 2、活动 2.1 定义大数据战略和业务需求 2.2 选择数据源 2.3 获得和接收数据源 2.4 制定数据假设和方法 2.5 集成和调整数据进行分析 2.6 使用模型探索数据 2.7 部署和监控 …

09链表-单链表移除元素

目录 链表&#xff08;Linked List&#xff09; 链表的数据结构 单链表 双链表 循环链表 链表的存储方式 删除节点 添加节点 LeetCode之路——203. 移除链表元素 分析&#xff1a; 链表&#xff08;Linked List&#xff09; 链表是一种线性数据结构&#xff0c;用于…

C运算符和控制语句

几乎每一个程序都需要进行运算&#xff0c;对数据进行加工处理&#xff0c;否则程序就没有意义了。要进行运算&#xff0c;就需规定可以使用的运算符。 C语言的运算符范围很宽&#xff0c;把除了控制语句和输人输出以外的几乎所有的基本操作都作为运算符处理。 运算符分类1 除…

Scala第六章节

Scala第六章节 scala总目录 章节目标 掌握类和对象的定义掌握访问修饰符和构造器的用法掌握main方法的实现形式掌握伴生对象的使用掌握定义工具类的案例 1. 类和对象 Scala是一种函数式的面向对象语言, 它也是支持面向对象编程思想的&#xff0c;也有类和对象的概念。我们依…

【Linux指令集】---git命令的基本使用

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【Linux专栏】&#x1f388; 本专栏旨在分享学习Linux的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 演示环境&#xff1…

Spring修炼之路(1)基础入门

一、简介 1.1Spring概述 Spring框架是一个轻量级的Java开发框架&#xff0c;它提供了一系列底层容器和基础设施&#xff0c;并可以和大量常用的开源框架无缝集成&#xff0c;可以说是开发Java EE应用程序的必备。Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器&…