|
JavaEE
JavaEE——No.1 多线程案例
JavaEE——No.2 多线程案例
乐观锁
假设数据一般情况下不会产生并发冲突, 预测锁冲突的概率不高
, 因此做的工作就可以简单一点.
悲观锁
总是假设最坏的情况, 预测锁冲突的概率较高
, 因此做的工作就可以复杂一点.
普通互斥锁
就如同 synchronized
, 当两个线程同一把锁, 就会产生等待.
读写锁
- 读锁和读锁之间, 不会产生竞争
- 写锁和写锁之间, 有竞争
- 读锁和写锁之间, 有竞争
# 注意 #
在日常的需求下, 读的场景往往很多, 写的场景往往更少, 所以, 读写锁
相比普通的互斥锁, 就少了很多竞争, 优化了效率.
重量级锁
加锁解锁的开销比较大
. 典型的, 进入内核态 (内核比较忙碌) 的加锁逻辑, 开销较大.
轻量级锁
加锁解锁的开销比较小
. 典型的, 纯用户态的加锁逻辑, 开销较小.
- 乐观锁和悲观锁是站在加锁的过程上看待的, 加锁解锁过程中干的工作的多少.
- 而重量级锁和轻量级锁是站在结果的角度上看待的, 最终加锁解锁操作消耗的时间多少.
- 通常情况下, 乐观锁, 一般也比较轻量. 悲观锁, 一般比较重量. 但也不是绝对的.
自旋锁
轻量级锁的一种典型实现
反复
尝试获取锁, 直到获取到锁为止. 第一次获取锁失败, 第二次的尝试会在极短的时间内到来. 一旦锁被其他线程释放, 就能第一时间获取到锁.
挂起等待锁
重量级锁的一种典型实现
在等待锁的时间CPU
可以做一些其他的事情, 可能锁已经被其他线程释放, 但我没有发现. 获取到锁的时间没那么及时.
公平锁
遵守 "先来后到"
, B 比 C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁.
非公平锁
不遵守 "先来后到"
, B 比 C 先来的. 当 A 释放锁的之后, B 和 C 都有可能获取到锁.
# 注意 #
适用场景
.synchronized
是非公平锁.可重入锁
同一个线程针对同一把锁, 连续加锁两次, 不会
死锁
不可重入锁
同一个线程针对同一把锁, 连续加锁两次, 会
死锁
类似于懒汉模式, 必要时再加锁, 能不加, 就不加.
你给某个线程夹里, synchronized, 但是这个代码实际过程中, 真的会发生锁竞争吗 ? 不一定 ! !
- 既是乐观锁, 也是悲观锁
- 即使轻量级锁, 也是重量级锁
- 乐观锁的部分是基于自旋锁实现的; 悲观锁的部分是基于挂起等待锁实现的
- 不是读写锁, 是普通互斥锁
- 是非公平锁
- 是可重入锁
- 是偏向锁
synchronized 是自适应的
初始使用的时候, 是乐观锁/ 轻量级锁/ 自旋锁, 如果锁竞争不激烈, 就处于该状态不变.
如果锁竞争激烈, synchronized
就会自动升级成 悲观锁/ 重量级锁/ 挂起等待锁
锁膨胀
JVM 将 synchronized
锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。会根据情况,进行依次升级。
synchronized 加锁的具体过程
- 偏向锁
- 轻量级锁
- 重量级锁
如果当前场景中, 锁竞争不激烈, 则是以轻量级状态来工作. (自旋) , 第一时间拿到锁
如果当前场景中, 锁竞争激烈, 则是以重量级锁状态来进行工作的. (挂起等待) , 拿到锁没那么及时, 但是节省了 CPU
的开销.
锁消除
JVM 自动判定, 发现这个地方的代码, 不必加锁. 如果你写了 synchronized
, 就会自动把锁给去掉.
缩消除, 只是在编译器/ JVM 有十足把握的时候才进行的. (如果代码 100% 能消除, 编译器才会给你消除, 如果 80% 的可能能消除, 就不消除了)
锁粗化
锁的粒度, synchronized 对应代码块包含多少代码, 包含的代码少, 粒度细. 包含的代码多, 粒度粗.
锁粗化, 就是把细粒度的加锁 -> 粗粒度的加锁.
频繁的加锁解锁会带来一些额外的锁竞争, 锁粗化后就能节省开销.
# 注意 #
锁的粒度不是越粗越好, 要看具体的使用场景, 像上述反复加速的场景, 变粗了确实可以. 但是有时锁太粗不利于多线程并发, 有悖之前写多线程的初衷.
|
以上就是今天要讲的内容了,希望对大家有所帮助,如果有问题欢迎评论指出,会积极改正!!