ReenterantLock基础
ReenterantLock是 synchronized关键字的扩展。它是一个显示锁,意味着锁的获取和释放必须由程序员手动编写代码控制。
相对于synchronized,它具备以下的优势:
- 未获取锁后的状态可中断
- 可以设置超时时间
- 可以设置公平锁
- 支持多个条件变量
并且它与synchronized一样都支持可重入。
可重入
可重入是指同一个线程如果首次获得这把锁后,因为它是这把锁的主人,可以再次获得这把锁。
相对的,不可重入是指获得这把锁之后不能再次获得这把锁,自身也会被锁挡住。
arduino
public static void main(String[] args) throws ExecutionException, InterruptedException {
ReentrantLock reentrantLock=new ReentrantLock();
for (int i=0;i<100;i++){
testReentrantLock(reentrantLock,i);
}
}
public static void testReentrantLock(ReentrantLock reentrantLock,int number){
reentrantLock.lock();
System.out.printf("第%d次获得锁\n",number);
}
未获得锁后的状态可打断
先看synchronized的行为。
csharp
public static void main(String[] args) throws ExecutionException, InterruptedException {
Object lock=new Object();
Thread thread1=new Thread(()->{
synchronized (lock){
while(true){
}
}
});
thread1.start();
TimeUnit.SECONDS.sleep(1);
Thread thread2=new Thread(()->{
synchronized (lock){
}
System.out.println("running");
});
thread2.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("线程1:"+thread1.getState());
System.out.println("线程2:"+thread2.getState());
thread2.interrupt();
System.out.println("线程2:"+thread2.getState());
}
程序运行结果如下:

再看ReentrantLock的lock行为。
csharp
public static void main(String[] args) throws ExecutionException, InterruptedException {
ReentrantLock lock=new ReentrantLock();
Thread thread1=new Thread(()->{
lock.lock();
while(true){
}
});
thread1.start();
TimeUnit.SECONDS.sleep(1);
Thread thread2=new Thread(()->{
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
System.out.println("interrupted");
return ;
}
System.out.println("running");
lock.unlock();
});
thread2.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("线程1:"+thread1.getState());
System.out.println("线程2:"+thread2.getState());
thread2.interrupt();
System.out.println("线程2:"+thread2.getState());
}
结果如下:

可以看出通过synchronized获取锁失败后是进入BLOCKED状态,而通过ReentrantLock获取锁失败后是进入WAITTING状态。
接下来继续观察lockInterruptibly
csharp
public static void main(String[] args) throws ExecutionException, InterruptedException {
ReentrantLock lock=new ReentrantLock();
Thread thread1=new Thread(()->{
lock.lock();
while(true){
}
});
thread1.start();
TimeUnit.SECONDS.sleep(1);
Thread thread2=new Thread(()->{
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
System.out.println("interrupted");
return ;
}
// lock.lock();
System.out.println("running");
lock.unlock();
});
thread2.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("线程1:"+thread1.getState());
System.out.println("线程2:"+thread2.getState());
thread2.interrupt();
System.out.println("线程2:"+thread2.getState());
}
结果是:

先探究一个问题:为什么BLOCKED不可以被打断,WAITING可以被打断?
我们知道,interrupt()是Java线程中用于协作式地通知一个线程应该停止当前正在执行的任务 的核心机制。它的核心作用是设置线程的中断标志位,而不是强制终止线程。
| 线程状态 | 调用 t.interrupt()的影响 |
|---|---|
RUNNABLE |
仅设置中断标志位 。线程的 isInterrupted()将返回 true,但线程会继续运行,直到它自己检查这个标志。 |
BLOCKED (等锁) |
仅设置中断标志位 。线程依然会卡在 BLOCKED状态,继续等它的锁。不会抛出 InterruptedException 。直到它终于拿到锁,进入 RUNNABLE后,才能通过检查标志位来响应中断。 |
WAITING TIMED_WAITING (等通知、睡眠、合并) |
立即抛出 InterruptedException ,并且清除中断标志位 (重置为false)。线程会从 wait(), join(), sleep()等方法调用中退出,进入 RUNNABLE状态。 |
那为什么ReentrantLock的lock也是WAITING状态,为什么不会被打断呢?
因为导致WAITING状态的原因和底层机制不同,ReentrantLock的加锁底层就是LockSupport.park()
进入 WAITING的方法 |
对 interrupt()的响应 |
说明 |
|---|---|---|
Object.wait() Thread.sleep() Thread.join() |
立即响应 ,抛出 InterruptedException。并清除中断状态 |
这些是Java标准库提供的协作式中断原语,设计目的就是快速响应中断请求。 |
LockSupport.park() |
立即解除阻塞 ,但不抛出异常,只是安静地返回。但是保留中断状态 | 这是更底层的线程阻塞原语。中断会唤醒它,但行为的控制权完全交给了上层的调用代码。ReentrantLock就构建在此之上。 |
也就是说其实interrupt是唤醒了线程的,但是它被唤醒之后再次去尝试获得锁,导致又调用了LockSupport.lock。
而ReentrantLock.lockInterruptibly只是设定了唤醒之后抛出异常,这部分在后面的源码部分再深挖。
锁超时
通过synchronized获得锁时,如果未获得,则会一直BLOCKED,而ReentrantLock可以设置超时的时间。
csharp
public static void main(String[] args) throws ExecutionException, InterruptedException {
ReentrantLock lock=new ReentrantLock();
Thread thread1=new Thread(()->{
lock.lock();
while(true){
}
});
thread1.start();
TimeUnit.SECONDS.sleep(1);
Thread thread2=new Thread(()->{
// try {
// lock.lockInterruptibly();
// } catch (InterruptedException e) {
// System.out.println("interrupted");
// return ;
// }
try {
lock.tryLock(5,TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("running");
//lock.unlock();
});
thread2.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("线程1:"+thread1.getState());
System.out.println("线程2:"+thread2.getState());
//thread2.interrupt();
System.out.println("线程2:"+thread2.getState());
}
结果如下:
使用tryLock()再尝试在给定的时间内获取锁,如果没有获得,将直接进行剩余代码。
公平锁
synchronized是非公平锁,ReentrantLock默认也是非公平锁。 以下代码启用公平锁:
ReentrantLock lock=new ReentrantLock(true);
支持多个条件变量
synchronized中存在唯一的一个条件变量,也就是waitSet,任何调用wait的线程都将进入waitSet,通过notifyAll将会唤醒waitSet中的所有线程。
而ReentrantLock支持多个条件变量。可以实现多个类似的waitSet,实现一种精细化的唤醒。
| 方法 | 作用 | 说明 |
|---|---|---|
await() |
使当前线程等待 | 释放锁,进入等待状态 |
signal() |
唤醒一个等待线程 | 从等待队列中唤醒一个 |
signalAll() |
唤醒所有等待线程 | 唤醒该条件的所有线程 |
awaitNanos(long) |
限时等待 | 超时自动唤醒 |
