4.线程安全与数据同步

什么是synchronized

  • synchronized关键字提供了一种互斥的机制, 能够确保共享变量的互斥访问,从而防止数据不一致的问题出现
  • synchronized关键字包括monitor enter和monitor exit两个JVM指令, 它能够保证在任何时候任何线程执行到monitor enter成功之前都必须从主内存获取数据,而不是从缓存中, 在monitor exit运行成功之后, 共享变量被更新后的值必须刷入主内存。
  • synchronized的指令严格遵守 java happens-befor规则, 一个monitor exit指令之前必须有一个monitor enter

synchronized关键字的用法

  • 同步方法 [default | public | private | protected] synchronized [static] type method()
public synchronized void sync() {
    ...
}

public synchronized static void staticSync() {
    ...
}
  • 同步代码块
private final Object MUTEX = new Object();
public void sync() {
    synchronized(MUTEX) {
        ...
    }
}

Monitorenter

每一个对象都与一个monitor相关联, 一个monitor的lock的锁只能被一个线程在同一时间获得, 在一个线程尝试获得与对象相关联monitor的所有权时会发生如下的几种事情。

  • 如果monitor的计算器为0, 则意味着该monitor的lock还没有获获得,某个线程获得之后将立即对该计数器加1, 从此该线程就是这个monitor的所有者了。
  • 如果一个已经拥有该 monitor所有权的线程重入,则会导致monitor的计数器再次累加。
  • 如果monitor已经被其他线程所拥有, 则其他线程尝试获取该monitor的所有权时,会被陷入阻塞状态直到monitor计数器变为0,才能再次尝试获取对monitor的所有权。

Monitorexit

释放对monitor的所有权, 想要释放对某个对象关联的monitor的所有权的前提是,你已经获得了所有权,释放monitor所有权的过程比较简单, 就是将monitor的计数器减1. 如果monitor的计数器结果为0 , 那就意味着该 线程下不再拥有对该monitor的所有权,

其他被该monitor阻塞的线程将再次尝试获取对该monitor的所有权。

使用synchronized需要注意的问题

  1. 与monitor关联的对象为空
// 注意此处对象初始化为null
private final Object mutex = null;

public void syncMethod() {
    synchronized(mutex) {
        ....
    }
}

对象为null, 也没没有与对象关联的monitor了

  1. synchronized作用域太大

由于synchronized关键字存在排他性, 也就是说所有的线程必须串行地经过synchronized保护的共享区域, 如果synchronized作用域越大,则其效率越低,甚至会丧失并发优势。

// 注意此处是Runnable的实例
public static class Task implements Runnable {
    @Override
    public synchronized void run() {
        //
    }
}

上面的代码对整个线程的执行逻辑单无都进行了同步, 从而丧失了并发能力。应尽可能地只作用于共享资源的读写作用域。

  1. 不同的monitor企图锁相同的方法
public static class Task implements Runnable {
    private final Object MUTEX = new Object();
    @Override
    public void run() {
        synchronized(MUTEX) {
            // ...
        }
        // ...
    }
    
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(Task::new).start();
        }
    }
}

上面的代码构造了五个线程, 同时也构造了五个Runnable实例, Runnable作为线程逻辑执行单元传递给Thread.

由于线程之间进行monitor lock的争抢只能发生在与monitor关联的同一个对象上,但上面的代码,每一个线程争抢的monitor关联的对象都是独立的,因不能互斥。

  1. 多个锁的交叉导致死锁

private final Object MUTEX_READ = new Object();
private final Object MUTEX_WRITE = new Object();

public void read() {
    synchronid(MUTEX_READ) {
        synchronized(MUTEX_WRITE) {
            // ...   
        }
    }
}

public void write() {
    synchronized(MUTEX_WRITE) {
        synchronized(MUTEX_READ) {
            // ...
        }
    }
}

以上代码, 在自已的锁未释放之前,又互相去尝试获取对方的锁,就有可能死锁。

