JavaEE进阶(1)——锁与synchronized

本文开始深度解析 线程底层的逻辑原理

1.常见锁策略

当我们需要自定义锁(标准库提供的锁 不够用) ——需要关注锁策略

一般情况下 synchronized 足矣覆盖绝大多数使用场景

锁策略 并不是和Java强相关,其他语言凡是涉及到并发编程,涉及到锁,都可以谈这样的锁策略

锁策略 可以简单认为是 ——这个锁在加锁的过程中 有什么特点 什么行为

1.1 悲观锁 乐观锁

描述的是加锁时 遇见的场景

不是针对某一种具体的锁,而是某个具体锁 具有“悲观” 特性或“乐观”特性

  • 悲观:加锁的时候,预测接下来的锁竞争的情况非常激烈,需要针对这样的激烈情况做一些额外的工作
  • 乐观:加锁的时候,锁竞争情况不激烈,不需要做额外工作

例如:

有一把锁 二十个线程尝试获取锁,每个线程加锁的频率都很高,一个线程加锁的时候,很可能锁被一个线程占用——悲观锁

有一把琐 假设只有两个线程线程尝试获取这把锁,每个线程加锁的频率都很低,一个线程加锁的时候,大概率另一个线程没有与其竞争——乐观锁

1.2 重量级锁 轻量级锁

遇到场景之后的解决方案

重量级锁,当悲观的场景下,此时就要付出更多的代价——更低效

轻量级锁,应对乐观的场景,此时付出的代价就会更小——更高效

1.3 挂起等待锁 自旋锁

挂起等待锁——重量级锁的典型实现

=> 操作系统内核级别,加锁的时候发现竞争,就会使该线程进入阻塞状态,后续就需要内核进行唤醒

自旋锁——轻量级锁的典型实现

=>应用程序级别,加锁的时候发现竞争,一般也不是进入阻塞,而是通过忙等的形式进行等待

(乐观锁的场景 本身遇见锁竞争的概率就很小,如果真的遇见竞争,在短时间内就能拿到锁)

例如 追女神 发现女神有对象 选择等待 有两种等待方式

1.等自己眼中的女神(竞争不激烈) =>乐观

等待女神,隔三岔五进行联系,会消耗一定CPU,过不了多久,就能有机会,只要发现机会就能上位,整个锁等待时间不长

即——获取锁的周期更短,及时获取锁,等待过程一直消耗CPU

2.等 公认的女神(竞争激烈)=>悲观

等待过程中不再联系,专心自己的事情,很长时间过去了,偶然听说女神单身的消息(此时女神可能分手好几次了),这个过程,不在女神身上消耗精力

即——获取锁的周期更长,很难及时获取锁,但这个过程不用一直消耗CPU,可以让CPU处理别的事情

悲观锁=>重量级锁=>挂起等待锁

乐观锁=>轻量级锁=>自旋锁

对于synchronized来说,属于自适应锁         JVM内部,会统计每个锁竞争的激烈情况

如果竞争不激烈,此时synchronized 就会按照 轻量级锁(自旋锁)

如果竞争很激烈,此时synchronized 就会按照 重量级锁(挂起等待锁)

1.4 普通互斥锁 读写锁

普通互斥锁——synchronized 加锁 解锁

读写锁—— 读方式加锁 写方式加锁 解锁

多线程读取一个数据,线程本身是安全的

多个线程读取,一个线程修改数据,肯定涉及线程安全问题

由于 服务器开发中,读多写少的场景十分常见

大部分操作在读,少部分操作在 写——一旦给 读 写都加上普通互斥锁,锁冲突会很严重

   读写锁——确保 读锁和读锁之间不会互斥(不发生阻塞)

                                写锁和写锁之间 有互斥

                                写锁和读锁之间也有互斥

可以保证线程安全的前提下,降低锁冲突概率,提高效率

1.5 可重入锁 不可重入锁

synchronized 是“可重入锁”——一个线程一把锁,连续加锁多次,是否会死锁

核心要点:

  1. 锁要记录当前哪个线程持有这把锁
  2. 使用计数器,记录当前加锁次数,在合适的时候解锁

1.6 公平锁 非公平锁

synchronized是非公平锁

当女神和男朋友分手 谁上位才是公平的呢?

1.按照先来后到的顺序

2.概率均等

对锁来说,默认情况是概率均等,操作系统 对线程的调度是随机的

要想实现公平锁,需要付出额外的东西,

比如需要使用一个队列,记录一下,各个线程获取锁的顺序

1.7小结

对 synchronized 来说 属于

  • 自适应的
  • 普通互斥锁(不是读写锁) 
  • 可重入锁
  • 非公平锁

2. synchronized的锁升级过程

自适应过程(单向)

无锁=>偏向锁=>自旋锁=>重量级锁

偏向锁是指,进入synchronized后,并没有真的加锁,——是一种懒汉模式的体现

先简单做一个轻量标记,相比于加锁解锁 效率更高

  • 如果没有其他线程竞争此锁,最终当线程执行到解锁代码时 也只是清除标记,(不涉及真正的加锁解锁)
  • 如果有其他线程竞争此锁,就抢先一步,宣誓主权,真加锁,偏向锁=>轻量锁 其他线程只能等待

升级情况:

代码进入synchronized 的代码块—— 无锁=>偏向锁

拿到偏向锁的线程运行过程,发现其他线程尝试竞争这个锁——偏向锁=>自旋锁

JVM发现当前竞争锁的情况非常激烈——自旋锁=>重量级锁

当前JVM只提供“锁升级” 不能“锁降级”

3.锁消除

是编译器优化的一种体现,编译器会判断,当前代码逻辑是否真的需要加锁,如果确实不需要,会自动忽视synchronized

锁消除是比较保守的,没有100%的把握是不会去除的

4.锁粗化

锁的粒度

加锁和解锁之间,包含的代码越多,认为锁的粒度越粗

如果包含代码越少,则认为锁的粒度越细

此处代码指的不是代码行数,而是实际执行的指令/时间

一个代码中,反复针对细粒度代码加锁,可能会被优化更粗粒度的代码加锁

例如给领导汇报工作,一共三件事,如果没有提前组织好语言,打一次电话汇报一件事情,一共打三次电话才汇报完毕,领导都厌烦了。

打电话的时候,领导不能做别的事情,全身心投入到汇报中,

如果没有锁粗化,是一种低效率浪费资源的做法

如图示,将三处细粒度代码 优化成一段粗粒度代码

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值