Java常见的锁策略

ops/2024/11/29 3:42:16/

目录

  • Java常见的锁策略
    • 悲观锁和乐观锁
    • 轻量级锁和重量级锁
    • 自旋锁和挂起等待锁
    • 普通互斥锁和读写锁
    • 公平锁和非公平锁
    • 可重入锁和不可重入锁
    • Java中的synchronized算哪种情况?
    • 系统原生的锁算哪种情况?
    • synchronized的加锁过程,尤其是“自适应”是咋回事?
    • synchronized中内置的优化策略
      • 锁消除
      • 锁粗化
    • 小结

Java常见的锁策略

悲观锁和乐观锁

这是两种不同的锁的实现方式

  • 乐观锁,在加锁之前,预估当前出现锁冲突的概率不大,因此在进行加锁的时候就不会做太多的工作。

加锁过程做的事情比较少,加锁的速度可能就更快,但是是更容易引入一些其他的问题(但是可能会消耗更多的CPU资源)

  • 悲观锁,在加锁之前,预估当前锁冲突出现的概率比较大,因此加锁的时候,就会做更多的工作,

做的事情更多,加锁的速度可能更慢,但是整个过程中不容易出现其他问题。

轻量级锁和重量级锁

  • 轻量级锁,加锁的开销小,加锁的速度更快—>轻量级锁,一般就是乐观锁
  • 重量级锁,加锁的开销更大,加锁速度更慢---->重量级锁,一般也就是悲观锁

轻量重量,加锁之后,对结果的评价。

悲观乐观,是加锁之前,对未发生的事情进行的预估

整体来说,这两种角度,描述的是同一个事情。

自旋锁和挂起等待锁

  • 自旋锁就是轻量级锁的一种典型实现

进行加锁的时候,搭配一个while循环,如果加锁成功,自然循环结束。如果加锁不成功,不是阻塞放弃CPU,而是进行下一次循环,再次尝试获取到锁。

这个反复快速执行的过程,就称为“自旋”,一旦其他线程释放了锁,就能第一时间拿到锁。

同时,这样的自旋锁,也是乐观锁。使用自旋的前提,就是预期锁冲突概率不大,其他线程释放了锁,就能第一时间拿到。

万一当前加锁的线程特别多,自旋意义就不大了,白白浪费CPU了。

  • 挂起等待锁,就是重量级锁的一种典型实现,同时也是一种悲观锁。

进行挂起等待的时候,就需要内核调度器介入了,这一块要完成的操作就多了,真正获取到锁要花的时间就更多一些了。

这个锁可以适用于锁冲突激烈的情况

普通互斥锁和读写锁

  • 普通互斥锁:类似于synchronized(操作涉及到 加锁 和 解锁)
  • 读写锁:这里的读写锁,把加锁分成两种情况:

1)加读锁

2)加写锁

读锁和读锁之间,不会出现锁冲突(不会阻塞)

写锁和写锁之间,会出现锁冲突(会阻塞)

读锁和写锁之间,会出现锁冲突(会阻塞)

一个线程加读锁的时候,另一个线程,只能读,不能写

一个线程加写锁的时候,另一个线程,不能写,也不能读

为啥要引入读写锁???

如果两个线程读,本身就是线程安全的!不需要进行互斥!

如果使用synchronized这种方式加锁,两个线程读,也会产生互斥,产生阻塞(对于性能有一定的损失)

完全给读操作不加锁,也不行,就怕一个线程读一个线程写,可能会读到写了一半的数据

读写锁,就可以很好的解决上述问题。

实际开发中,读操作本身就是非常频繁的,非常常见的

读写锁就能把这些并发读之间的锁冲突的开销给省下了,就对于性能提升很明显了

公平锁和非公平锁

注意,此处定义的 ”公平“,遵循先来后到,才叫公平!

Java中的synchronized就是非公平的(也就是没有按先后顺序)

要想实现公平锁,就需要引入额外的数据结构(引入队列,记录每个线程先后顺序)才能实现公平锁。(能记录先后顺序的)

使用公平锁,天然就可以避免线程饿死的问题

非公平锁:就是每个线程等概率竞争,不遵循先来后到

可重入锁和不可重入锁

一个线程针对这一把锁,连续加锁两次,不会死锁,就是 可重入锁;会死锁,就是不可重入锁

synchronized是可重入锁。

系统自带的锁,是不可重入的锁。

可重入锁需要记录持有锁的线程是谁,加锁的次数的计数

Java中的synchronized算哪种情况?

synchronized具有自适应能力!!

synchronized在某些情况下是 乐观锁/轻量级锁/自旋锁,某些情况下是 悲观锁/重量级锁/挂起等待锁

内部会自动的评估当前锁冲突的激烈程度。

如果当前锁冲突的激烈程度不大,就处于 乐观锁/轻量级锁/自旋锁

如果当前锁冲突的激烈程度很大,就处于 悲观锁/重量级锁/挂起等待锁

不是读写锁

非公平锁

可重入锁

