Token如何把你的手机账单改写成AI时代的电费单
2026/7/27 2:46:59
Object lock1 = new Object(); Object lock2 = new Object(); // 线程A new Thread(() -> { synchronized (lock1) { System.out.println("Thread A: Holding lock 1..."); try { Thread.sleep(100); } catch (InterruptedException e) {} System.out.println("Thread A: Waiting for lock 2..."); synchronized (lock2) { System.out.println("Thread A: Acquired lock 2"); } } }).start(); // 线程B new Thread(() -> { synchronized (lock2) { System.out.println("Thread B: Holding lock 2..."); try { Thread.sleep(100); } catch (InterruptedException e) {} System.out.println("Thread B: Waiting for lock 1..."); synchronized (lock1) { System.out.println("Thread B: Acquired lock 1"); } } }).start();上述代码模拟了经典的死锁情形,执行后程序将无法正常退出。| 挑战类型 | 描述 |
|---|---|
| 诊断难度 | 需人工分析大量线程堆栈信息 |
| 预防机制 | 缺乏语言层面的强制约束 |
jstack -l <pid>该命令输出指定进程 ID 的完整线程快照,-l参数启用长格式输出,包含额外的锁信息,如持有的监视器和等待的同步对象。RUNNABLE、WAITING、TIMED_WAITING和BLOCKED。每个状态反映线程在调度中的所处阶段。"WorkerThread-1" #12 prio=5 os_prio=0 tid=0x00007f8a8c0b9000 nid=0x4e3b waiting for monitor entry java.lang.Thread.State: BLOCKED (on object monitor) at com.example.Counter.increment(Counter.java:25) - waiting to lock <0x000000076b0a7ee8> (a com.example.Counter) at com.example.Task.run(Task.java:15)上述输出中: -tid表示线程ID; -nid是本地线程ID,用于关联操作系统线程; -waiting for monitor entry指出线程正尝试获取对象监视器; - 堆栈轨迹显示阻塞发生在Counter.java第25行的同步方法调用处。// 示例:简化版依赖关系检测逻辑 for (ThreadInfo thread : threadInfos) { long waiter = thread.getThreadId(); long[] waitLocks = thread.getLockedSynchronizers(); for (long lock : waitLocks) { Long owner = lockToOwnerMap.get(lock); if (owner != null) { addEdge(waiter, owner); // 构建等待图 } } } detectCycle(dependencyGraph); // 深度优先搜索环路上述代码构建线程间的等待依赖关系。参数说明:threadInfos为 jstack 获取的线程快照,getLockedSynchronizers()返回该线程正在等待的同步器实例。| 工具 | 主要用途 | 输出内容 | 实时性 |
|---|---|---|---|
| jstack | 线程分析 | 线程栈跟踪 | 高 |
| jmap | 堆内存分析 | 对象实例分布 | 中 |
| jstat | JVM运行监控 | GC频率与内存使用 | 高 |
jstack -l 12345 > thread_dump.log上述命令获取进程 ID 为 12345 的 Java 应用线程快照,并保存至文件。参数-l提供更详细的锁信息,有助于识别死锁或竞争瓶颈。相比 jmap 的堆转储操作可能引发短暂停顿,jstack 对系统性能影响极小,适合频繁采样。jstack对线程状态的标识更加精确,特别是在区分WAITING和TIMED_WAITING时引入了更细粒度的上下文信息。"main" #1 prio=5 os_prio=0 cpu=1234.56ms elapsed=10.12s tid=0x00007f8a8c00b000 nid=0x1a2b runnable在JDK 8中,nid(native thread ID)以十六进制显示,而JDK 11+增强了该字段的可读性,并补充了elapsed时间统计。jstack调用更安全,避免因频繁采样引发性能抖动。public class DeadlockExample { private static final Object lock1 = new Object(); private static final Object lock2 = new Object(); public static void main(String[] args) { Thread t1 = new Thread(() -> { synchronized (lock1) { System.out.println("Thread 1: Holding lock 1..."); try { Thread.sleep(100); } catch (InterruptedException e) {} System.out.println("Thread 1: Waiting for lock 2..."); synchronized (lock2) { System.out.println("Thread 1: Acquired lock 2"); } } }); Thread t2 = new Thread(() -> { synchronized (lock2) { System.out.println("Thread 2: Holding lock 2..."); try { Thread.sleep(100); } catch (InterruptedException e) {} System.out.println("Thread 2: Waiting for lock 1..."); synchronized (lock1) { System.out.println("Thread 2: Acquired lock 1"); } } }); t1.start(); t2.start(); } }上述程序中,线程t1先获取lock1再请求lock2,而t2相反。由于sleep调用确保了两个线程在同时尝试获取第二个锁时彼此阻塞,从而形成死锁。
jstack是JDK自带的Java线程堆栈分析工具,适用于排查死锁、线程阻塞等问题。在Linux或Windows命令行中均可使用,前提是已配置JAVA_HOME并确保目标Java进程正在运行。
首先通过jps命令列出本地Java进程:
jps -l # 输出示例: # 12345 org.apache.catalina.startup.Bootstrap其中数字部分即为PID,后续将用于jstack调用。
使用如下命令导出指定进程的线程堆栈:
jstack 12345 > thread_dump.txt该命令将PID为12345的Java进程所有线程状态输出至文件。若需强制打印(如进程挂起),可加-F参数(仅限HotSpot)。
| 参数 | 作用 |
|---|---|
| -l | 显示额外的锁信息,如监视器和持有者 |
| -F | 强制输出堆栈,当正常请求无响应时使用 |
| -m | 混合模式,同时显示Java和本地C++栈帧 |
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); long[] threadIds = threadMXBean.getAllThreadIds(); for (long tid : threadIds) { ThreadInfo ti = threadMXBean.getThreadInfo(tid, 100); System.out.println(ti.getThreadName() + ": " + ti.getStackTrace()[0]); }上述代码获取所有活动线程的ID,并提取其最新的100帧堆栈信息。ThreadMXBean 提供了无需暂停JVM即可读取线程状态的能力,适用于生产环境。jstack工具可导出线程快照,其中明确标注了线程状态与持有的监视器:"Thread-1" #12 prio=5 tid=0x00007f8c8c0a1000 nid=0x7b43 waiting for monitor entry [0x00007f8c9d4e] java.lang.Thread.State: BLOCKED (on object monitor) at com.example.Counter.increment(Counter.java:25) - waiting to lock <0x000000076b0a1230> (a com.example.Counter) at com.example.Worker.run(Task.java:18)该输出表明 Thread-1 在Counter.increment()方法处等待获取对象锁,而该锁已被其他线程持有。- locked <monitor>记录的内存地址是否与等待线程一致pprof获取 goroutine 堆栈信息:import _ "net/http/pprof" // 访问 /debug/pprof/goroutine 可查看所有阻塞中的 goroutine该机制帮助定位处于semacquire或sync.Mutex.Lock等系统调用中的协程。// 示例:记录线程与内存地址的绑定关系 void* tracked_malloc(size_t size) { pthread_t tid = pthread_self(); void* ptr = malloc(size); log_allocation(tid, ptr, size); // 记录日志 return ptr; }上述代码在每次内存分配时记录线程ID与返回地址,便于后续回溯分析。synchronized (resourceA) { // 持有 resourceA Thread.sleep(100); synchronized (resourceB) { // 等待 resourceB // 执行操作 } } // 另一线程反向获取 resourceB -> resourceA,形成循环等待上述代码中两个线程以相反顺序获取锁,极易引发死锁。关键在于避免不一致的加锁顺序。| 策略 | 说明 |
|---|---|
| 固定加锁顺序 | 所有线程按统一顺序请求资源 |
| 超时重试机制 | 使用 tryLock(timeout) 避免无限等待 |
| 死锁检测工具 | 借助 jstack 或 JConsole 实时分析线程状态 |
receivers: prometheus: config: scrape_configs: - job_name: 'app-metrics' static_configs: - targets: ['localhost:9090'] exporters: jaeger: endpoint: "jaeger-collector:14250" tls: insecure: true| 工具 | 适用场景 | 实时性 | 学习曲线 |
|---|---|---|---|
| Fluent Bit | 边缘节点轻量日志采集 | 毫秒级 | 低 |
| VictoriaMetrics | 高基数时间序列存储 | 亚秒级 | 中 |
kubectl describe pod <name>查看 Events →kubectl get events --sort-by=.lastTimestamp追踪调度器事件 → 检查 Node Taints 与 Pod Toleration 匹配逻辑 → 验证 ResourceQuota 是否耗尽命名空间配额。某金融客户曾因未配置memory.limit.in_bytescgroup 参数导致 kubelet 拒绝调度,最终通过cAdvisor /metrics/cadvisor端点确认内存子系统异常。