当前位置: 首页 > article >正文

多线程之旅:锁策略

前面的文章中呢,小编介绍到synchronized这样的一个关键字。

这个关键字呢,是Java推荐使用一个锁来的,而这对于锁的内容呢,其实还是有很多值得分享的。

所以,现在小编来继续分享下关于锁的内容。

比如,今天要分享的锁策略。

那么这个锁策略是比较广泛的内容,包括不限于锁的类型、锁的粒度、锁的实现方式、锁的公平性等。

那么介绍一下锁的类型以及锁实现方式和公平性吧。

锁策略

其实常见的锁类型是也是挺多的。

比如以下:

乐观锁  vs 悲观锁

轻量级锁 vs 重量级锁

公平锁     vs  非公平锁

挂起等待锁   vs  自选锁

可重入锁     vs    不可重入锁

读写锁

那么接下来一一解释它们究竟是何方神圣吧

乐观锁   vs   悲观锁

乐观锁:假设并发冲突很少发生,此时,当更新数据的时候,此时才会检查是否是有冲突

如若是有冲突,那么一般选择是重试或放弃操作,亦或者交给用户来处理。

悲观锁:假设并发冲突是频繁发生的,此时呢,没有更新数据之前,就可以加锁,从而导致其他线程无法访问该资源。

举个例子:

小编去约女孩子看电影。

此时会有两种情况

小编假设这个女孩子是挺忙,可能不一定有空和我一起去看。

那么我会这样先发消息说:你在忙嘛?我下午2点钟约你看电影行不?(相当于加锁操作了)

如若是不忙的,并得到肯定答复后,那么此时这个女孩子就会和我一起看电影了。

如若是很忙的情况下,那小编会等下一段时间,再去和 这个女孩子确定时间。

那么这个就是悲观锁的体现

另一种情况呢

小编假设这个女孩子是不忙的,所以小编是直接找到女孩子了,然后就问她可不可以一起看电影,如若是真的不忙,还答应了,那就一起去了,此时如果是忙的,那么就下次再来(我们是没有加锁的,但是此时也是可以得到访问冲突了)

这就是乐观锁的体现。

而对于Java的synchronized来说

它是自适应的.什么意思呢?

即开始之初是为一个乐观锁,等到锁竞争较为频繁的时候,就会自动切换为悲观锁。

轻量级锁 和 重量级锁

这个轻量级锁是乐观锁的其中一种

重量级锁是悲观锁的其中一种。

轻量级锁做的工作较少,加锁的开销较小,往往是在乐观锁的情况下的。

重量级锁做的工作较多,加锁的开销较大,往往是在悲观锁的情况下的。

同样的,synchronized对于这个也是自适应的。

挂起等待锁 和 自旋锁

这个挂起等待锁也是一个悲观锁/重量级锁的典型实现

自旋锁是一个乐观锁/轻量级锁的典型实现。

当线程获取锁失败的时候,此时线程就会阻塞(挂起),并释放CPU资源

等到锁被释放了,操作系统就会唤醒等待的锁,去竞争锁,这就是挂起等待锁。

显然,有这样的优点:

线程挂起后不占用CPU资源,减少CPU的浪费,适合高竞争的场景

缺点嘛也比较明显

在线程挂起中,它是设计到内核态和用户态的切换,开销较大

响应较慢,毕竟是要系统唤醒。

当线程获取锁失败的时候,线程不是进入阻塞,而是通过循环(自旋)状态不断的尝试获取锁。

这就是自旋锁。

优点嘛,比较明显

就是响应速度较快,毕竟没有挂起。

适合锁竞争不激烈,有锁但时间较短的场景下。

缺点嘛,同样也是比较明显的

自旋期间占用较高的CPU资源。此时可能导致CPU资源的浪费

在synchronized中,对此自旋锁也是自适应的,并在java1.6后就引入了。

公平锁 vs 非公平锁

公平锁:遵循“先来后到”原则,即谁先来,谁先等待,那么锁资源就会分配给谁

非公平锁:就是不遵循“先来后到”原则,即每个线程都有机会获取到锁。

举个例子

就比如,一个非常漂亮的妹子,即使有男朋友,此时身边还是有很多追求者,

例如A(追求两年)、B(追求了1年)、C(追求半年)

此时呢,这个漂亮的妹子分手了,那么接下来的追求者就有机会了。

那么如若这个女生按照公平锁这样的话,

