定义
重入锁就是支持重新进入的锁,表示该锁能够支持一个线程对资源的重新加锁。
实现重进入
ReenTrantLock为了实现重进入,必须要解决两个问题:
(1) 线程再次获取锁;
(2) 锁的最终释放。
ReentrantLock是通过组合自定义同步器来实现锁的获取和释放,
处理逻辑:
通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true,表示获取同步状态成功。
成功获取锁的线程再次获取锁,只是增加了同步状态值,这也就要求ReentrantLock在释放同步状态时减少同步状态值。当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。
公平锁和非公平锁
ReentrantLock支持设置公平锁或者使用非公平锁。首先,公平性与否是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序应该符合请求的绝对时间顺序(FIFO)。
ReentrantLock同步锁的实现逻辑:
在线程等待队列中加入当前节点是否有前驱节点的判断,如果存在前驱节点,则等待前驱节点获取锁并释放锁之后才能继续获取锁。
非公平锁的实现逻辑:
只要CAS设置同步状态成功,则表示当前线程获取了锁。
公平性锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平性锁虽然可能会造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量。(饥饿原因:当一个线程请求锁时,只要获取了同步状态即成功获取锁。在这个前提下,刚释放锁的线程再次获取同步状态的几率会非常大,使得其他线程只能在同步队列中等待)
tryLock
使用ReentrantLock可以进行尝试锁定"tryLock",这样无法锁定、或者在指定时间内无法锁定,线程可以决定是否继续等待。
ReentrantLock的优势
(1)具备尝试非阻塞地获取锁的特性:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁
(2)能被中断地获取锁的特性:与synchronized不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放
(3)超时获取锁的特性:在指定的时间范围内获取锁;如果截止时间到了仍然无法获取锁,则返回
ReentrantLock的典型使用场景
ArrayBlockingQueue。ArrayBlockingQueue使用ReentrantLock来实现加锁机制,保证队列的安全读取,并且使用Condition来实现队列阻塞的条件判断和读写唤醒。
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); // Lock only for visibility, not mutual exclusion
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
注意事项
在使用ReentrantLock类的时,一定要注意三点:
(1)在finally中释放锁,目的是保证在获取锁之后,最终能够被释放
(2)不要将获取锁的过程写在try块内,因为如果在获取锁时发生了异常,异常抛出的同时,也会导致锁无故被释放。而我们一般在finally中手动释放锁,当锁在try中被无故释放后,再次执行手动释放锁时,会因为锁的状态问题抛出异常。
(3)ReentrantLock提供了一个newCondition的方法,以便用户在同一锁的情况下可以根据不同的情况执行等待或唤醒的动作。
底层实现
ReentrantLock是基于AQS来实现的。ReentrantLock通过重写AQS的tryAcquire和tryRelease方法实现的lock和unlock。
ReentrantLock继承自父类Lock,然后有3个内部类,其中Sync内部类继承自AQS,另外的两个内部类继承自Sync,这两个类分别是用来实现公平锁和非公平锁的。
从Sync重写的方法tryAcquire、tryRelease可以知道,ReentrantLock实现的是AQS的独占模式,也就是独占锁,这个锁是悲观锁。