多线程八股文之锁模式及优化策略

本文详细探讨了Java中的锁策略,包括悲观锁、乐观锁、重量锁与轻量锁、自旋锁与挂起等待锁,以及可重入锁、公平锁和互斥/读写锁。重点介绍了synchronized的实现原理,如升级策略、锁消除和锁粗化的过程。
摘要由CSDN通过智能技术生成

目录

一、锁策略

1.悲观锁、乐观锁

2.重量锁、轻量锁

3.自旋锁、挂起等待锁

4.可重入锁、不可重入锁

5.公平锁、非公平锁

6.互斥锁、读写锁

二、sychronized实现原理

1.升级策略

2.锁消除

3.锁粗化


一、锁策略

什么是锁策略,也就是根据不同的场景,设置不同的锁。这个锁应该要适应当前的场景,比如,这个锁是否要加重、是否可重入等等。

1.悲观锁、乐观锁

这是一个相对的锁策略,在加锁的时候,会预测当前这个锁的所冲突是大或者小,就会根据大或小决定锁的策略。

(1)悲观锁预测到当前锁冲突概率大,后续要做的工作往往就很多,所以加锁的开销就更大(包括时间、系统资源等等)。

(2)乐观锁:预测当前锁冲突概率不大,后续就不需要做很多的工作,加锁开销就更小。

(3)像我们常使用的synchronized锁,支持自适应,也就是既属于悲观锁,也属于乐观锁。

synchronized会根据当前锁冲突的情况做出判断。当冲突概率低的时候,就会按照乐观锁的方式来执行,效率也就更快;当冲突的概率高时,就会按照悲观锁的方式来执行,效率更慢。

2.重量锁、轻量锁

这一组锁和上述的悲观、乐观和像,但是定义概念的出发点不同。

(1)重量锁:在加锁过程中,做的事情多

(2)轻量锁:在加锁的过程中,做的事情少

(3)synchronized既属于重量锁,又属于轻量锁。

3.自旋锁、挂起等待锁

这一组锁,也是和上述的锁有关联,可以说是在上述锁的基础上

(1)自旋锁:是实现轻量锁的一种典型实现方式

举例:在尝试获取锁时,锁已被其他线程获取,此时要如何等待锁。此时,自旋锁就会不停的循环等待锁的释放,在锁释放的第一时间,就可以拿到锁,拿到锁的效率更快了,但是会消耗很多cpu的资源。

(2)挂起等待锁:是实现重量锁的一种典型实现方式

当尝试加锁的时候,锁被占用了,也就是发生锁冲突,这个时候,不会像自旋锁那样一种等待,而是被挂起(进入阻塞状态),此时该线程不会参与调度。一直到这个锁被释放,系统才会去唤醒该线程,重新进入竞争锁的队列中,但是已经不是一直在等待锁,所以拿到锁的速度会更慢,但是更加节省cpu。

(3)像synchronized轻量级锁部分,是基于自旋锁实现;而重量级锁部分,基于挂起等待锁实现

上面的三组锁,在概念上面都有很大的相同之处。

4.可重入锁、不可重入锁

(1)可重入锁:同一个线程连续对同一个对象加锁多次,不会造成死锁

(2)不可重入锁:同一个线程对同一个对象加锁多次,会造成死锁

(3)像synchronized就是可重入锁

5.公平锁、非公平锁

(1)公平锁:严格按照先来后到的顺序来获取锁(也就是按照队列来获取)。哪一个线程等待的时间最长,哪个线程就拿到锁。

(2)非公平锁:n个线程,不按顺序,随机获取到锁(抢占式、随机获取的),和线程等待的时间没有关系。

(3)synchronized属于非公平锁

6.互斥锁、读写锁

(1)互斥锁:当一个线程对一个对象加锁之后,其他的线程再想对这个对象加锁,就一定会产生互斥(也就是阻塞)

(2)读写锁:是一种特殊的锁,分为:加读锁、加写锁、解锁。

当一个线程对一个对象加锁之后,其他线程根据加锁的类型是否要产生互斥

1)当该第一个线程加的锁是读锁,第二个线程也加读锁,是不会产生互斥的。(读锁和读锁之间,会产生互斥)

2)第一个线程是写锁,第二个线程也是写锁,也会产生互斥。(写锁和写锁之间,会产生互斥)

3)读锁和写锁之间,也会产生互斥

读写锁的优势,就是可以增加并发能力,提供程序执行的效率。

(3)像synchronized是互斥锁。

二、sychronized实现原理

我们的sychronized锁,即是悲观锁,也是乐观锁;即是重量锁,也是轻量锁;即是自选锁,也是挂起等待锁;还是可重入锁、非公平锁和互斥锁。正是因为它是一个“自适应”锁,在使用synchronized加锁时,就会有一系列的升级策略。

1.升级策略

升级的步骤大概是四步:未加锁状态(无锁)---》偏向锁---》轻量级锁---》重量级锁。而且这种升级是不可逆的。下面会侧重介绍什么是偏向锁。

(1)无锁状态

这个也就是没有加锁时,或者代码中还未执行到synchronized处

(2)偏向锁

当执行完了synchronized后,此时并不是真正的加锁,仅仅只是做了一个标记,当有冲突时,才会真正的加锁(也就是升级为轻量级锁后)

举例:当第一次synchronized时,只是轻轻的标记了一下,未真正加锁;此时,如果有其他线程也想尝试加锁(有冲突出现),此时该线程就会感到危机,就会真正的加锁。但是如果一种没有其他线程来尝试加锁,就只是标记,不会真正加锁,这样就可以提高效率,减少资源的消耗。

偏向锁是懒的一种体现,能不加锁,就不加锁。 

(3)轻量级锁

当有冲突时,就会从偏向锁升级为轻量级锁。

(4)重量级锁

当冲突进一步加大时,就会从轻量级锁升级为重量级锁,开销进一步加大。

2.锁消除

(1)锁消除,是编译器的一种优化机制

(2)当代码中有加锁操作之后,编译器会根据当前的代码做出判断,如果该位置确实不需要真的加锁,就会自动将锁的操作给优化掉。例如:只有一个线程的代码中,使用了锁操作。

(3)编译器会保证这种操作前后是不会出错的,所以该优化出现的场景也比较少

3.锁粗化

在锁的粗化之前,有个词称为锁的粒度

(1)锁的粒度:在一个加锁范围内,包含的代码越多,锁的粒度就越粗;反之,锁的粒度就越细。

(2)做法:在一些代码中,需要频繁的加锁和解锁,编译器就会自动的把多个细粒度的锁,合并称一个粗粒度的锁。前提都是对同一个对象加锁

举例:像打电话汇报三个任务,是打一次电话汇报一个任务还是打一次汇报三个任务的效率快呢?那当然是打一次电话汇报三个任务快。所以jvm就会把多个单独的锁合并成一个锁。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

代码小娥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值