【JavaEE】锁策略

news/2024/5/17 20:21:42 标签: java, 开发语言, 多线程, synchronized,

在这里插入图片描述

文章目录

前言

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

1. 乐观和悲观

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

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

在这里插入图片描述

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

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

2. 重量级和轻量级

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

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

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

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

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

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

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

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

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

3. 自旋和挂起等待

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

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

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

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

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

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

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

4. 公平和非公平

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

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

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

5. 可重入和非可重入

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

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

6. 读写

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

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

读写的特点是:

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

synchronized__87">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.niftyadmin.cn/n/5060153.html

相关文章

SPSS列联表分析

前言: 本专栏参考教材为《SPSS22.0从入门到精通》,由于软件版本原因,部分内容有所改变,为适应软件版本的变化,特此创作此专栏便于大家学习。本专栏使用软件为:SPSS25.0 本专栏所有的数据文件可在个人主页—…

[题]宝物筛选 #单调队列优化

五、宝物筛选&#xff08;洛谷P1776&#xff09; 题目链接 好家伙&#xff0c;找到了一个之前学习多重背包优化时的错误…… 之前记的笔记还是很有用的…… #include<bits/stdc.h> using namespace std; const int N 1e5 10; int f[N]; int n, m; int v, w, s; int l…

二十,镜面IBL--打印BRDF积分贴图

比起以往&#xff0c;这节应该是最轻松的了&#xff0c; 运行结果如下 代码如下&#xff1a; #include <osg/TextureCubeMap> #include <osg/TexGen> #include <osg/TexEnvCombine> #include <osgUtil/ReflectionMapGenerator> #include <osgDB/Re…

【中秋】ijoc论文20230929

**1.DiversiTree: A New Method to Efficiently Compute Diverse Sets of Near-Optimal Solutions to Mixed-Integer Optimization Problems DiversiTree&#xff1a;一种有效计算混合整数优化问题的各种近最优解集的新方法** Izuwa Ahanor , Hugh Medal , Andrew C. Trapp 伊…

认识Oracle

当你想要深入了解 Oracle 数据库时&#xff0c;你进入了一个激动人心的领域。Oracle 是全球最大的关系型数据库管理系统 (RDBMS) 提供商之一&#xff0c;它在企业级数据库领域拥有着强大的地位。本文将带你探索 Oracle 数据库的重要性、特点和一些基本概念&#xff0c;以帮助你…

k8s搭建EFK日志系统

搭建 EFK 日志系统 前面大家介绍了 Kubernetes 集群中的几种日志收集方案&#xff0c;Kubernetes 中比较流行的日志收集解决方案是 Elasticsearch、Fluentd 和 Kibana&#xff08;EFK&#xff09;技术栈&#xff0c;也是官方现在比较推荐的一种方案。 Elasticsearch 是一个实…

LabVIEW风力涡轮机的雷电流测量系统中集成高速摄像机

LabVIEW风力涡轮机的雷电流测量系统中集成高速摄像机 随着全球风电装机容量的快速增长&#xff0c;雷电活动对风力发电机组造成的损害受到更多关注&#xff0c;特别是在雷电活动强烈的地区。在冬季闪电期间&#xff0c;风力涡轮机等高层结构会受到向上的雷击。众所周知&#x…

计算机网络笔记 第一章 概述

课程链接 https://www.bilibili.com/video/BV1c4411d7jb/?spm_id_from333.337.search-card.all.click 1.2 因特网概述 网络、互联网与因特网的区别与关系 若干节点和链路互相形成网络若干网络通过路由器互联形成互联网因特网是当今世界上最大的互联网 我们有时并没有严格区…