如何监控和调优锁的等待队列?
                           
天天向上
发布: 2025-07-20 11:04:28

原创
837 人浏览过

在高并发系统中,锁等待队列监控与调优是保障系统性能和响应时间的关键手段,特别是在使用 ReentrantLocksynchronizedStampedLock 等显式或隐式锁时。

以下将从可观测性工具、源码接口、调优方法与最佳实践四个方面全面展开。


一、什么是“锁等待队列”?

当多个线程同时请求某个锁对象,而该锁已被占用时,未能成功获取锁的线程会进入一个等待队列,称为锁等待队列(Lock Wait Queue)。

  • 对于 AQS 实现(如 ReentrantLock),这些线程会加入 CLH 等待队列。
  • 对于 synchronized,这些线程会被加入对象监视器的 EntryListWaitSet

二、如何监控锁的等待队列?

1. JDK 内置监控工具

JDK 工具 jstack

jstack <pid> > thread-dump.txt

分析线程栈:

  • BLOCKED:被阻塞在锁上(未获得 monitor)
  • WAITING / TIMED_WAITING:调用了 wait()LockSupport.park()
  • 栈中可看到 at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(...) 等锁相关堆栈

示例输出:

"Thread-1" #12 prio=5 os_prio=0 tid=0x00007fbd6800c000 nid=0x4bd waiting for monitor entry [0x00007fbd5c3fc000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.example.LockExample.method(LockExample.java:25)

2. Java 编程 API(ThreadMXBean

使用 Java 提供的管理接口:

import java.lang.management.*;

ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
long[] ids = mbean.findMonitorDeadlockedThreads();
ThreadInfo[] infos = mbean.getThreadInfo(ids, true, true);

还可查看:

  • 哪些线程持有锁
  • 哪些线程被哪个锁阻塞

3. Arthas 实时监控(推荐)

阿里开源的强大 Java 诊断工具,支持运行时观察锁等待情况:

./as.sh

常用命令:

  • thread 查看线程状态
  • dashboard 实时监控
  • monitor 监控方法调用时间
  • watch 查看某段方法调用前后锁情况

4. 使用 JFR(Java Flight Recorder)

JFR 可以捕获线程等待、锁竞争事件,并在 JDK Mission Control 中可视化分析。

启动方式:

java -XX:StartFlightRecording=filename=record.jfr,settings=profile ...

在 GUI 中分析锁竞争:

  • LockInstance
  • Contended Lock
  • Lock Owner

三、锁等待队列调优策略

问题识别

现象可能问题
某些线程长时间 BLOCKED锁粒度过大,或某线程持锁太久
死锁(两个线程相互等待)获取锁顺序不一致,或资源获取不一致
大量线程频繁进入 WAITING 或 TIME_WAITING线程过多、资源竞争激烈、调度饱和

四、调优手段和方法

1. 减少锁粒度 / 拆分锁

大锁变小锁,避免竞争。例如按模块、数据分片或操作区间加锁。

// 不推荐
lock.lock();
try {
    // 操作 A + B
} finally {
    lock.unlock();
}

// 推荐
lockA.lock();
try {
    // 操作 A
} finally {
    lockA.unlock();
}

lockB.lock();
try {
    // 操作 B
} finally {
    lockB.unlock();
}

2. 使用 tryLock() 设置超时控制

避免线程无限等待:

if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
    try {
        // 处理任务
    } finally {
        lock.unlock();
    }
} else {
    // 放弃处理或入队重试
}

3. 使用读写锁 ReentrantReadWriteLock

如果读操作远多于写操作,可提升并发度:

ReadWriteLock rwLock = new ReentrantReadWriteLock();
rwLock.readLock().lock();   // 多线程并发读
rwLock.writeLock().lock();  // 写独占

4. 使用 StampedLock 替代传统锁(更轻量)

StampedLock 提供乐观读锁:

StampedLock lock = new StampedLock();
long stamp = lock.tryOptimisticRead();
if (lock.validate(stamp)) {
    // 没有写入发生,可以放心使用数据
} else {
    // 获取悲观读锁
    stamp = lock.readLock();
    try {
        // 读取操作
    } finally {
        lock.unlockRead(stamp);
    }
}

5. 限制线程池最大线程数,减少争用

例如:使用 ThreadPoolExecutorcorePoolSize 限制同时竞争锁的线程数。


五、检测死锁情况

使用 JDK 提供 API:

long[] deadlockedThreads = mbean.findDeadlockedThreads();
if (deadlockedThreads != null) {
    for (long tid : deadlockedThreads) {
        ThreadInfo ti = mbean.getThreadInfo(tid);
        System.out.println("Deadlocked: " + ti.getThreadName());
    }
}

六、参考资料


总结口诀

锁监控,先看 jstack;
Arthas 实时显神通;
死锁可查 MXBean,
调优粒度、读写分、tryLock 辅助抢时空。


更多详细内容请关注其他相关文章!

发表回复 0

Your email address will not be published. Required fields are marked *