重入锁ReentrantLock,就是支持重进入的锁,它表示该锁能够支持一个线程对资源重复加锁。
除此之外,该锁的还支持获取锁时的公平和非公平选择。
1.实现重进入
重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞,该特性的实现需要解决两个问题:
- 线程再次获锁。锁需要去识别锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
- 锁的最终释放。线程重复n此获取锁,随后再第n此释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0表示锁已经成功释放。
ReentantLock是通过组合自定义同步器来实现锁的获取与释放,以非公平性实现为例,获取同步状态的代码如下。
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){
throw new Error("Maximum lock count exceeded");
}
setState(nextc);
return true;
}
return false;
}
该方法增加了再次获取同步状态的处理逻辑:通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true,表示获取同步状态成功。
成功获取锁的线程再次获取锁,只是增加了同步状态值,这也就要求ReentrantLock在释放状态时减少同步状态值。
protected final boolean tryRelease(int releases){
int c = getState()-releases;
if(Thread.currentThread()!=getExclusiveOwnerThread()){
throw new IllegalMonitorStateException();
}
if(c==0){
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false,而只有同步状态完全释放了,才能返回true。可以看到,该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。
2.公平与非公平获取锁的区别
公平性与否是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合绝对时间顺序,也就是FIFO。
protected final boolean tryAcquire(int acquires){
final Thread current = Thread.currentThread();
int c = getState();
if(c==0){
if(!hasQueuePredecessors()&&compareAndSetState(0,acquires)){
setExclusiveOwnerThread(current);
return true;
}
}else if(current==getExclusiveOwnerThread()){
int nextc = c + acquires;
if(nextc<0){
throw new Error("Maximum lock count exceeded");
}
setState(nextc);
return true;
}
return false;
}
该方法与nonfairTryAcquire(int acquires)比较,唯一不同的位置为判断条件多了hasQueuedPredecessors()方法,即加入了同步队列中当前节点是否有前驱节点的判断,如果该方法返回true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。
公平锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平性锁虽然可能造成线程饥饿,但极少线程切换,保证了其更大的吞吐量。