「JavaEE」锁策略

🎇个人主页Ice_Sugar_7
🎇所属专栏JavaEE
🎇欢迎点赞收藏加关注哦!

🍉简介

加锁的过程中可能会出现冲突,这就会涉及到不同的处理方式,这些方式就称为锁策略


🍌乐观锁&悲观锁

这是锁的两种不同实现方式
对于乐观锁,它在加锁前预估出现锁冲突的概率不大,因此在加锁时不会做太多的工作。由于加锁过程做的事比较少,所以加锁速度可能会更快。当然,快就更容易引入一些其他的问题,比如消耗更多的 cpu 资源

而对于悲观锁,它在加锁之前预估出现冲突的概率比较大,在加锁时就会做更多的工作,因此加锁的速度可能更慢,但是整个过程就不容易出现其他问题


🍌轻量级锁&重量级锁

轻量级锁加锁的开销更小,加锁速度更快
重量级锁加锁的开销更大,加锁速度更慢

这两种锁和乐观锁、悲观锁只是所站角度不同,称呼才不一样。轻量重量是站在结果的角度,是对加锁结果的评价;而悲观乐观则是站在预测的角度,在加锁之前,对锁冲突的概率进行预估

本质上说的是同一件事,所以我们一般认为轻量级锁就是乐观锁,重量级锁就是悲观锁


🍌自旋锁&挂起等待锁

自旋锁是轻量级锁的一种典型实现
就是在加锁时搭配一个 while 循环。如果加锁成功,循环自然就会结束;反之就进行下一次循环,再次尝试获取锁。如果加锁一直没有成功,就会反复循环,而且循环速度很快,这个过程就称为“自旋”。这样,一旦其他线程释放锁,就能第一时间拿到锁
自旋锁也是乐观锁,使用自旋的前提就是预期锁冲突的概率不大,因为如果有多个线程参与竞争,那么自旋就很可能拿不到锁,就会白白浪费 cpu 的资源

而挂起等待锁是重量级锁的一种典型实现,因为在挂起等待的时候需要内核调度器介入,所以这一块要完成的操作就比较多,获取到锁要花费的时间也就更多一些


🍌普通互斥锁&读写锁

普通互斥锁就类似 synchronized,操作只涉及到加锁和解锁
使用 synchronized 有一个问题,当两个线程都进行读操作也会产生阻塞,这就导致性能会有一定的损失
所以引入了读写锁,它对加锁操作进行了细分,把加锁分成两种情况:

①加读锁:一个线程加读锁时,另一个线程只能读不能写
②加写锁:一个线程加写锁时,另一个线程既不能读,也不能写

引入读写锁之后,就可以省去并发读产生的锁冲突的开销,显著提升性能

注意:

  1. 读锁和读锁之间不会出现锁冲突(因为都只是读操作)
  2. 写锁和写锁之间、读锁和写锁之间都会出现锁冲突(就是会阻塞)

🍌公平锁&非公平锁

这里的“公平”,是只要遵循“先来后到”这一规则,就叫“公平”,这和我们常识上的“公平”不是一回事
系统原生的锁属于非公平锁,因为系统是随机调度线程的;synchronized 也是非公平的
想要实现公平锁,就需要引入额外的数据结构,比如引入队列记录每个线程的先后顺序


🍌synchronized 内部优化

Java 的 synchronized 具有自适应能力。也就是说它在某些情况下是乐观锁,某些情况下是悲观锁。至于是哪种锁,这取决于当前锁冲突的激烈程度,它内部会自动评估激烈程度。如果锁冲突激烈程度不大,那就是乐观锁;反之则是悲观锁

下面来了解一下 synchronized 的加锁过程,尤其是“自适应”是怎么回事

🥝锁升级

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

  1. 偏向锁阶段
    这个阶段的加锁思想就是非必要不加锁,能不加锁就不加锁,能晚一点加锁,就尽可能晚一点”(这有点像懒汉模式)
    所谓的偏向锁,并不是真的加锁了,而是只做了一个非常轻量的标记。如果全程都没有其他线程来竞争锁,那就会完全忽略加锁的过程,所以在没有竞争的情况下,偏向锁的效率比加锁高很多
    而一旦有其他线程来竞争这个锁,那这个线程就能在另一个线程加锁之前捷足先登,获取到锁,这样就会从偏向锁升级到轻量级锁

  2. 轻量级锁阶段
    一旦进入这个阶段,那就是真的加锁了,这个阶段是通过自旋锁的方式实现的。它的优势在于:其他线程释放锁之后,当前线程就可以第一时间拿到锁;不过劣势也很明显,就是比较消耗 cpu
    而与此同时,synchronized 内部也会当前这个锁对象上有多少个线程在参与竞争。如果发现竞争的线程比较多,那就会进一步升级为重量级锁(升级的意义在于降低 cpu 的消耗,因为对于自旋锁来说,如果同一个锁的竞争者很多的话,那大量的线程都在自旋,整体 cpu 的消耗就很大)

  3. 重量级锁阶段
    在这个阶段,拿不到锁的线程不会继续自旋,而是进入阻塞等待的状态,这样就可以让出 cpu,使 cpu 占用率不会太高。等到当前线程释放锁时,系统会随机唤醒一个线程来获取锁

注意:在当前 jvm 版本中,锁只能升级,而不能降级,就是说不能从轻量级锁降为偏向锁,不过可能未来某个版本就可以降级了


🥝锁消除

这也是 synchronized 内置的优化策略。编译器编译代码时,如果发现这个代码不需要加锁,就会自动把锁给干掉
举些例子,比如只有一个线程,然后在这个线程里面加锁,单线程的情况下没必要加锁,就优化掉
再比如,加锁代码中没有涉及到成员变量的修改,或者只是一些局部变量,这也无需加锁

不过锁消除还是比较保守的,只有当这个代码一眼看上去就知道完全不涉及线程安全问题时,才会把锁消除掉。而像其他一些比较复杂、模棱两可的情况,编译器不知道是否需要加锁,就不会去消除了


🥝锁粗化

synchronized {} 大括号里面包含的代码越少,就认为锁的粒度越细;反之则认为锁的粒度越粗
通常情况下,我们会倾向于让锁的粒度细一些,这样更有利于多个线程并发执行,但有时候是希望锁的粒度粗一点,因为每次加锁都可能阻塞,把多个细粒度的锁合并为一个粗粒度的锁,这样可以降低阻塞的可能性,从而提高效率

  • 89
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值