This Monitor 和 Class Monitor的详细介绍。

public class ThisMonitor {
    public synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + " enter method1");

        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void method2() {
        System.out.println(Thread.currentThread().getName() + " enter method2");

        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ThisMonitor thisMonitor = new ThisMonitor();
        new Thread(thisMonitor::method1, "T1").start();
        new Thread(thisMonitor::method2, "T2").start();
    }
}

以上代码, 同一个类中的两个方法都用synchronized修改, 然后启动两个线程,各调用一个方法,结果会怎样 ?

运行会发现,同一时间只有一个方法被调用了。 我们通过jstack打印堆栈信息,会发现两个线程争抢的都是thisMonitor 对象关联的monitor lock

E:\workspace\shinley-concurrency\target\classes>jps
278004
287700 Launcher
288532 ThisMonitor
289812 Jps
276540 RemoteMavenServer
278476 JConsole

E:\workspace\shinley-concurrency\target\classes>jstack 288532

"T2" #12 prio=5 os_prio=0 tid=0x000000001dc65800 nid=0x46fd4 waiting for monitor
 entry [0x000000001e72f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.shinley.concurrent.chapter4.ThisMonitor.method2(ThisMonitor.java:
17)
        - waiting to lock <0x000000076b391838> (a com.shinley.concurrent.chapter
4.ThisMonitor)
        at com.shinley.concurrent.chapter4.ThisMonitor$$Lambda$2/1078694789.run(
Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"T1" #11 prio=5 os_prio=0 tid=0x000000001dc83000 nid=0x469dc waiting on conditio
n [0x000000001e62f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.shinley.concurrent.chapter4.ThisMonitor.method1(ThisMonitor.java:
10)
        - locked <0x000000076b391838> (a com.shinley.concurrent.chapter4.ThisMon
itor)
        at com.shinley.concurrent.chapter4.ThisMonitor$$Lambda$1/990368553.run(U
nknown Source)
        at java.lang.Thread.run(Thread.java:748)

精简之后,我们只看T1和T2两个线程的堆栈,我们注意下面两行:

T2线程

- waiting to lock <0x000000076b391838> (a com.shinley.concurrent.chapter
4.ThisMonitor)

T1线程

- locked <0x000000076b391838> (a com.shinley.concurrent.chapter4.ThisMon
itor)

发现T1获得了锁,T2在等待锁, 而他们对应的锁都是<0x000000076b391838>(a com.shinley.concurrent.chapter 4.ThisMonitor), 也就是说都同一个ThisMonitor的实例对应的monitor对应的锁。

class monitor

package com.shinley.concurrent.chapter4;

import java.util.concurrent.TimeUnit;

public class ClassMonitor {
    public static synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + " enter method1");

        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void method2() {
        System.out.println(Thread.currentThread().getName() + " enter method2");

        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        
//        new Thread(ClassMonitor::method1, "T1").start();
//        new Thread(ClassMonitor::method2, "T2").start();
        
        ClassMonitor classMonitor01 = new ClassMonitor();
        ClassMonitor classMonitor02 = new ClassMonitor();
        new Thread(() -> classMonitor01.method1(), "T1").start();
        new Thread(() -> classMonitor02.method2(), "T2").start();
    }
}

上面的代码, synchronized 修饰的方法都是静态方法, 在main方法, 无论是用类直接调用,还是用对象调用的方式, 两个线程在同一时间只有一个线程进入。

使用jstack打印堆栈信息:

E:\workspace\shinley-concurrency\target\classes>jps
292948 Launcher
292976 ClassMonitor
276540 RemoteMavenServer
278476 JConsole
293244 Jps

E:\workspace\shinley-concurrency\target\classes>jstack 292976

"T2" #12 prio=5 os_prio=0 tid=0x000000001daf8800 nid=0x47930 waiting for monitor
 entry [0x000000001e54f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.shinley.concurrent.chapter4.ClassMonitor.method2(ClassMonitor.jav
a:17)
        - waiting to lock <0x000000076b38ee28> (a java.lang.Class for com.shinle
y.concurrent.chapter4.ClassMonitor)
        at com.shinley.concurrent.chapter4.ClassMonitor.lambda$main$1(ClassMonit
or.java:34)
        at com.shinley.concurrent.chapter4.ClassMonitor$$Lambda$2/1096979270.run
(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"T1" #11 prio=5 os_prio=0 tid=0x000000001daf6000 nid=0x4792c waiting on conditio
n [0x000000001e44e000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.shinley.concurrent.chapter4.ClassMonitor.method1(ClassMonitor.jav
a:10)
        - locked <0x000000076b38ee28> (a java.lang.Class for com.shinley.concurr
ent.chapter4.ClassMonitor)
        at com.shinley.concurrent.chapter4.ClassMonitor.lambda$main$0(ClassMonit
or.java:33)
        at com.shinley.concurrent.chapter4.ClassMonitor$$Lambda$1/1324119927.run
(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

精简之后,我们只看T1和T2线程的堆栈, 只关注下面两行

T2线程

- waiting to lock <0x000000076b38ee28> (a java.lang.Class for com.shinle
y.concurrent.chapter4.ClassMonitor)

T1线程

- locked <0x000000076b38ee28> (a java.lang.Class for com.shinley.concurr
ent.chapter4.ClassMonitor)

我们发现T1线程获得了锁, T2线程在等待锁, 它们竞争的都是同一个锁。我们还发现,后面多了a java.lang.Class for, 说明T1和T2竞争的是ClassMonitor.clss实例关联的 monitor lock.

对上面的代码,稍作修改,也具有同样的效果

package com.shinley.concurrent.chapter4;

import java.util.concurrent.TimeUnit;

public class ClassMonitor {
    public static synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + " enter method1");

        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static  void method2() {
        synchronized (ThisMonitor.class) {
            System.out.println(Thread.currentThread().getName() + " enter method2");
    
            try {
                TimeUnit.MINUTES.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

//        new Thread(ClassMonitor::method1, "T1").start();
//        new Thread(ClassMonitor::method2, "T2").start();

        ClassMonitor classMonitor01 = new ClassMonitor();
        ClassMonitor classMonitor02 = new ClassMonitor();
        new Thread(() -> classMonitor01.method1(), "T1").start();
        new Thread(() -> classMonitor02.method2(), "T2").start();
    }
}

死锁的原因及诊断

程序死锁

  1. 交叉锁可导致程序出现死锁
    • 线程A持有R1和销,等待获取R2的锁, 线程B持有R2的锁,等待获取R1的锁, 这种情况容易导致发生死锁。
  2. 内存不足
    • 当并发请求申请系统可用内存时, 如果此时系统内存不足,则可能出现死锁。 例如:两个线程T1和T2,执行某个任务,其中T1已经获得了10MB内存,T2获取了20M内存,如果两个线和的执行单元都需要30MB的内存, 但是剩余可用的内存刚好为20M, 那么两个线程都有可能在等待彼此能够释放内存资源。
  3. 一问一答式的数据交换
    • 服务端开启某个端口, 等待客户端访问, 客户端发送请求立即等待接收, 由于某种原因服务端错过了客户端的请求,仍然在等待一问一答式的数据交换, 些时服务端和客户端都在等待着对主发送数据。
  4. 数据库锁
    • 无论是数据库表级别的锁,还是行级别的锁,比如某个线程执行for update语句退出了事务,其他线程访问该数据库时都将陷入死锁。
  5. 文件锁
    • 某个线程获得了文件锁后,意外退出,其他读取该 文件的线程也会进入死锁直到系统释放文件句柄资源。
  6. 死循环引起的死锁
    • 程序由于代码原因或者对某些异常处理不得当,进入了死循环,虽然查看线程堆栈信息不会发现任何死锁迹象, 但是程序不工作,CPU占有率又居高不下,这种死锁一般称为系统假死。 这是一种最为致命也是最难排查的死锁现像。 由于重现困难,进程对系统资源的使用量又达到了极限, 想要做出dump有时候也是非常困难的。

程序死锁举例

package com.shinley.concurrent.chapter4;

public class DeadLock {
    private final Object MUTEX_READ = new Object();
    private final Object MUTEX_WRITE = new Object();

    public void read() {
        synchronized (MUTEX_READ) {
            System.out.println(Thread.currentThread().getName() + " get Read Lock");
            synchronized (MUTEX_WRITE) {
                System.out.println(Thread.currentThread().getName() + " get Write Lock");
            }
            System.out.println(Thread.currentThread().getName() + " release  Write Lock");
        }
        System.out.println(Thread.currentThread().getName() + " relase Read Lock");
    }

    public void write() {
        synchronized (MUTEX_WRITE) {
            System.out.println(Thread.currentThread().getName() + " get Read Lock");
            synchronized (MUTEX_READ) {
                System.out.printf(Thread.currentThread().getName() + "get Write Lock");
            }
            System.out.println(Thread.currentThread().getName() + " release Read Lock");
        }
        System.out.println(Thread.currentThread().getName() + " release Write Lock");
    }

    public static void main(String[] args) {
        final DeadLock deadLock = new DeadLock();
        new Thread(() -> {
            while (true) {
                deadLock.read();
            }
        }, "READ-THREAD").start();

        new Thread(() -> {
            while (true) {
                deadLock.write();
            }
        }).start();
    }
}

上面的程序都是在自已获得了锁之后,又尝试去获取对方的锁,这样就有可能导致死锁。

HashMap 导致的死锁

package com.shinley.concurrent.chapter4;

import java.util.HashMap;

public class HashMapDeadLock {
    private final HashMap<String, String> map = new HashMap<>();

    public void add(String key, String value) {
        this.map.put(key, value);
    }

    public static void main(String[] args) {
        final HashMapDeadLock hmdl = new HashMapDeadLock();

        for (int x = 0; x < 2; x++) {
            new Thread(() -> {
                for (int i = 1; i < Integer.MAX_VALUE; i++) {
                    hmdl.add(String.valueOf(i), String.valueOf(i));
                    System.out.println(Thread.currentThread().getName() +"###"+ i);
                }
            }).start();
        }
    }
}

以上代码运行了几次,也没出现死锁, 但是在多线程下操作map时,还是推荐使用ConcurrentHashMap或者使用Collections.synchronizedMap来代替

死锁诊断

  1. 交叉锁引起的死锁 运行DeadLock代码, 程序陷入死锁, 打开jstack工具或者jconsole工具,会直接发现死锁的信息
2018-11-08 17:45:19
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode):

"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000002623000 nid=0x48d78 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"Thread-0" #12 prio=5 os_prio=0 tid=0x000000001d417000 nid=0x48f60 waiting for monitor entry [0x000000001de6f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.shinley.concurrent.chapter4.DeadLock.write(DeadLock.java:22)
	- waiting to lock <0x000000076b3915d8> (a java.lang.Object)
	- locked <0x000000076b3915e8> (a java.lang.Object)
	at com.shinley.concurrent.chapter4.DeadLock.lambda$main$1(DeadLock.java:39)
	at com.shinley.concurrent.chapter4.DeadLock$$Lambda$2/1096979270.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
	- None

"READ-THREAD" #11 prio=5 os_prio=0 tid=0x000000001d414000 nid=0x48fec waiting for monitor entry [0x000000001dd6f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at com.shinley.concurrent.chapter4.DeadLock.read(DeadLock.java:11)
	- waiting to lock <0x000000076b3915e8> (a java.lang.Object)
	- locked <0x000000076b3915d8> (a java.lang.Object)
	at com.shinley.concurrent.chapter4.DeadLock.lambda$main$0(DeadLock.java:33)
	at com.shinley.concurrent.chapter4.DeadLock$$Lambda$1/1324119927.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
	- None

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x000000001d129000 nid=0x48f50 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"C1 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001d11f800 nid=0x48e18 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001d0b8800 nid=0x489f4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001d0c9000 nid=0x47fe0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001d0be800 nid=0x48acc runnable [0x000000001d76e000]
   java.lang.Thread.State: RUNNABLE
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
	at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
	at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
	- locked <0x000000076b4d05f8> (a java.io.InputStreamReader)
	at java.io.InputStreamReader.read(InputStreamReader.java:184)
	at java.io.BufferedReader.fill(BufferedReader.java:161)
	at java.io.BufferedReader.readLine(BufferedReader.java:324)
	- locked <0x000000076b4d05f8> (a java.io.InputStreamReader)
	at java.io.BufferedReader.readLine(BufferedReader.java:389)
	at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

   Locked ownable synchronizers:
	- None

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001bd2d000 nid=0x48ab8 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001d080800 nid=0x48b08 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
	- None

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000002719000 nid=0x48020 in Object.wait() [0x000000001d06e000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000076b208ed0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
	- locked <0x000000076b208ed0> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:212)

   Locked ownable synchronizers:
	- None

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000002713000 nid=0x485ec in Object.wait() [0x000000001cf6f000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x000000076b206bf8> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:502)
	at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
	- locked <0x000000076b206bf8> (a java.lang.ref.Reference$Lock)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

   Locked ownable synchronizers:
	- None

"VM Thread" os_prio=2 tid=0x000000001bce6800 nid=0x48ecc runnable 

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002638800 nid=0x48c10 runnable 

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000000000263a000 nid=0x4826c runnable 

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000263b800 nid=0x487c0 runnable 

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000263d000 nid=0x48b64 runnable 

"VM Periodic Task Thread" os_prio=2 tid=0x000000001d12f000 nid=0x48cdc waiting on condition 

JNI global references: 316


Found one Java-level deadlock:
=============================
"Thread-0":
  waiting to lock monitor 0x000000001bd0d5a8 (object 0x000000076b3915d8, a java.lang.Object),
  which is held by "READ-THREAD"
"READ-THREAD":
  waiting to lock monitor 0x000000001bd10048 (object 0x000000076b3915e8, a java.lang.Object),
  which is held by "Thread-0"

Java stack information for the threads listed above:
===================================================
"Thread-0":
	at com.shinley.concurrent.chapter4.DeadLock.write(DeadLock.java:22)
	- waiting to lock <0x000000076b3915d8> (a java.lang.Object)
	- locked <0x000000076b3915e8> (a java.lang.Object)
	at com.shinley.concurrent.chapter4.DeadLock.lambda$main$1(DeadLock.java:39)
	at com.shinley.concurrent.chapter4.DeadLock$$Lambda$2/1096979270.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)
"READ-THREAD":
	at com.shinley.concurrent.chapter4.DeadLock.read(DeadLock.java:11)
	- waiting to lock <0x000000076b3915e8> (a java.lang.Object)
	- locked <0x000000076b3915d8> (a java.lang.Object)
	at com.shinley.concurrent.chapter4.DeadLock.lambda$main$0(DeadLock.java:33)
	at com.shinley.concurrent.chapter4.DeadLock$$Lambda$1/1324119927.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

jstack会在最后直接打印出 Found 1 deadlock. 上面就是死锁的线程堆栈。

  1. 死循环引起的死锁(假死)

这里只介绍linux下的排查方法

使用top命令查看cpu使用情况

打印进程号为pid的所有线程,线程命名用cup较高的会排在前面

top -p pid -H

把线程号转成16进制

printf "%x" 8247

使用jstack导出进程, cd

jstack pid > t.txt

最后在t.txt中搜索转换后的16进制的nid, 然后就可以查看相应的堆栈信息。