此时A就上位了,毕竟A先来,并且还追求了两年

那么如若是这个女生按照非公平锁这样的话

那么此时A、B、C就都有机会,那么A、B、C就会出现“抢夺”这个女生的情况。

在java中,synchronized,它是非公平锁。

可重入锁  vs  不可重入锁

可重入锁:即允许同一个线程多次获取同一把锁而不会发生死锁

不可重入锁:即不允许同一个线程多次获取同一个锁。

Java里的synchronized是可重入锁。

读写锁

这个读写锁,它加锁的时候分两个情况

一个给读加锁

一个给写加锁

同时呢,这个读写锁也提供两种api,一个是加写锁、一个是加读锁。

但是解锁的api是一样的。

读写锁的出现,是为了应对这样的情况

情况:

一个线程读数据,一个线程写数据

两个线程都在写数据,此时呢,这两种情况有可能会出现多线程安全。

如若,一个线程加读锁,一个线程加写锁,会产生冲突

后者两个线程都加了写锁,此时呢也是会产生冲突的。

Java中的synchronized,它不是读写锁。

ok,那么一些锁策略类型,就介绍到这。

由此可见,java的synchronized是一个类型丰富的锁

1.可重入锁

2.非公平锁

3.乐/悲观锁          自适应

4.轻/重量级锁        自适应

5.自选挂起等待锁     自适应

那么这个自适应,到底是怎么自适应呢?

那么就不得不说synchronized的加锁过程了

synchronized加锁过程

1.锁升级

什么又是锁升级呢?

显然就是由小到大,由轻到重。

这个过程如下:

当使用这个synchronized进行加锁的时候,

此时会处于“偏向锁”状态

那这个偏向锁是什么呢?

它是对正在使用锁的线程进行一个标记,不会涉及真正的加锁,毕竟加锁操作也是会消耗一定资源的。

当线程之间出现竞争,就会升级为“轻量级锁”

继续统计竞争频率,频率上升到一定地步,那么又会升级成一个“重量级锁”

值得注意的是,这个锁升级的过程,它不是可逆的,只能一步步变“大”,变“重”

所以,这个锁升级过程可以表示为:

无锁==》偏向锁==》轻量级锁==》重量级锁

2.锁消除

那什么又是锁消除呢?

锁消除其实一种编译器优化策略。

就是编译器对你写出的代码,进行一定的判定,看看哪里不需要加锁。

如若是本来就是一种单线程环境的话,就不会涉及到多线程安全,此时呢,就会把锁给撤掉。

3.锁粗化

那么什么又是锁粗化呢?

其实呢,这个锁粗化也是一种编译器优化策略。

但是,理解这个锁粗话之前,先理解下锁的粒度。

锁的粒度就是指:一个锁控制代码块或其数据的大小

简单理解为:锁的代码块越大,粒度也就越粗。

但要值得注意的是,这个代码块的代码量,要考虑其引入的方法等。

回到锁粗化中。

锁粗化就是指,把多个细粒度的锁,合并成一个粗粒度的锁。

所以这个synchronized加锁过程中,会涉及到锁升级、锁消除、锁粗化等一些操作。

ok,关于锁策略呢,小编分享到这。


http://www.kler.cn/a/564534.html

相关文章:

  • 使用DeepSeek/chatgpt等AI工具辅助网络协议流量数据包分析
  • 大语言模型概念科普
  • 计算机毕业设计 ——jspssm510springboot 的人职匹配推荐系统
  • uniapp vue3实现的一款数字动画调节器件,支持长按、单点操作,提供丝滑的增减动画效果
  • Ecode前后端传值
  • 3 年→ 资深开发速通计划 序言
  • AndroidManifest.xml文件的作用
  • VSCode轻松调试运行.Net 8.0 Web API项目
  • 前端TypeScript 面试题及参考答案
  • leetcode 214. 最短回文串
  • 本地部署语言大模型deepseek完整步骤
  • SheetDataMerge合并工作表(excel)内多行同类数据的小工具。
  • C语言基础之【指针】(上)
  • 快速排序与归并排序模板
  • 微信小程序换行符真机不生效问题
  • DeepSeek再推开源力作,DeepEP高效通信库来袭
  • ES6模块化详解:导入与导出方式
  • 空中机械臂仿真问题
  • Java List实现类面试题
  • 华为AP 4050DN-HD的FIT AP模式改为FAT AP,家用FAT基本配置