可重入锁是一种线程同步的机制,它允许线程多次获取同一个锁,而不会产生死锁。可重入锁的特点是在持有锁的情况下可以再次进入同步代码块或方法,而不会被阻塞。ReentrantLock和synchronized都是可重入锁。ReentrantLock属于显式可重入锁,synchronized属于隐式可重入锁。
synchronized案例
public static void main(String[] args) {
final Object object = new Object();
new Thread(() -> {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + " 外层调用");
synchronized (object) {
System.out.println(Thread.currentThread().getName() + " 中层调用");
synchronized (object) {
System.out.println(Thread.currentThread().getName() + " 内层调用");
}
}
}
}, "A").start();
}
打印信息:
A 外层调用
A 中层调用
A 内层调用
如上面代码中,线程A外层调用已经获得object锁对象,中层调用时需要同样需要获得object锁对象,但是最外层并没有释放object锁对象,但是线程A照样能进入到代码的中层。内层也是一样的逻辑。简单来说:在一个synchronized修饰的方法或者代码块的内部调用本类的其他synchronized修饰的方法或者代码块时,是可以永远获得锁的。
每个对象多可以成为一个锁对象,都拥有一个锁计数器和指向持有该锁的线程的指针。当monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,java虚拟机将该锁对象的持有线程设置为当前线程,并且将其计数器+1。在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程时当前线程,那么虚拟机可以将其计数器+1,否则需要等待,直到持有线程释放该锁。当执行monitoreexit时,java虚拟机则需要将锁对象的计数器-1,计数器为零代表该锁已经被释放。
ReentrantLock案例
public static void main(String[] args) {
final Lock lock = new ReentrantLock();
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 进入最外层");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 进入中间层");
} finally {
//正常情况下,加锁几次就需要解锁几次。如果没有unlock,B线程就获取不到锁。
lock.unlock();
}
} finally {
//正常情况下,加锁几次就需要解锁几次。如果没有unlock,B线程就获取不到锁。
lock.unlock();
}
}, "A").start();
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 进入最外层");
} finally {
lock.unlock();
}
}, "B").start();
}
打印结果:
A 进入最外层
A 进入中间层
B 进入最外层
案例中,A线程需进入最外层已经获得lock对象,想要进入中间层也需要获得lock对象,但是进入中间层之前并没有unlock。
还有一点需要注意的是,A线程获得lock对象几次就与之相对应的unlock几次,不然线程B根本无法获得lock对象。