IOTXING

记录技术学习之路

0%

synchorinzed的一个面试题

背景

前两天一个朋友面试,遇到了一个题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.LinkedList;

public class Stack {
LinkedList list = new LinkedList();
public synchronized void push(Object x) {
synchronized(list) {
list.addLast( x );
notify();
}
}

public synchronized Object pop()
throws Exception {
synchronized(list) {
if( list.size() <= 0 ) {
wait();
}
return list.removeLast();
}
}
}

上面的代码,一般情况下工作正常,问什么时候会有问题,并且问题的根源是什么

现象

既然这么问,那么肯定是有问题的,下面就写代码复现一下

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
public static void main(String[] args)
{
Stack stack = new Stack();
new Thread(() -> {
System.out.println("push thread" + Thread.currentThread().getName());
while (true)
{
try
{
stack.push("asd");
} catch (Exception e)
{
System.out.println("sd");
}
}

}).start();
new Thread(() -> {
System.out.println("pop thread" + Thread.currentThread().getName());
while (true)
{
try
{
stack.pop();
} catch (Exception e)
{
e.printStackTrace();
}
}
}).start();
}

果不其然,代码跑起来之后就卡主了。dump了一下进程,发现是出现了死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"Thread-1" #12 prio=5 os_prio=31 tid=0x00007f8848873000 nid=0x5803 in Object.wait() [0x000070000a55b000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076ada6460> (a Stack)
at java.lang.Object.wait(Object.java:502)
at Stack.pop(Stack.java:32)
- locked <0x000000076ada6610> (a java.util.LinkedList)
- locked <0x000000076ada6460> (a Stack)
at Stack.lambda$main$1(Stack.java:69)
at Stack$$Lambda$2/381259350.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

"Thread-0" #11 prio=5 os_prio=31 tid=0x00007f884701c800 nid=0x5603 waiting for monitor entry [0x000070000a458000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Stack.push(Stack.java:14)
- waiting to lock <0x000000076ada6610> (a java.util.LinkedList)
- locked <0x000000076ada6460> (a Stack)
at Stack.lambda$main$0(Stack.java:53)
at Stack$$Lambda$1/1607521710.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

pop线程持有list的锁,等待获取到stack的锁,而push线程持有stack的锁,等待获取list的锁,因此就出现了死锁导致卡主。

而这种情况的产生,是因为在代码中调用了wait(),因为没有指明对象,所以默认的就是对当前对象执行wait()。因为我们在pop方法中用了两个synchronized,其实就是拿了两个锁,一个是当前对象的锁,一个是list的锁。直接调用wait,释放了this对象的锁,然后push线程拿到了,但是因为pop线程调用了wait之后挂起来了,所以list的锁也就没有释放掉。现在就属于pop等待this对象的锁,而push持有this对象,等待list的锁,因此死锁就产生了。
如果把wait改成list.wait(),会是下面的清空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"Thread-1" #15 prio=5 os_prio=0 cpu=31.25ms elapsed=4.21s tid=0x0000019b436f9000 nid=0x1a5c in Object.wait()  [0x0000005c5b0fe000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(java.base@13.0.2/Native Method)
- waiting on <0x00000006206104b0> (a java.util.LinkedList)
at java.lang.Object.wait(java.base@13.0.2/Object.java:326)
at Stack.pop(Stack.java:19)
- locked <0x00000006206104b0> (a java.util.LinkedList)
- locked <0x0000000620610240> (a Stack)
at Stack.lambda$main$1(Stack.java:36)
at Stack$$Lambda$16/0x0000000800ba5040.run(Unknown Source)
at java.lang.Thread.run(java.base@13.0.2/Thread.java:830)

"Thread-0" #14 prio=5 os_prio=0 cpu=0.00ms elapsed=4.21s tid=0x0000019b436f7000 nid=0x453c waiting for monitor entry [0x0000005c5affe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Stack.push(Stack.java:8)
- waiting to lock <0x0000000620610240> (a Stack)
at Stack.lambda$main$0(Stack.java:29)
at Stack$$Lambda$15/0x0000000800ba4440.run(Unknown Source)
at java.lang.Thread.run(java.base@13.0.2/Thread.java:830)

情况刚好相反,pop等待list的锁,push等待stack的锁。
因此如果想要避免这种情况,需要把pop函数上面的synchronized去掉,只保留一个锁即可