重入锁ReentrantLock
About 3 min
之前介绍的synchronized
关键字就是一种最简单的同步控制方法,而在JDK 5引入了重入锁与使用synchronized
具有相同的基本行为,但具有扩展功能。现在我们来学习下重入锁。
重入锁的使用
重入锁使用java.util.concurent.locks.ReentrantLock类来实现。下面是一段最简单的重入锁使用案例:
public class Reenterlock implements Runnable {
public static final Lock LOCK = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
for (int j = 0; j < 1000_000; j++) {
LOCK.lock();
try {
i++;
} finally {
LOCK.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
var reenterlock = new Reenterlock();
var t1 = new Thread(reenterlock);
var t2 = new Thread(reenterlock);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i); // 2000000
}
}
上述代码8~12行,使用重入锁保护临界区资源i
,确保多线程对i
操作的安全性。与synchronized
略有不同,我们需要手动的加锁和释放锁,这提供了更好的灵活性。
Warning
在退出临界区时,必须记得释放锁unlock()
,否则其他线程就没有机会再访问临界区了。
为什么叫重入锁
之所以叫重入锁,是因为同一个线程是可以连续获得同一把锁。试想一下场景:同步代码直接或间接调用包含同步代码的方法,并且两组代码使用相同的锁。如果没有可重入同步,同步代码将必须采取许多额外的预防措施,以避免线程导致自身阻塞。
LOCK.lock();
LOCK.lock();
try {
i++;
} finally {
LOCK.unlock();
LOCK.unlock();
}
在这种情況下,一个线程连续两次获得同一把锁。如果说一个线程多次获得了锁,那么也必须释放相同次数的锁。假如线程获得锁m
次,释放锁n
次
m > n
: 此线程仍旧持有这个锁,其他线程想进入临界区需要等待m = n
: 此线程不再持有这个锁,其他线程想进入临界区无需等待m < n
: 这里会有抛出IllegalMonitorStateException
的异常
对于判断当前线程是否持有锁,或者说持有多少次锁,JDK提供了相应的方法:isHeldByCurrentThread()
和getHoldCount
,下面给一个简单的例子:
public class Reenterlock implements Runnable {
public static final ReentrantLock LOCK = new ReentrantLock();
@Override
public void run() {
LOCK.lock();
LOCK.lock();
System.out.printf("HoldCount %d and isHeld %s.\n", LOCK.getHoldCount(), LOCK.isHeldByCurrentThread());
LOCK.unlock();
System.out.printf("HoldCount %d and isHeld %s.\n", LOCK.getHoldCount(), LOCK.isHeldByCurrentThread());
}
public static void main(String[] args) throws InterruptedException {
var reenterlock = new Reenterlock();
var t1 = new Thread(reenterlock);
t1.start();
System.out.printf("In main thread: HoldCount %d and isHeld %s.\n", LOCK.getHoldCount(), LOCK.isHeldByCurrentThread());
t1.join();
}
}
输出结果为:
In main thread: HoldCount 0 and isHeld false.
HoldCount 2 and isHeld true.
HoldCount 1 and isHeld true.
中断响应lockInterruptibly
对于synchronized
来说,如果一个线程在等待锁,要么它获得这把锁继续执行,要么它就保持等待。而重入锁提供另外一种可能,那就是在获取锁的过程中线程可以被中断,从而取消对锁的请求。这主要得益于方法lockInterruptibly
。
下面给出一个使用lockInterruptibly
解决死锁的例子:
限时等待锁申请
tryLock()
方法
公平锁和非公平锁
public ReentrantLock (boolean fair)