简介
ReentrantLock(重入锁)就是支持可重进入的锁,它表示该锁能支持一个线程对资源的重复加锁。另外还支持获取锁的公平和非公平选择ReentrantLock的实现不仅可以替代隐式的synchronized关键字,而且还能够提供超过关键字本身的多种功能。
公平与非公平
这个概念是针对锁的获取的,在绝对时间上,先对锁进行获取的请求一定先满足,那么这个锁是公平的,反之就是不公平的。公平锁的获取就是等待时间最长的线程最先获取锁,也就是锁获取是顺序的。但是公平锁的机制往往效率不高。
ReentrantLock的调用过程
ReentrantLock把所有Lock接口的操作都委派到一个sync类上,该类继承了队列同步器:
static abstract class Sync extends AbstractQueuedSynchronizer {
final static class NonfairSync extends Sync
final static class FairSync extends Sync
1、sync有两个子类,分别实现的是公平锁和非公平锁,默认为非公平锁。
2、由于该同步组件的实现使用的模板方法模式,我们看下ReentrantLock.lock()方法的调用过程(默认为非公平锁)
非公平锁语义
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;
}
1、首先判断锁当前的状态,如果锁空闲,则当前线程尝试获取。
2、当前线程获取锁成功,则设置当前线程为锁的拥有者。
3、如果锁状态为非空闲状态且当前线程为锁的拥有者,则直接将同步状态值进行增加并返回true,表示获取同步状态成功。
这里就是支持锁重入的场景,跟synchronized类似:成功获取锁的线程再次获取锁,只是增加了同步状态值,与此对应的是,在释放锁的时候需要对应减少同步状态值,直到同步状态值为0的时候,才表示这把锁真正的被释放了。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
公平锁语义
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (isFirst(current) &&
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;
}
这里与非公平锁大部分地方类似,只是线程在获取同步状态时,需要判断当前节点的前驱节点是否为首节点。公平锁总是按照队列的顺序来来获取锁的。
ReentrantLock的使用场景
1、发现该操作已经在执行中则不再执行
a、用在定时任务时,如果任务执行时间可能超过下次计划执行时间,确保该任务只有一个正在执行,忽略重复触发。
b、用在界面交互时点击执行较长时间请求操作时,防止多次点击导致后台重复执行。
主要用于进行非重要任务时防止重复执行。
private ReentrantLock lock = new ReentrantLock(); public void test()
{
if(lock.tryLock())
{
try
{
//dosomothing
}
finally
{
lock.unlock();
}
}
}
2、如果该操作已经在执行,则等待一个个执行(同步执行,与synchronized类似)
3、如果该操作已经在执行,则尝试等待一段时间,超时则放弃执行
if(lock.tryLock(5, TimeUnit.SECONDS))
{
try
{
//dosomothing
}
finally
{
lock.unlock();
}
}
这种其实属于场景2的改进,等待获得锁的操作有一个时间的限制,如果超时则放弃执行。
用来防止由于资源处理不当长时间占用导致死锁情况(大家都在等待资源,导致线程队列溢出)。
场景4:如果发现该操作已经在执行,等待执行。这时可中断正在进行的操作立刻释放锁继续下一操作。
synchronized与Lock在默认情况下是不会响应中断(interrupt)操作,会继续执行完。lockInterruptibly()提供了可中断锁来解决此问题。(场景2的另一种改进,没有超时,只能等待中断或执行完毕)