技术

synchorinzed的一个面试题

背景

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

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();
      }
    }
  }

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

现象

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

 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了一下进程,发现是出现了死锁

"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(),会是下面的清空

"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去掉,只保留一个锁即可