0%

重入锁 ReentrantLock 以及公平性问题

概念

重入锁 ReentrantLock,是指支持重进入的锁,表示锁可以支持一个线程对资源的重复加锁,即「任意线程在获取到这个锁之后,如果再次获取该锁,不会被锁阻塞」。重入锁还支持锁时的公平非公平性(默认)的选择。

实现

实现重入的机制,需要解决以下两个问题:

  • 线程需要再次获取锁:锁一定要能够识别获取锁的线程是否是当前占据锁的线程,如果符合,就能够获取成功
  • 锁需要得到最终的释放:线程重复了 n 次获取了锁之后,就需要在第 n 次释放锁后,其它的线程才能获取到该锁。主要是通过计数器来实现。锁每获取一次,计数器就要自增 1;每释放一次,计数器就要自减 1,一直减到 0 为止,表示当前线程已经成功释放了该锁,其它线程可以来获取该锁。

重入性和公平性

ReentrantLock 不支持隐式的重入锁,但是可以在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁且不被阻塞。ReentrantLock 的公平与否,是通过构造方法来决定的,内部类Sync继承了AQS,分为公平锁FairSync和非公平锁NonfairSync

公平锁和非公平锁

先对锁进行获取的请求肯定是优先执行的,锁获取的顺序也符合请求的绝对时间顺序,类似于 FIFO,那么就是公平锁。反之,就是非公平锁。

以下是公平锁与非公平锁的测试输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 公平锁的结果
FAIR lock by [9], waiting by []
FAIR lock by [10], waiting by [11, 12, 9]
FAIR lock by [11], waiting by [12, 9, 13, 10]
FAIR lock by [12], waiting by [9, 13, 10, 11]
FAIR lock by [9], waiting by [13, 10, 11, 12]
FAIR lock by [13], waiting by [10, 11, 12]
FAIR lock by [10], waiting by [11, 12, 13]
FAIR lock by [11], waiting by [12, 13]
FAIR lock by [12], waiting by [13]
FAIR lock by [13], waiting by []

// 非公平锁的结果
UNFAIR lock by [10], waiting by [9, 11]
UNFAIR lock by [10], waiting by [9, 11, 12, 13]
UNFAIR lock by [9], waiting by [11, 12, 13]
UNFAIR lock by [9], waiting by [11, 12, 13]
UNFAIR lock by [11], waiting by [12, 13]
UNFAIR lock by [11], waiting by [12, 13]
UNFAIR lock by [12], waiting by [13]
UNFAIR lock by [12], waiting by [13]
UNFAIR lock by [13], waiting by []
UNFAIR lock by [13], waiting by []

从结果中我们可以看出,公平锁每次都是从同步队列中的第一个节点获取锁,而非公平锁则是会连续两次获取锁。

从开销上来看,公平锁的开销会更大一些,因为它每次都要切换到另一个线程,而对于非公平锁,会出现连续获取锁的对象,切换次数要少一些,所以非公平锁的开销会更小一些。所以,公平锁保证了锁的获取按照顺序进行,保证了公平性,解决了饥饿问题,但是增加了大量的线程切换。