synchronized内部优化地非常好,大部分情况下使用synchronized都是不会有啥问题的(无脑用

系统原生的锁算哪种情况?

对于系统原生的锁(Linux提供的mutex这个锁)

1.悲观锁

2.重量级锁

3.挂起等待锁

4.不是读写锁

5.非公平锁

6.不可重入锁

synchronized的加锁过程,尤其是“自适应”是咋回事?

当线程执行到synchronized的时候,如果这个对象当前处于未加锁的状态,就会经历以下过程:

1.偏向锁阶段

核心思想,“懒汉模式”,能不加锁,就不加锁,能晚加锁,就晚加锁

所谓的偏向锁,并非真的加锁了,而只是做了一个非常轻量的标记

搞暧昧,就是偏向锁,只是做了一个标记。没有真加锁(也不会有互斥)

一旦有其他线程,来和我竞争这个锁,就在另一个线程之前,先把锁获取到

从偏向锁就会升级到轻量级锁(真加锁了,就有互斥了)

如果我搞暧昧的过程中,要是没人来竞争,整个过程就把加锁这样的操作就完全省略了

非必要不加锁。

在遇到竞争的情况下,偏向锁没有提高效率

但是如果在没有竞争的情况下,偏向锁就大幅度的提高了效率。

总的来说,偏向锁意义还是很大的。

2.轻量级锁阶段

(假设有竞争,但是不多)

此处就是通过自旋锁的方式来实现的

优势:另外的线程把锁释放了,就会第一时间拿到锁

劣势:比较消耗CPU

于此同时,synchronized内部也会统计,当前这个锁对象上,有多少个线程在参与竞争

这里当发现参与竞争的线程比较多了

就会进一步升级到重量级锁

对于自旋锁来说,如果同一个锁竞争很多

大量的线程都在自旋,整体CPU的消耗就很大了

3.重量级锁阶段

此时拿不出锁的线程就不会继续自旋了,而是进入“阻塞等待”

就会让出CPU了(不会使CPU占用率太高)

当当前线程释放锁的时候,就由系统随机唤醒一个线程来获取锁了

此处锁 只能 升级,不能降级,自适应这个词,严格的说不算很严谨(但是,保不齐未来某个版本就能降级了)

synchronized中内置的优化策略

锁消除

编译器编译代码的时候,如果发现这个代码,不需要加锁,就会自动把锁给干掉

这里的优化是比较保守的,针对一眼看上去就完全并不涉及线程安全问题的代码,能够把锁消除掉

锁粗化

会把多个细粒度的锁,合并一个粗粒度的锁

synchronized{}大括号里面包含的代码越少,就认为锁的粒度越细

包含的代码越多,就认为锁的粒度越粗

在这里插入图片描述

在这里插入图片描述

小结

synchronized背后涉及到了很多的“优化手段”

1.锁升级。偏向锁–>轻量级锁–>重量级锁
2.锁消除。自动干掉不必要的锁
3.锁粗化。把多个细粒度的锁合并成一个粗粒度的锁,减少锁竞争的开销

这些机制都是在内部,在看不到的地方默默发挥作用的


http://www.ppmy.cn/ops/137550.html

相关文章

【软件入门】Git快速入门

Git快速入门 文章目录 Git快速入门0.前言1.安装和配置2.新建版本库2.1.本地创建2.2.云端下载 3.版本管理3.1.添加和提交文件3.2.回退版本3.2.1.soft模式3.2.2.mixed模式3.2.3.hard模式3.2.4.使用场景 3.3.查看版本差异3.4.忽略文件 4.云端配置4.1.Github4.1.1.SSH配置4.1.2.关联…

Leetcode - 周赛424

目录 一,3354. 使数组元素等于零 二, 3355. 零数组变换 I 三,3356. 零数组变换 II 四,3357. 最小化相邻元素的最大差值 一,3354. 使数组元素等于零 本题实际上是一个前/后缀和的问题,就是判断前缀和与后…

Tri Mode Ethernet MAC IP核详解

本文对 Vivado 的三速 MAC IP 核(Tri Mode Ethernet MAC,TEMAC)进行介绍。 在自行实现三速以太网 MAC 控制器时,GMII/RGMII 接口可以通过 IDDR、ODDR 原语实现,然而实际使用中自己实现的模块性能不是很稳定&#xff08…

单片机电路基本知识

单片机电路基本知识 MCU(C51) 概念:应用实例家用电子,汽车电子,嵌入式系统,低成本,低功耗,小型化,通常使用c语言或者汇编语言,用于家用电器控制,智能家居,汽…

使用flink编写WordCount

1. env-准备环境 2. source-加载数据 3. transformation-数据处理转换 4. sink-数据输出 5. execute-执行 流程图&#xff1a; DataStream API开发 //nightlies.apache.org/flink/flink-docs-release-1.13/docs/dev/datastream/overview/ 添加依赖 <properties>&l…

C语言超详细教程

系列文章目录 文章目录 系列文章目录1 运算符1.1 算术运算符:2 控制语句2.1 条件语句:2.2 循环语句:3 函数3.1 函数的定义与声明:3.2 递归函数:4 指针4.1 指针的定义与使用函数指针:5. 数组与字符串5.1 数组一维数组:相同类型元素的集合(如:多维数组:数组的数组(如:…

【多线程-第一天-多线程的技术方案-pthread演示 Objective-C语言】

一、多线程的技术方案 1.我们来看一下多线程的技术方案 技术方案 pthread:一套通用的多线程API、适用于Unix\Linux Windows等系统、跨平台、可移植、使用难度大、C语言、线程的生命周期由程序员管理、使用频率:几乎不用 NSThread:使用更加面向对象、简单易用、可直接操作…

macos 14.0 Monoma 修改顶部菜单栏颜色

macos 14.0 设置暗色后顶部菜单栏还维持浅色&#xff0c;与整体不协调。 修改方式如下&#xff1a;