Java线程栈中的锁信息

分析运行时Java应用性能问题或死锁问题时,获取线程栈中锁信息是最基本的手段。JDK5.0之后加锁存在两种方法:1.Jvm内置的synchronized 2. Java并发包中相关同步类。本篇主要讨论线程栈中表现出来的锁的信息,对于我们分析Java性能或死锁提供哪些线索。

情况1: Object.wait()/ Object.wait(int timeout)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class InvokeWait {
public static void main(String[] args){
Object scarce = new Object();
Thread t = new Thread(new SyncWaiter(scarce));
t.setName("Test-Thread-1");
t.start();
}
}

class SyncWaiter implements Runnable {
privateObject scarce;
publicSyncWaiter(Object scarce){
this.scarce = scarce;
}

@Override
publicvoidrun() {
synchronized (scarce) {
try {
scarce.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1
2
3
4
5
6
7
8
"Test-Thread-1" #10 prio=5os_prio=0 tid=0x0000000058709800 nid=0x1d14 in Object.wait() [0x000000005930f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d7ac6058>(a java.lang.Object)
at java.lang.Object.wait(Object.java:502)
at org.guojje.SyncWaiter.run(InvokeWait.java:23)
- locked <0x00000000d7ac6058> (ajava.lang.Object)
at java.lang.Thread.run(Thread.java:745)

这种情况下,线程处于WAITING状态,等待获取对象锁,该线程必须先锁定了该对象(即进入synchronized区域),才可以调用wait方法(画重点),并在调用wait方法后释放对象锁,并进入对象锁Wait Set队列等待被唤醒。WAITING状态的线程必须有对应的notify唤醒。这与后面遇到Blocked状态的线程不同,Blocked是不需要唤醒的。
备注:
1) 每一个Java对象与生俱来带有唯一一把对象锁(叫Intrinsic lock或Monitor)。
2) (a java.lang.Object)表示资源的类名,这里为一个Object对象。
3) 当调用带有时间参数的Wait方法,这时线程状态显示TIMED_WAITING。
4)Wait Set队列与下面的Entry Set队列的含义,可自行Google。

