Java 可重入锁
可重入锁的意思就是:同一个线程可以多次获得同一把锁。
隐式锁
synchronized关键字使用的锁(默认为可重入锁)
同步块
private static final Object KEY = new Object();
@Test
void reEnterLock() {
new Thread(() -> {
synchronized (KEY) {
System.out.println(Thread.currentThread().getName() + "\t调用1");
synchronized (KEY) {
System.out.println(Thread.currentThread().getName() + "\t调用2");
synchronized (KEY) {
System.out.println(Thread.currentThread().getName() + "\t调用3");
}
}
}
}, "可重入锁")
.start();
}
控制台输出:
可重入锁 调用1
可重入锁 调用2
可重入锁 调用3
同步方法
@Test
void reEnterLockMethod() {
m1();
}
public synchronized void m1() {
System.out.println("方法1");
m2();
}
public synchronized void m2() {
System.out.println("方法2");
m3();
}
public synchronized void m3() {
System.out.println("方法3");
}
控制台输出:
方法1
方法2
方法3
字节码解析
Java代码
public void codeBlock() {
synchronized (KEY) {
System.out.println("synchronized code block");
}
}
对应字节码
javap -c xxx.class
public void codeBlock();
Code:
0: getstatic #15 // Field KEY:Ljava/lang/Object;
3: dup
4: astore_1
5: monitorenter
6: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #16 // String synchronized code block
11: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: aload_1
15: monitorexit
16: goto 24
19: astore_2
20: aload_1
21: monitorexit
22: aload_2
23: athrow
24: return
我们需要注意关键字monitorenter
及monitorexit
,monitorenter
表示加锁,monitorexit
表示释放锁,那么为什么monitorexit
出现了两次,也就是说释放了两次锁,因为Java代码要保证出异常后能够把锁给释放掉,所以在代码结束前再执行了一次锁的释放。
每个锁对象①拥有一个锁计数器和一个指向持有该锁的线程指针,当monitorenter
时,如果目标锁对象的计数器为0,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象线程设置为当前线程,并将其锁计数器加1。
①如同步块内的静态对象
KEY
。
在目标锁对象的计数器不为0的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有锁的线程释放该锁。
当执行monitorexit
时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁被释放。
显式锁(Lock)
这里以ReentrantLock作为示例
private final static Lock LOCK = new ReentrantLock();
@Test
void reentrantLock() {
new Thread(() -> {
LOCK.lock();
try {
LOCK.lock();
System.out.println(Thread.currentThread().getName() + "\t调用1");
try {
System.out.println(Thread.currentThread().getName() + "\t调用2");
} finally {
LOCK.unlock();
}
} finally {
LOCK.unlock();
}
}, "Thread 1")
.start();
new Thread(() -> {
LOCK.lock();
try {
LOCK.lock();
System.out.println(Thread.currentThread().getName() + "\t调用1");
try {
System.out.println(Thread.currentThread().getName() + "\t调用2");
} finally {
LOCK.unlock();
}
} finally {
LOCK.unlock();
}
}, "Thread 2")
.start();
}
控制台输出:
Thread 1 调用1
Thread 1 调用2
Thread 2 调用1
Thread 2 调用2
使用显式锁一定要注意,Lock#lock
与Lock#unlock
一定要成对出现,否则会出现线程死锁的问题,正常情况下,加锁几次,解锁几次。