Java多线程(03)—— 锁策略与锁升级

常见的锁策略

锁的策略是指:在加锁过程中,处理锁冲突的时候的处理方式;

1. 悲观锁 & 乐观锁

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁, 这样别⼈想拿这个数据就会阻塞直到它拿到锁;

乐观锁:假设数据⼀般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生 并发冲突进行检测,如果发现并发冲突了,则会返回用户错误的信息,让用户决定如何去做;

乐观锁,在加锁前预测锁冲突的概率不大,会少做一些工作,而悲观锁预测锁冲突的概率较大,会多做一些工作;

2. 重量级锁 & 轻量级锁

轻量级锁:加锁的开销更小,加锁的速度更快;一般轻量级锁是乐观锁;

重量级锁:加锁的开销更大,加锁的速度更慢;一般重量级锁是悲观锁;

3. 自旋锁 & 挂起等待锁

自旋锁(纯用户态):是轻量级锁的典型实现,在加锁时,如果加锁不成功,会再次尝试加锁,

while (抢锁(lock) == 失败) {}

反复如此,因此,当其他线程释放锁的时候,能第一时间拿到锁,同时自旋锁也是乐观锁,使用自旋锁的前提就是预期出现锁冲突的概率不大;

挂起等待锁(内核态 + 用户态):是重量级锁的典型实现,在加锁不成功的时候,会进入阻塞等待(等待系统内核调度,再次加锁时,就要花费更长的时间了),不再占用 cpu 资源,当其他线程释放锁的时候,再尝试加锁;挂起等待锁是悲观锁,用于锁冲突激烈的情况;

4. 普通互斥锁 & 读写锁

普通互斥锁:例如 synchronized,涉及加锁和解锁;

读写锁:在加锁的时候分为加读锁和加写锁;读锁和读锁之间不会阻塞,读锁和写锁之间会阻塞,写锁和写锁之间也会阻塞,即加读锁的时候只能读不能写,而加写锁的时候,既不能读也不能写;synchronized 不是读写锁,使用 synchronized 加锁,两个线程读,也会互斥;Java 标准库中提供了读写锁,在java.util.concurrent.locks 包下 ReentrantReadWriteLock

5. 公平锁 & 非公平锁

公平锁遵循 "先来后到" 的原则,若 A 线程对某个对象加锁了,B 线程先尝试对该对象加锁,则会进入阻塞等待,此时 C 线程后尝试对该对象加锁,C 也进入阻塞等待,当 A 释放锁之后,由于是 B 线程先来的,所以 B 线程会拿到锁;想要实现公平锁,就需要额外的数据结构,来记录线程的先后顺序;

非公平锁:与公平锁相反,不会遵循 "先来后到" 的原则,在上述例子中,A 线程释放锁之后,B 线程和 C 线程都可能会拿到锁; synchronized 是非公平锁

6. 可重入锁 & 不可重入锁

对于可重入锁和不可重入锁的概念 在这里,Java 中以 Reentrant 开头命名的锁都是可重入锁,并且 JDK 提供的所有现成的 Lock 的实现类,包括 synchronized 关键字锁都是可重入的,而 Linux 系统提供的 mutex 是不可重入锁;

对比    Java 中的 synchronized  Linux 中的 mutex
乐观 / 悲观                   自适应                           悲观锁
轻量级 / 重量级                   自适应        重量级锁
自旋 / 挂起等待                   自适应      挂起等待锁
是否为读写锁                不是读写锁      不是读写锁
公平 / 非公平                  非公平锁               非公平锁
可重入 / 不可重入                  可重入锁              不可重入锁

 

锁升级

synchronized 对于  乐观/悲观,轻量级/重量级,自旋/挂起等待 都是自适应的,什么是自适应呢 ?synchronized 会预测出现锁冲突的概率,来决定使用哪种锁策略;

当执行 synchronized 的时候,如果这个加锁对象处于未被加锁的状态,根据后续锁冲突的概率,就可能会经历以下过程:

偏向锁  ->  轻量级锁  -> 重量级锁       这个过程就是 锁升级

偏向锁:偏向锁的思想和懒汉模式类似,(看懒汉模式的详细讲解可以 点击这里)偏向锁不是真的加锁,只是做个标记,直到另一个线程准备对该对象加锁的时候,该线程会抢先一步,立刻加锁;

此时就升级为了轻量级锁(通过自旋锁来实现的),此时就是真的加锁了;当后续锁竞争更加激烈的时候,轻量级锁就会升级为重量级锁(通过挂起等待锁实现);

可以这样理解:偏向锁假设没有其他线程来竞争;轻量级锁:有线程来竞争,但不多;重量级锁:有线程来竞争并且很多;

目前只有锁升级的过程,没有锁退化的过程;

锁消除(了解)

锁消除只是在编译时存在的,属于编译器优化;

编译器在编译代码的时候,如果发现当前代码不需要加锁,就会自动把锁去掉;

锁粗化(了解)

锁粗化也属于编译器优化;

把多个细粒度的锁,合并为一个粗粒度的锁;synchronized 代码块里面的代码越少,粒度就越细,代码越多,粒度就越粗;

通常情况下,锁的粒度更细,更有利于多个线程并发执行;但有时也需要锁的粒度粗一点,例如在一段代码中,连续多次的对一个对象进行加锁,解锁操作(包含连续的多个 synchronized 代码块),此时编译器可能会将多个 synchronized 代码块合并为一个;

 

 

  • 25
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rcnhtin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值