ReentrantLock是java并发包(Java.util.concurrent)下,基于AbstractQueuedSynchronizer实现的可重入的互斥锁
AbstractQueuedSynchronizer
AQS是java并发包下的一个抽象类,不能使用它直接创建一个对象
具体锁的实现需要通过继承AQS
底层结构
AQS底层是基于双向队列(同步队列)实现的,类中维护了三个成员变量:
- 一个是int类型的STATE: 用于标记当前锁的状态
- 一个头节点:HEAD
- 一个尾节点:TAIL
每当有线程竞争锁失败后,会将该线程包装成一个Node放入双向队列(同步队列)中
同步队列为空时,会先创建一个空的头节点,里面没有线程任何信息,然后再将竞争失败的Node节点添加到同步队列中(因为等待锁的节点需要被前一个节点唤醒)
Node节点存放的信息:
- waitStatus:当前节点的状态
- 0:节点创建好之后的默认状态
- SIGNAL(-1):当前节点需要唤醒下一个节点
- CANCELLED(1):当前节点被取消,不再参与锁的竞争
- CONDITION(-2):节点在条件队列中等待
- PROPAGATE(-3):共享模式下需传播唤醒信号
- prev:前驱指针
- next:后继指针
- thread:竞争锁失败的线程
- nextWaiter:在条件队列中等待的节点(条件队列是单向链表)
ReentrantLock
内部实现了两种锁:公平锁和非公平锁(默认使用的是非公平锁)
公平锁:当有新的线程尝试获取锁时,如果ReentrantLock的同步队列中已经存在节点,那么直接将线程加入到队列中
非公平锁:当有新的线程尝试获取锁时,不管队列中是否已经有节点,都去参与竞争锁,竞争失败则放入到同步队列中
lock()
lock()是不可中断互斥锁,方法在尝试获取锁的期间,如果线程被中断,也不会响应中断,会一直尝试获取锁
在线程第一次尝试获取锁失败后,并不会立即加入同步队列,会再次尝试获取锁
图解为非公平锁的获取锁流程:
unlock()
unlock()释放锁的流程: 如果是同一个线程多次调用的方法中都去获取了锁,是可以允许的。在释放锁时,调用了多少次就要释放多少次,当锁的状态变为0之后,就会唤醒同步队列中的线程去竞争锁
需要手动调用,所以最好是在try块的finally方法中调用,确保获取锁的次数和释放锁的次数一致。不然队列中的线程就会一致阻塞
trylock()
trylock()只支持非公平锁
//使用的是非公平锁,在获取锁失败后直接返回false,不会进入同步队列等待
//需要业务中自己处理获取失败后的备用方案
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
tryLock(long timeout, TimeUnit unit)
默认实现的是非公平锁,公平锁在创建ReentrantLock时指定
在线程获取锁的时候,没有超过过期时间,会在同步队列中等待被前驱节点唤醒继续竞争锁。
如果超过了过期时间,线程会退出同步队列不再去竞争锁
如果再次期间线程被中断了,会抛出异常