<JavaEE> 锁进阶 -- 锁策略(乐观锁和悲观锁、重量级锁和轻量级锁、自旋锁和挂起等待锁、可重入锁和不可重入锁、公平锁和非公平锁、读写锁)

发布时间:2023年12月17日

目录

一、锁策略介绍

二、乐观锁和悲观锁

三、轻量级锁和重量级锁

四、自旋锁和挂起等待锁

五、公平锁和非公平锁

六、可重入锁和不可重入锁

七、读写锁


一、锁策略介绍

????????加锁的目的是为了保证线程安全,根据不同的实际情况,锁也会有不同的策略来应对。

以下将介绍多种锁策略,包括:

1)乐观锁 和 悲观锁
2)轻量级锁 和 重量级锁
3)自旋锁 和 挂起等待锁
4)读写锁
5)公平锁 和 非公平锁
6)可重入锁 和 不可重入锁

二、乐观锁和悲观锁

乐观锁在加锁之前进行预估,如果预估锁冲突发生概率不大或锁冲突不激烈,则不会在加锁时做太多工作,因此加锁速度也快,称为乐观锁。
悲观锁在加锁之前进行预估,如果预估锁冲突发生概率较大或锁冲突激烈,则需要在加锁时做更多工作,因此加锁速度也慢,称为悲观锁。
synchronized 是乐观锁还是悲观锁?
synchronized 在初始阶段是乐观锁,但如果发现锁竞争变得频繁,也会根据场景的变化而转换为悲观锁。

三、轻量级锁和重量级锁

图示演示锁的层次:

重量级锁重量级锁重度依赖mutex互斥锁,这种互斥锁是由CPU指令实现的,CPU指令又具有原子性。因此,使用重量级锁会引发大量的内核态和用户态切换,容易引发线程调度,加锁开销大。
轻量级锁轻量级锁则尽量不使用mutex互斥锁,代码能在用户态处理就尽量不切换内核态,除非确实需要时,才会使用mutex互斥锁,因此不容易发生用户态和内核态的切换,也不容易引发线程调度。
1)synchronized 是轻量级锁还是重量级锁?
synchronized 在初始阶段是轻量级锁,但如果发现锁竞争变得频繁,也会根据场景的变化而转换为重量级锁。
2)轻量级锁和重量级锁、乐观锁和悲观锁,这两中类型的锁之间有什么关系?

轻量级锁加锁开销小,加锁速度快,是乐观锁;

重量级锁枷锁开销大,加锁速度慢,是悲观锁;

由此可以看出,轻量级锁和重量级锁、乐观锁和悲观锁,其实是对同一个事物不同角度的描述。

锁的轻量与重量,是从加锁时做的工作的多少来判定的;

而锁的乐观与悲观,是从加锁前对后续工作量的预估来判定的。


四、自旋锁和挂起等待锁

自旋锁

如果获取锁失败,将会在极短的时间内再次尝试获取。获取锁这个行为会不断循环,直到成功获取到锁。

自旋锁没有放弃CPU,不涉及线程阻塞和调度,一旦锁被释放,就会第一时间获得锁。但与此同时,如果锁被其他线程长期持有,那么自旋锁就会持续占用CPU资源,造成资源浪费。

挂起等待锁

如果获取锁失败,会让线程挂起等待,直到锁释放或满足其他条件,再重新尝试获取锁。

挂起等待时,内核调度器会介入,此时线程被调度离开,此时CPU资源释放,可以另作其他用途,但重新获取锁的开销也就随之变大。

1)synchronized 是自旋锁还是挂起等待锁?
synchronized 在初始阶段是自旋锁,但如果发现锁竞争变得频繁,也会根据场景的变化而转换为挂起等待锁。
2)轻量级锁和重量级锁、乐观锁和悲观锁、自旋锁和挂起等待锁之间有什么关系?

自旋锁是轻量级锁的一种具体实现,同时也是一种乐观锁;

挂起等待锁则是重量级锁的一种具体实现,同时也是一种悲观锁。


五、公平锁和非公平锁

公平锁在线程阻塞后,形成一个等待队列,在待锁释放后,等待的线程按照“先来后到”的顺序获得锁。
非公平锁在锁释放之后,等待锁的线程一同竞争锁,不关心哪个线程先等待的。
1)synchronized 是公平锁还是非公平锁?

synchronized 是非公平锁。

操作系统内部的线程调度是无序的,synchronized 并没有使用额外的数据结构来记录线程的阻塞顺序,没有对随即调度这个机制进行限制,因此是非公平锁。

2)公平锁一定比非公平锁好吗?

公平锁天然就不会产生“线程饿死”问题。

但是,公平锁和非公平锁没有优劣之分,只需看哪个场景更适用。


六、可重入锁和不可重入锁

1)什么是可重入锁和不可重入锁?

同一个线程多次获取同一把锁而不会导致死锁,就是可重入锁;否则就是不可重入锁。

可重入锁中有专门用于记录锁的持有线程和加锁次数的属性。

可重入锁也叫递归锁。

2)可重入锁和不可重入锁分别有什么具体的实现?

Java 中,以 Reentrant 开头命名的锁都是可重入锁;JDK 中提供的 Lock 实现类,包括 synchronized 关键字,都是可重入锁。

Linux 系统提供的 mutex 是不可重入锁。


七、读写锁

1)多线程对同一共享数据进行读写的三种情况

<1>多个线程同时读同一共享数据,不会产生线程安全问题;

<2>多个线程同时写同一共享数据,会产生线程安全问题;
<3>多个线程一边读,一边写同一共享数据,也会产生线程安全问题;
2)为什么要有读写锁?

从上文可以看出,多线程同时读的操作并不会产生线程安全问题,而在实际开发中,这种读操作是十分频繁的,如果使用和后两种情况一样“重量”的锁,那么就会有非常多的资源开销和性能浪费。因此,如果讲读锁和写锁分开,那么就可将资源开销省下,提升了性能。

3)什么是读写锁?

读锁,就是一个线程加锁的时候,其他线程只能读不能写;

写锁,就是一个线程加锁的时候,其他线程不能读也不能写;

读锁与读锁之间不会出现锁冲突;

写锁和写锁之间会出现锁冲突;

读锁和写锁之间也会出现锁冲突;

读写锁实际上就是把读操作和写操作区分对待了。
4)synchronized 和 ReentrantReadWriteLock

synchronized 不是读写锁。

Java 标准库提供了?ReentrantReadWriteLock 类,实现了读写锁。

ReentrantReadWriteLock.ReadLock 类表示读锁。

ReentrantReadWriteLock.writeLock 类表示写锁。

上述两个锁,都通过 lock() 和 unlock() 方法进行加锁和解锁。


阅读指针 —>《锁进阶 -- synchronized 的锁优化》

链接生成中..........

文章来源:https://blog.csdn.net/zzy734437202/article/details/134916407
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。