什么是锁重入?
一个线程拿到锁之后,不用释放,还能再拿同一把锁。
举个例子
你进家门(拿到家门锁),进了客厅后,想进卧室(卧室也用同一把钥匙),不用先把家门钥匙还回去,直接用手里的钥匙开门就行。
Java 里的情况
synchronized 关键字
比如一个同步方法 A 里调用了另一个同步方法 B,且两者用的是同一把锁(比如都是同一个对象的锁),线程进了 A 之后,能直接进 B,不用等自己释放 A 的锁。
ReentrantLock 锁
手动加锁时,线程第一次lock()拿到锁后,还能再lock()一次(重入),但要记得解锁同样多次(unlock()),最后一次解锁后,其他线程才能抢。
为什么需要它?
避免死锁。如果不支持重入,线程进了 A 之后想进 B,就得先放 A 的锁,可自己还拿着 A 的锁呢,就会卡住(死锁)。
关键点
- 必须是「同一把锁」才能重入,不同的锁不行。
- ReentrantLock 要记得加几次锁就解几次,不然锁会一直被占着。
以下是几个直观的锁重入代码示例,分别展示 synchronized 和 ReentrantLock 的重入特性:
示例 1:synchronized 方法的重入(同一对象锁)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class SyncReentrantDemo { public static void main(String[] args) { new Thread(new Task()).start(); }
static class Task implements Runnable { @Override public void run() { test1(); }
private synchronized void test1() { System.out.println(Thread.currentThread().getName() + " 进入test1"); test2(); System.out.println(Thread.currentThread().getName() + " 离开test1"); }
private synchronized void test2() { System.out.println(Thread.currentThread().getName() + " 进入test2"); test3(); System.out.println(Thread.currentThread().getName() + " 离开test2"); }
private synchronized void test3() { System.out.println(Thread.currentThread().getName() + " 进入test3"); System.out.println(Thread.currentThread().getName() + " 离开test3"); } } }
|
运行结果(同一线程连续进入 3 个同步方法,体现重入):
1 2 3 4 5 6
| Thread-0 进入test1 Thread-0 进入test2 Thread-0 进入test3 Thread-0 离开test3 Thread-0 离开test2 Thread-0 离开test1
|
示例 2:synchronized 代码块的重入(同一锁对象)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class SyncBlockReentrantDemo { private static final Object lock = new Object();
public static void main(String[] args) { new Thread(() -> { synchronized (lock) { System.out.println("外层代码块:拿到锁"); synchronized (lock) { System.out.println("内层代码块:再次拿到锁"); } System.out.println("外层代码块:释放锁"); } }).start(); } }
|
运行结果(同一线程在嵌套代码块中重复获取锁):
1 2 3
| 外层代码块:拿到锁 内层代码块:再次拿到锁 外层代码块:释放锁
|
示例 3:ReentrantLock 的重入(显式锁)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo { private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) { new Thread(() -> { lock.lock(); try { System.out.println("第一次加锁,计数器:" + lock.getHoldCount()); method1(); } finally { lock.unlock(); System.out.println("最终解锁后,计数器:" + lock.getHoldCount()); } }).start(); }
private static void method1() { lock.lock(); try { System.out.println("第二次加锁,计数器:" + lock.getHoldCount()); method2(); } finally { lock.unlock(); System.out.println("第二次解锁后,计数器:" + lock.getHoldCount()); } }
private static void method2() { lock.lock(); try { System.out.println("第三次加锁,计数器:" + lock.getHoldCount()); } finally { lock.unlock(); System.out.println("第三次解锁后,计数器:" + lock.getHoldCount()); } } }
|
运行结果(通过 getHoldCount() 查看锁计数器变化,体现重入次数):
1 2 3 4 5 6
| 第一次加锁,计数器:1 第二次加锁,计数器:2 第三次加锁,计数器:3 第三次解锁后,计数器:2 第二次解锁后,计数器:1 最终解锁后,计数器:0
|
总结
- 锁重入的核心是「同一线程重复获取同一把锁」,无需释放后再获取。
synchronized 自动维护重入计数器,ReentrantLock 需手动保证 lock() 和 unlock() 次数一致(否则锁无法释放)。
- 嵌套调用同步方法 / 代码块时,重入特性避免了线程自己等自己的死锁问题。