情况2: 资源争用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Share {
public static void main(String[] args){
Object scarce = new Object();
Thread thread = new Thread(new SyncLock2(scarce));
thread.setName("Test-thrad-1");
Thread thread2 = new Thread(new SyncLock2(scarce));
thread2.setName("Test-thrad-2");
thread.start();
thread2.start();
}
}
class SyncLock2 implements Runnable {
private Object scarce;
public SyncLock2(Object scarce){
this.scarce = scarce;
}
@Override
public void run() {
synchronized (scarce) {
try {
System.in.read();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"Test-thrad-2" #11 prio=5os_prio=0 tid=0x0000000058a09000 nid=0x1c94 waiting for monitor entry [0x00000000595af000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.guojje.SyncLock2.run(Share.java:28)
- waiting to lock <0x00000000d7ac5f48>(a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
"Test-thrad-1" #10 prio=5os_prio=0 tid=0x0000000058a08800 nid=0x1eb8 runnable [0x00000000596ce000]
java.lang.Thread.State: RUNNABLE
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:246)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
- locked <0x00000000d79d8d38> (ajava.io.BufferedInputStream)
at org.guojje.SyncLock2.run(Share.java:28)
- locked <0x00000000d7ac5f48> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)

Test-thrad-2处于BLOCKED状态,说明线程Test-thrad-2进入synchronized临界区, 进入对象锁的Entry Set队列并等待对象锁0x00000000d79d8d38的释放。通过定位locked <0x00000000d79d8d38>,我们很容易发现哪个线程正在占用这把锁。在性能分析过程,如果发现大量的BLOCKED线程,我们就需要分析谁当前持有这把锁,而且长时间没有释放。 通过locked指示的代码行,我们很容易定位到加锁位置。然后进行降低锁粒度等手段,减少线程加锁的持续时间,从而优化性能。

情况3 死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Main {
public static void main(String[] args){
Object scarce = new Object();
Object scarce2 = new Object();
Thread thread = new Thread(new DeadLock(scarce, scarce2));
thread.setName("Test-thrad-1");
Thread thread2 = new Thread(new DeadLock(scarce2, scarce));
thread2.setName("Test-thrad-2");
thread.start();
thread2.start();
}
}
class DeadLock implements Runnable {
private Object scarce;
private Object scarce2;
public DeadLock(Object scarce,Object scarce2){
this.scarce = scarce;
this.scarce2 = scarce2;
}
@Override
public void run() {
while (true) {
synchronized (scarce) {
synchronized (scarce2) {
try {
new Socket("localhost",443);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
"Test-thrad-2" #11 prio=5 os_prio=0tid=0x00000000185c7800 nid=0x2b88 waiting for monitor entry [0x00000000196bf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.guojje.DeadLock.run(DeadLock.java:21)
- waiting to lock <0x00000000d7ac5e90>(a java.lang.Object)
- locked <0x00000000d7ac5ea0>(a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
"Test-thrad-1" #10 prio=5os_prio=0 tid=0x00000000185c2800 nid=0x281c waiting for monitor entry [0x00000000194bf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.guojje.DeadLock.run(DeadLock.java:21)
- waiting to lock <0x00000000d7ac5ea0>(a java.lang.Object)
- locked <0x00000000d7ac5e90> (ajava.lang.Object)
at java.lang.Thread.run(Thread.java:745)

Test-thrad-2线程拿到了0x00000000d7ac5ea0锁,在等待x00000000d7ac5e90锁,而Test-thrad-1刚好相反,发生了死锁的条件, 而且从线程栈很容易定位这些锁是在哪一行代码获取的,且又在哪一行代码阻塞的。JVM线程栈信息的尾端提供了更详细的死锁环的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Found one Java-level deadlock:
=============================
"Test-thrad-2":
waiting to lock monitor 0x0000000017235028 (object 0x00000000d7ac5e90, a java.lang.Object),
which is held by "Test-thrad-1"
"Test-thrad-1":
waiting to lock monitor 0x00000000172350d8 (object 0x00000000d7ac5ea0, a java.lang.Object),
which is held by "Test-thrad-2"
Java stack information for the threadslisted above:
===================================================
"Test-thrad-2":
at org.guojje.DeadLock.run(DeadLock.java:21)
- waiting to lock <0x00000000d7ac5e90>(a java.lang.Object)
- locked <0x00000000d7ac5ea0>(a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
"Test-thrad-1":
at org.guojje.DeadLock.run(DeadLock.java:21)
- waiting to lock <0x00000000d7ac5ea0>(a java.lang.Object)
- locked <0x00000000d7ac5e90> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.

你会发现JDK内置的synchronized加锁方式,你会发现问题分析死锁问题非常容易。这个例子就是因为竞争线程对资源的加锁顺序不同,导致死锁。而保持相同的加锁顺序是解决这类死锁问题关健。
对于内置的synchronized的同步方式,线程栈提供了足够的信息让我们分析与定位问题。但synchronized不够灵活等,使Java并发包更受程序序员青睐,但同时也为分析这类问题代来复杂性。

情况1

同样,我们先看一个ReentrantLock(重入锁的)await方法(与Ojbect.wait区别开来)的调用情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 public class Main {
public static void main(String[] args){
Thread thread = new Thread(new RTLock());
thread.setName("Test-thrad-1");
thread.start();
}
}
class RTLock implements Runnable {
private ReentrantLock lock= newReentrantLock();
public Condition scarce= lock.newCondition();
public RTLock() {
}
@Override
public void run() {
lock.lock();
try {
scarce.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}
}

1
2
3
4
5
6
7
8
"Test-thrad-1" #10 prio=5os_prio=0 tid=0x00000000189d0800 nid=0x2da8 waiting on condition [0x000000001968e000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parkingto wait for <0x00000000d7ac9088>(a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at com.guojje2.RTLock.run(RTLock.java:18)
at java.lang.Thread.run(Thread.java:745)

显示与synchronized有些不同。Synchronized显示为Wait on,这里- parking to wait for。
另外可以发现调用栈再也找不到locked字眼了,虽然你确认该线程一定locked过这把锁(否则无法调用await)。因为这个原因,为定位加锁位置带来难度。

情况2 资源争用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Main {
public static void main(String[] args){
ReentrantLock lock = new ReentrantLock();
Thread thread = new Thread(new RTLock(lock));
thread.setName("Test-thrad-1");
Thread thread2 = new Thread(new RTLock(lock));
thread2.setName("Test-thrad-2");
thread.start();
thread2.start();
}
}
class RTLock implements Runnable {
private ReentrantLock lock;
public RTLock(ReentrantLock lock) {
this.lock = lock;
}
@Override
public void run() {
lock.lock();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"Test-thrad-2" #11 prio=5os_prio=0 tid=0x0000000018802000 nid=0x3368 waiting on condition [0x000000001965f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000d7ac72a8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.guojje2.RTLock.run(Main.java:33)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
"Test-thrad-1" #10 prio=5os_prio=0 tid=0x0000000018801800 nid=0x2d3c runnable [0x000000001933f000]
java.lang.Thread.State: RUNNABLE
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:246)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
- locked <0x00000000d79d8d38> (a java.io.BufferedInputStream)
at com.guojje2.RTLock.run(Main.java:35)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- <0x00000000d7ac72a8> (ajava.util.concurrent.locks.ReentrantLock$NonfairSync)

如果是synchronized的方式,Test-thrad-2线程应该是Blocked状态,但这里为WAITING。这也是内置锁与JUL锁的不同。
注: 只要看Blocked状态的线程,代码一定是用synchronized作的同步。
在线程栈中多出了Locked ownable synchronizers条目。这是用jstack-l打印出来。(-l表示打印更详细的栈信息)。Java已经做了努力,弥补线程栈没有locked信息的缺陷。假如没有这个条目,无法确认Test-thrad-2等待的锁被哪一个线程持有。即便如此,还是无法定位加锁的代码位置,目前本人并没有找到更好的方法,只能翻代码了。
Ownable synchronizers这里的ownable的可以理解为,可被霸占的,即抢到就只属于你的。(有的邪恶哦?)
所以Locked ownable synchronizers只会列出排他锁,如ReentrantLock锁和ReentrantReadWriteLock的写锁,而对于ReentrantReadWriteLock读是不会显示的。因为他不算排他锁(ownable)。某些情况下,这一点也为定位问题加在难度。
注:- locked <0x00000000d79d8d38> (a java.io.BufferedInputStream)是BufferedInputStream.read上的锁不要搞混.

情况3 死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 public class Main {
public static void main(String[] args){
ReentrantLock lock = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
Thread thread = new Thread(new RTLock(lock, lock2));
thread.setName("Test-thrad-1");
Thread thread2 = new Thread(new RTLock(lock2, lock));
thread2.setName("Test-thrad-2");
thread.start();
thread2.start();
}
}

class RTLock implements Runnable {
private ReentrantLock lock;
private ReentrantLock lock2;
publicRTLock(ReentrantLock lock, ReentrantLock lock2) {
this.lock = lock;
this.lock2 = lock2;
}
@Override
public void run() {
while (true) {
lock.lock();
try {
lock2.lock();
try {
} finally {
lock2.unlock();
}
} finally {
lock.unlock();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
"Test-thrad-2" #11 prio=5os_prio=0 tid=0x0000000018871000 nid=0x2588 waiting on condition [0x00000000198ef000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000d7ac7388> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.guojje2.RTLock.run(Main.java:35)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- <0x00000000d7ac73b8> (ajava.util.concurrent.locks.ReentrantLock$NonfairSync)
"Test-thrad-1" #10 prio=5os_prio=0 tid=0x0000000018870000 nid=0x2fb0 waiting on condition [0x00000000184ae000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000d7ac73b8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.guojje2.RTLock.run(Main.java:35)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- <0x00000000d7ac7388> (ajava.util.concurrent.locks.ReentrantLock$NonfairSync)
Found one Java-level deadlock:
=============================
"Test-thrad-2":
waiting for ownable synchronizer 0x00000000d7ac71a0,(a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "Test-thrad-1"
"Test-thrad-1":
waiting for ownable synchronizer 0x00000000d7ac71d0, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "Test-thrad-2"
.
Java stack information for the threadslisted above:
===================================================
"Test-thrad-2":
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000d7ac71a0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.guojje2.RTLock.run(Main.java:41)
at java.lang.Thread.run(Thread.java:745)
"Test-thrad-1":
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000d7ac71d0> (ajava.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.guojje2.RTLock.run(Main.java:41)
at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.

借助Locked ownable synchronizers,确认哪些线程位于死锁的环形竞争中,还是比较容易。先到这吧。。