目录
????????加锁的目的是为了保证线程安全,根据不同的实际情况,锁也会有不同的策略来应对。
以下将介绍多种锁策略,包括:
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 的锁优化》
链接生成中..........