Java中的锁是用于控制多个线程对共享资源进行访问的机制,目的是保证共享资源在同一时刻只能被一个线程访问,从而防止多线程并发导致的数据不一致问题。
在Java中,锁可以通过多种方式实现,包括使用Java语言提供的synchronized关键字,以及java.util.concurrent.locks
包下的一系列锁实现。
synchronized
关键字
synchronized
关键字是Java语言内置的,使用方便,无需额外导入包。
当进入synchronized
代码块或方法时,会自动获取锁,在退出时会自动释放锁。
这种锁是独占锁,也就是说,在同一时间只有一个线程可以持有该锁,其他线程需要等待。一旦线程获取了锁,它只能等待锁的释放,无法被中断。
synchronized
用法
同步实例方法
当synchronized
修饰一个实例方法时,锁定的是调用这个方法的对象实例。
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
在上面的例子中,任何时刻只有一个线程能够执行increment
或getCount
方法,因为这些方法是同步的,并且锁定的是同一个对象实例。
同步静态方法
当synchronized
修饰一个静态方法时,锁定的是这个类的Class对象。
public class Counter {
private static int count = 0;
public static synchronized void increment() {
count++;
}
public static synchronized int getCount() {
return count;
}
}
在这个例子中,increment
和getCount
方法是同步的,并且锁定的是Counter
类的Class对象。这意味着即使是不同的Counter
实例,它们的同步静态方法也不能同时被多个线程执行。
同步代码块
synchronized
也可以用来同步一个代码块,而不是整个方法。这时,需要指定一个锁对象。
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized(lock) {
count++;
}
}
}
在这个例子中,synchronized
代码块使用了一个名为lock
的对象作为锁。只有获取了这个锁的线程才能执行代码块中的代码。这种方式比同步整个方法更加灵活,因为它允许对不同的代码块使用不同的锁对象。
注意事项
不能在构造方法上使用synchronized
,因为构造方法在对象未完全构造完成之前是不允许被其他线程访问的。
接口方法默认是public的且应该是抽象的,所以synchronized
关键字不能用于接口方法。
在使用 synchronized
锁定字符串时,为了确保所有线程引用同一个字符串实例,通常会使用 String.intern()
将字符串放入常量池。然而,在处理大量数据的情况下,这样做可能会导致内存占用增加、性能下降和频繁的 Full GC。因此,建议使用 Guava 的 Interners
,创建的弱引用 interner
允许字符串在不再被引用时及时被回收。
wait()
方法
wait()
方法属于 Object
类,因此任何对象都可以调用它。
当一个线程调用一个对象的wait()方法时,它会释放该对象的锁并进入等待状态,直到其他线程调用同一个对象的notify()或notifyAll()方法唤醒它或者等待时间超时。
wait()
方法有以下几种形式:
wait()
: 导致当前线程等待,直到另一个线程调用该对象的notify()
或notifyAll()
方法。wait(long timeout)
: 导致当前线程等待,直到另一个线程调用notify()
或notifyAll()
,或者指定的时间已过。wait(long timeout, int nanos)
: 与wait(long timeout)
类似,增加了额外的纳秒级精度。
wait()
方法必须在 synchronized
代码块或方法内部调用,否则会抛出 IllegalMonitorStateException
。
public void doWait() {
synchronized(this) {
try {
this.wait(); // 当前线程进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
notify()
方法
notify()
方法也是 Object
类的一部分。当在一个对象上调用 notify()
时,它会唤醒在该对象上等待的一个线程(如果有的话)。如果有多个线程在等待,那么会随机选择一个线程。
notify()
方法也必须在 synchronized
代码块或方法内部调用。
public void doNotify() {
synchronized(this) {
this.notify(); // 唤醒一个等待的线程
}
}
实现线程交替打印AB10次
- 创建一个共享对象,例如一个锁对象或者一个标志位。
- 创建两个线程,分别负责打印A和B。
- 在每个线程中,使用同步块或者wait/notify方法来控制线程的执行顺序。
- 让每个线程循环5次,每次打印一个字符,然后唤醒另一个线程。
示例代码
public class AlternatePrinting {
private static final Object lock = new Object();
private static int count = 10;
private static boolean printA = true; // 控制打印顺序的标志位
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
while (count > 0) {
synchronized (lock) {
while (!printA) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (count <= 0) {
return;
}
System.out.print("A");
count--;
printA = false;
lock.notify();
}
}
});
Thread threadB = new Thread(() -> {
while (count > 0) {
synchronized (lock) {
if (printA) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (count <= 0) {
return;
}
System.out.print("B ");
count--;
printA = true;
lock.notify();
}
}
});
threadB.start();
threadA.start();
}
}
notify() 和 wait() 方法都必须在同步块或方法中使用,以确保当前线程已经获得了监视器锁。
synchronized
的底层实现
synchronized
的底层实现依赖于Java对象头和监视器锁(Monitor Lock),也称为内置锁(Intrinsic Lock)。在JVM中,每个对象都可以作为监视器锁的载体。
一个Java对象的内存布局由对象头、实例数据、对齐填充3部分组成:
对象头(Object Header)
- Mark Word: 这是对象头的一部分,用于存储对象的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。Mark Word的大小与JVM的具体实现有关,有32位和64位。
- 类型指针: 指向对象的类元数据,JVM通过这个指针来确定对象是哪个类的实例。在64位JVM中,类型指针可能占用32位或64位,这取决于是否启用了压缩指针。
实例数据(Instance Data)
包含对象真正存储的有效信息,即各个实例字段的内容。这些字段的存储顺序受到JVM分配策略和字段在类中声明的顺序影响。例如,相同宽度的字段可能会被分配到一起以优化对齐。
对齐填充(Padding)
确保对象的大小是8字节的倍数。这是为了在硬件层面上提高内存的访问效率,因为CPU在访问8字节对齐的数据时会更加高效。
Java对象头
在JVM中,每个对象都有一个对象头(Object Header),对象头中的一部分用于存储锁状态和锁信息。对象头通常包括以下部分:
- Mark Word:存储对象的HashCode、锁状态、线程ID等信息。通常占32位或64位,具体取决于JVM的架构。它包含的信息会随着锁状态的变化而变化。
- Class Metadata Address:指向对象所属类的元数据的指针。
32位和64位Mark Word结构如下:
锁状态(32位) | 25bit | 25bit | 4bit | 1bit | 2bit |
---|---|---|---|---|---|
23bit | 2bit | 是否偏向锁 | 锁标志位 | ||
无锁态 | 对象的hashCode | 分代年龄 | 0 | 01 | |
偏向锁 | 线程ID | Epoch | 分代年龄 | 1 | 01 |
轻量级锁 | 指向栈中锁记录的指针 | 00 | |||
重重量级锁 | 指向互斥量(重重量级锁)的指针 | 10 | |||
GC标记 | 空 | 空 | 11 |
锁类型(64位) | 61 Bit | 是否偏向锁标记 | 锁类型 |
---|---|---|---|
无锁 | 未使用(25 Bit) HashCode(31 Bit) 未使用(1 Bit) 分代年龄(4 Bit) | 0 | 01 |
偏向锁 | 当前线程指针(54 Bit) Epoch(2 Bit) 未使用(1 Bit) 分代年龄(4 Bit) | 1 | 01 |
轻量锁 | 指向线程栈中lock record 指针 (62 Bit) | 00 | |
重重量锁 | 指向互斥量(重量级锁)的指针 (62 Bit) | 10 | |
GC标记 | 11 |
监视器锁
监视器锁(Monitor Lock)是一种基于进入和退出监视器的二元锁。在JVM中,synchronized
通过monitorenter
和monitorexit
两个字节码指令来实现锁的获取和释放。
锁的获取(monitorenter
)
当一个线程尝试进入一个synchronized
方法或代码块时,会发生以下操作:
- 尝试获取锁:线程会尝试使用CAS操作将对象的Mark Word更新为指向锁记录(Lock Record)的指针。
- 锁竞争:如果Mark Word已经指向了其他锁记录,表示锁已经被另一个线程持有,当前线程会进行自旋等待(Spin Wait),尝试多次获取锁。
- 锁膨胀:如果自旋等待失败,JVM会将轻量级锁膨胀为重量级锁,这涉及到操作系统层面的线程状态转换和调度。
锁的释放(monitorexit
)
当一个线程退出synchronized
方法或代码块时,会发生以下操作:
- 更新Mark Word:线程会更新对象的Mark Word,将锁记录的指针清空。
- 唤醒等待线程:如果对象的等待集中有其他线程,JVM会选择一个线程唤醒,使其可以重新尝试获取锁。
锁的状态
监视器锁有几种状态,包括无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。JVM会根据竞争情况自动在这些状态之间转换,以优化性能。
偏向锁状态(Biased Locking)
偏向锁主要解决无竞争或低竞争场景下的锁性能问题。它的目标是减少无竞争条件下的同步开销,即当一个线程多次获得同一把锁时,无需进行线程切换和调度,从而提高性能。
当一个线程访问同步块时,如果该锁没有被其他线程占用,JVM会将这个锁"偏向"给第一个获得它的线程,记录其线程ID到对象头Mark Word中。此后,同一个线程再次获取锁时,只需检查线程ID是否匹配即可直接进入同步块,无需额外的同步操作。
如果锁被另一个线程尝试获取,偏向状态会被撤销,锁会升级为轻量级锁。
轻量级锁状态(Lightweight Locking)
轻量级锁的目的是在没有多线程竞争或者竞争不激烈的情况下,减少传统的重量级锁使用操作系统互斥量(mutex)产生的性能消耗。
当线程发现对象已经被另一个线程持有轻量级锁时,会在自己的线程栈中创建一个栈帧用于存放锁记录的空间,并使用CAS操作尝试将自身线程栈中的锁记录地址写入对象头。
如果成功,表示获得了锁;如果失败,则进入自旋状态。
如果自旋超过一定次数(默认10次),或者持有锁的线程长时间不释放锁,轻量级锁就会膨胀为重量级锁。
CAS操作是硬件级别的原子操作,这意味着它在执行过程中不会被其他线程中断。
重量级锁状态(Heavyweight Locking)
重量级锁是传统的互斥锁机制,其依赖于操作系统提供的互斥量(Mutex)来处理线程间的同步问题。它的主要目的是在高度竞争的环境下维持系统的稳定性。
一旦锁进入重量级状态,这表明锁竞争已十分激烈,此时,JVM将依赖于操作系统的互斥量来管理线程的阻塞与唤醒,以此避免由于轻量级锁状态下频繁的CAS操作导致的CPU资源浪费。
使用重量级锁涉及到用户态与核心态之间的转换,这是因为操作系统互斥量的介入。线程的阻塞与唤醒过程需要执行上下文切换,这是一项成本较高的操作,因为它不仅需要保存和恢复CPU寄存器状态,还要处理线程栈在内存中的切换。
上下文切换和用户态至内核态的转换均带来了较大的性能开销。因此,为了减少这种开销,JVM在锁竞争不是非常激烈的情况下,会优先使用轻量级锁或偏向锁,以优化同步操作的性能。当锁竞争确实很激烈时,重量级锁成为确保线程同步正确性的有效手段。
locks
包下的锁实现
java.util.concurrent.locks
包是Java并发API的一部分,它提供了一系列的锁实现,用于更细粒度和更灵活的线程同步。
这个包下的主要类和接口包括ReentrantLock
、ReadWriteLock
、Lock
、Condition
等。
Lock接口
Lock
接口是Java并发锁的核心,它提供了比synchronized
关键字更丰富的锁定操作。Lock
接口的主要方法包括:
lock()
:获取锁,如果锁已经被其他线程持有,则当前线程会阻塞。lockInterruptibly()
:获取锁,如果锁已经被其他线程持有,则当前线程会阻塞,直到获取锁或者被中断。tryLock()
:尝试非阻塞地获取锁,返回一个布尔值表示是否成功获取到锁。tryLock(long time, TimeUnit unit)
:在给定的时间内尝试获取锁,如果超过时间还未获取到锁,则返回false。unlock()
:释放锁。newCondition()
:返回一个新的Condition
对象,这个新对象与当前的Lock
实例相关联。
LockSupport
类
LockSupport
是Java并发包java.util.concurrent.locks
的一部分,提供了基本的线程阻塞和解除阻塞操作。它是构建高级同步类的底层工具,比如锁(Lock
)、信号量(Semaphore
)等。
主要方法和功能如下:
park()
: 阻塞当前线程,直到unpark()
被调用、线程被中断或者"莫名其妙"地返回。unpark(Thread thread)
: 解除指定线程的阻塞状态。如果该线程因为调用park()
而阻塞,它将解除阻塞。如果该线程未阻塞,则下次调用park()
时不会阻塞。parkNanos(long nanos)
: 阻塞当前线程,最长不超过指定的纳秒数。parkUntil(long deadline)
: 阻塞当前线程,直到指定的绝对时间戳。getBlocker(Thread t)
: 获取导致线程t
阻塞的blocker对象。这可以用于监视和诊断线程为何被阻塞。setBlocker(Thread t, Object blocker)
: 设置线程t
的blocker对象。
LockSupport
内部使用Unsafe
类来实现这些功能,而Unsafe
类包含一些native方法。
它为每个线程关联了一个"许可",park()
会消耗这个许可,unpark()
会重新给予许可。这样,线程就可以通过这个许可来控制自己的阻塞和解除阻塞。
park()
和unpark()
的调用顺序会被保持,与volatile变量的访问顺序一致,但与非volatile变量访问顺序不一定保持一致。
LockSupport
的主要用途是作为构建锁等同步工具的底层原语。它不是用于一般并发控制的直接工具,而是为锁等同步工具提供底层支持。
实现线程ABC依次打印ABC
查看代码
import java.util.concurrent.locks.LockSupport;
public class PrintABCUsingLockSupport {
private static Thread A;
private static Thread B;
private static Thread C;
public static void main(String[] args) {
A = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.print("A");
LockSupport.unpark(B); // 解除B线程的阻塞
LockSupport.park(); // 阻塞A线程
}
}, "A");
B = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park(); // 阻塞B线程
System.out.print("B");
LockSupport.unpark(C); // 解除C线程的阻塞
}
}, "B");
C = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LockSupport.park(); // 阻塞C线程
System.out.print("C");
LockSupport.unpark(A); // 解除A线程的阻塞
}
}, "C");
// 按照A、B、C的顺序启动线程
A.start();
B.start();
C.start();
}
}
在这个例子中,我们创建了三个线程A、B、C。
A打印后解除B的阻塞,然后阻塞自己,
B先是阻塞自己,解除阻塞后打印,然后解除C的阻塞,
C同样是阻塞自己,解除阻塞后打印,然后解除A的阻塞,
这样形成一个闭环,使得ABC依次打印。
ReentrantLock
类
ReentrantLock
是一个实现了Lock
接口的类,它是一个可重入锁,意味着同一个线程可以多次获取同一把锁而不会发生死锁。
公平锁和非公平锁
ReentrantLock
提供了公平锁和非公平锁两种模式,默认是非公平锁。
创建方法:
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
ReentrantLock nonFairLock = new ReentrantLock(); // 默认是非公平锁
ReentrantLock nonFairLockExplicit = new ReentrantLock(false);
公平锁
当 ReentrantLock
被创建为公平锁时,线程尝试获取锁的顺序是按照请求锁的顺序来的,即先来先得。这意味着如果一个线程长时间持有一个锁,那么等待队列中的线程将按照它们请求锁的顺序获得锁。这有助于防止线程饥饿,即某些线程长时间得不到锁的情况。
initialTryLock
方法尝试在没有任何先入队线程!hasQueuedThreads()
的情况下获取锁。它首先尝试通过 compareAndSetState
方法将同步状态从 0
更新为 1
。如果成功,表示锁未被其他线程持有,当前线程将设置为自己作为独占所有者线程。如果锁已经被其他线程持有,那么会检查是否是重入锁(即持有锁的线程是当前线程),如果是,则增加锁的计数。
如果 getState()
返回 0
(表示锁当前可用),并且 compareAndSetState
成功,那么线程将获取锁。如果不是重入锁,或者锁已经被其他线程持有,那么获取锁将失败。
非公平锁
非公平锁和公平锁在initialTryLock()
代码上区别是没有使用下面这个方法,在tryAcquire()
方法没有!hasQueuedPredecessors()
。
非公平锁是 ReentrantLock
的默认模式。在非公平锁模式下,线程获取锁的顺序是不一定的,新来的线程有可能立即获得锁,即使等待队列中已经有其他线程在等待。这种模式下,系统开销较小,因为线程不必严格按照请求顺序来获得锁,这可能导致更高的吞吐量和性能。
ReentrantLock
实现
ReentrantLock
内部的Sync
使用AQS(AbstractQueuedSynchronizer)来实现锁的获取和释放,这个后面会讲。
实现线程交替打印AB10次
示例代码
public class AlternatePrinting {
private static final Lock lock = new ReentrantLock();
private static final Condition conditionA = lock.newCondition();
private static final Condition conditionB = lock.newCondition();
private static boolean flag = true; // 控制打印顺序的标志位
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
for (int i = 0; i < 5; i++) {
lock.lock();
try {
while (!flag) {
conditionA.await();
}
System.out.print("A");
flag = false;
conditionB.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
Thread threadB = new Thread(() -> {
for (int i = 0; i < 5; i++) {
lock.lock();
try {
while (flag) {
conditionB.await();
}
System.out.print("B ");
flag = true;
conditionA.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
threadB.start();
threadA.start();
}
}
Condition
接口
Condition
接口与Lock
接口配合使用,提供了类似Object
的wait()
和notify()
方法的功能,但更为强大和灵活。Condition
可以用于线程间的条件等待和通知。Condition
的主要方法包括:
await()
:使当前线程等待,直到收到其他线程的通知或被中断。signal()
:唤醒一个等待线程。signalAll()
:唤醒所有等待线程。
ReadWriteLock
接口
ReadWriteLock
接口提供了读锁(共享锁)和写锁(排他锁)两种锁。读锁可以被多个读线程同时持有,而写锁一次只能被一个线程持有。ReadWriteLock
的主要实现类是ReentrantReadWriteLock
。
ReentrantReadWriteLock
类
ReentrantReadWriteLock
实现了ReadWriteLock
接口,它内部维护了两把锁:一把读锁和一把写锁。读锁和写锁都实现了Lock
接口,因此它们都可以像ReentrantLock
一样使用。ReentrantReadWriteLock
允许读线程并发访问,但在写线程访问时会阻塞所有读线程和写线程。
ReentrantReadWriteLock
是一个可重入的读写锁,允许多个线程同时持有读锁,但只允许一个线程持有写锁。当写锁被占用时,其他线程不能获取读锁或写锁;当读锁被占用时,其他线程可以获取读锁,但不能获取写锁。
这个类的主要特点和实现机制如下:
- 内部锁状态: 锁的状态被编码为一个整数,高16位表示共享锁(读锁)的计数,低16位表示独占锁(写锁)的计数。
- 公平性策略: 通过
FairSync
和NonfairSync
两个内部类,实现了公平锁和非公平锁两种策略。公平锁按照请求锁的顺序来分配,非公平锁允许线程插队。 - 重入性: 线程可以多次获取读锁或写锁,而不会造成死锁。锁的持有计数确保了重入。
- 锁的获取和释放:
tryAcquireShared
和tryReleaseShared
方法用于获取和释放读锁。tryAcquire
和tryRelease
方法用于获取和释放写锁。这些方法通过CAS操作来更新锁的状态。 - 条件变量: 通过
newCondition
方法创建条件变量,线程可以在条件变量上等待或唤醒。 - 锁的计数和所有权: 提供了方法来查询锁的持有计数和当前锁的持有线程。
- 线程局部存储: 使用
ThreadLocal
来存储每个线程的读锁计数,这避免了每次都需要查询线程的局部存储。 - 锁的尝试获取:
tryWriteLock
和tryReadLock
方法提供了非阻塞的锁获取方式,如果锁不可用,则立即返回false。
内部实现类
ReentrantReadWriteLock
类的两个内部实现类:ReadLock
和WriteLock
的定义。这两个类实现了Lock
接口,用于获取和释放读锁和写锁。
ReadLock
类
不支持条件变量:
newCondition()
:由于读锁不支持条件变量,该方法抛出UnsupportedOperationException
。
锁的状态描述:
toString()
:返回一个描述读锁状态的字符串,包括持有的读锁数量。
tryReadLock()
流程图:
WriteLock
类
支持条件变量:
newCondition()
:返回一个新的条件变量,用于在写锁上进行等待和通知操作。
锁的状态描述:
toString()
:返回一个描述写锁状态的字符串,包括是否被锁定以及持有写锁的线程名称。
synchronized和ReentrantLock的区别
底层实现
synchronized是Java的一个内置锁机制,它通过JVM层面的monitor对象来实现,基于进入和退出Monitor对象(monitorenter和monitorexit指令)。每个对象都可以作为一把锁,当线程进入synchronized块或方法时,它会自动获得这把锁,当线程退出同步块或方法时,它会自动释放锁。synchronized的实现涉及到锁的升级策略,从无锁状态开始,可以升级为偏向锁、轻量级锁(自旋锁),最后在竞争激烈的情况下升级为重量级锁,即向操作系统申请锁。
ReentrantLock是Java 5引入的java.util.concurrent.locks包中的一个类,它提供了比synchronized更灵活的锁机制。ReentrantLock基于CAS(Compare And Swap)操作和volatile关键字来实现锁。CAS操作确保了线程操作的原子性,而volatile关键字保证了数据的可见性。
使用方式
使用synchronized非常简单,直接在方法或代码块前加上synchronized关键字即可。
synchronized (new Object()) {
// 同步代码块
}
与synchronized不同,使用ReentrantLock时,必须手动释放锁。通常在try块中获取锁,在finally块中释放锁,以确保即使在发生异常时也能释放锁。
private Lock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}
}
锁的可中断性
synchronized
是不可中断的锁,除非遇到异常或正常完成同步代码块。
ReentrantLock
可以中断,通过调用lockInterruptibly()
方法,可以让线程在等待锁的过程中响应中断。
公平性
synchronized
是非公平锁,锁的获取顺序不一定与线程请求锁的顺序一致。
ReentrantLock
可以选择性地创建公平锁或非公平锁,公平锁会按照线程请求锁的顺序来分配锁。
查看代码
private final Lock lock = new ReentrantLock(true); // true表示创建公平锁
public void fairLock() {
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}
}
条件绑定
synchronized
不能绑定条件,它使用Object
类的wait()
、notify()
和notifyAll()
方法来等待和通知线程。
查看代码
public synchronized void methodA() {
try {
wait(); // 等待通知
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public synchronized void methodB() {
notify(); // 发出通知
}
ReentrantLock
可以与多个Condition
对象绑定,提供更精细的线程等待/通知机制。
查看代码
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void methodA() {
lock.lock();
try {
condition.await(); // 等待条件
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public void methodB() {
lock.lock();
try {
condition.signal(); // 发出信号
} finally {
lock.unlock();
}
}
锁的状态管理
synchronized
的锁信息保存在对象头中,包括锁的重入次数等。synchronized
的锁状态管理是内置的,无法直接访问。
ReentrantLock
通过一个int类型的state
来跟踪锁的状态,包括锁是否被持有、重入次数等。
private final Lock lock = new ReentrantLock();
public void checkLockState() {
System.out.println("Is locked: " + lock.isLocked());
System.out.println("Is held by current thread: " + lock.isHeldByCurrentThread());
System.out.println("Queue length: " + lock.getQueueLength());
}