如何解决这个僵局

时间:2014-09-09 12:18:24

标签: java multithreading concurrency deadlock

我有两个主题。这两个线程必须调用send()(然后receive())或receive(),但这个代码有一个很好的死锁。有没有办法解决这个问题?

    public class C 
    {
        public static void main(String[] args) 
        {
            Z z1=new Z();
            Z z2=new Z();
            z1.setZ(z2);
            z2.setZ(z1);
            z1.start();
            z2.start();
        }
    }

    class Z extends Thread
    {
        Z z;
        Object lock=new Object();

        public void setZ(Z zz)
        {
            z=zz;
        }

        public void run()
        {
            new Thread()
            {
                public void run()
                {
                    z.send();
                }
            }.start();
            new Thread()
            {
                public void run()
                {
                    z.send();
                }
            }.start();
        }

        public void send()
        {
            synchronized(lock)
            {
                System.out.println("[Z] Send");
                z.receive();
            }
        }

        public void receive()
        {
            synchronized(lock)
            {
                System.out.println("[Z] Receive");
            }
        }
    }

2 个答案:

答案 0 :(得分:0)

这里的僵局非常明显。

z1产生两个新线程;让我们称之为T1和T2。它还创建了一个名为lock的Object实例,让我们称之为L1。 z2也这样做;让我们调用线程T3和T4以及锁定L2。

现在,让我们假设*,T1首先开始。

  1. T1调用Z实例z2上的send方法。
  2. 此方法使T1获取锁L2然后打印出" [Z]发送"。然后,该方法在z1实例上调用receive方法。
  3. z1实例上的接收方法导致T1获取锁L1,然后打印出" [Z]接收"。
  4. T1释放锁L1然后退出z1上的接收方法。
  5. T1释放锁L2然后退出z2上的send方法。
  6. 现在,让我们说在第2步和第3步之间,T3开始并执行以下操作:

    1. T3在Z实例z1上调用send方法。
    2. 此方法使T3获取锁L1然后打印出" [Z]发送"。然后,该方法在z2实例上调用receive方法。
    3. z2实例上的receive方法导致T3尝试获取锁定L2 ...
    4. L2已经被T1保留并且尚未发布。所以T3等待。
    5. 切换回T1,步骤3现在变为:

      1. z1实例上的receive方法导致T1尝试获取锁L1。
      2. L1已经被T3持有并且尚未被释放。所以T1等待。
      3. 上下文切换到T3,L2仍由T1保持,T3等待。 上下文切换到T1,L1仍由T3保持,T1等待。

        ......等等。

        这是解释可能发生死锁的地方。要解决此问题,您可能希望将调用移到send中synchronized块之外的z.receive(),以便在调用另一个实例的接收方法之前释放当前实例中的锁:

            public void send()
            {
                synchronized(lock)
                {
                    System.out.println("[Z] Send");
                }
                z.receive();
            }
        

        修改

        如果锁意味着保护所有实例不会同时执行,那么您可以使用在所有线程之间共享的一个锁,因此:

        class Z extends Thread
        {
            static final Object lock=new Object();
            ...
        }
        

        现在我们只有一个锁定实例,让我们称之为L0,再看看上面的步骤是如何运行的:

        1. T1调用Z实例z2上的send方法。
        2. 此方法使T1获取锁定L0然后打印出" [Z]发送"。然后,该方法在z1实例上调用receive方法。
        3. z1实例上的接收方法使T1再次获取锁L0;因为它已经拥有这个锁,它可以继续。它打印出" [Z]接收"。
        4. T1退出z1上的接收方法。
        5. T1释放锁L0,然后退出z2上的send方法。
        6. 再次,让我们说在第2步和第3步之间,T3开始并执行以下操作:

          1. T3在Z实例z1上调用send方法。
          2. 此方法使T3尝试获取锁L0。
          3. L0已被T1保留,尚未发布。所以T3等待。
          4. 此时T1中的步骤3不受影响。这意味着它仍然可以继续并最终释放锁定L0,这最终将让T3获得该锁定并自行继续。

            不再陷入僵局。

            __

            *线程启动顺序从不保证与调用start()方法的顺序相同,但在这种情况下,存在所有可能的死锁风险场景。

答案 1 :(得分:0)

最简单的方法,只需要一行更改,就是使用静态类级别锁定而不是非静态锁定。

   private static Object lock=new Object();

首先,让我们看一下问题的根源: 在此示例中,发生死锁,因为两个Z实例彼此持有锁(共享资源),并且两者都在等待其他资源完成其任务。并且,没有人能够锁定它所持有的资源。

由于锁是非静态的,z的每个实例都有自己的锁,因此允许两个z实例在调用send()时一起获取实例级锁,这会导致死锁,因为调用receive()需要锁定已经锁定的对象。

这可以通过使用静态/类级别锁来解决。通过在静态锁上同步,您将同步整个类方法和属性(而不是实例方法和属性)。 换句话说,Z类的所有实例将共享该锁定对象,因此没有两个实例能够同时锁定该对象。

将锁定对象设为静态后,程序将能够按如下方式运行:

  
      
  1. 当z1启动可运行任务时,它将获取类级别锁定和send();
  2.   
  3. z2尝试发送(),但z1已经保持锁定,因此它等待z1释放锁定;
  4.   
  5. 由于z1持有Z类的锁定,因此能够接收()。
  6.   
  7. 只有当z1完成所有工作时,z2才能开始获取Z类的锁定并继续发送和接收。
  8.   

PS:如果您需要更多解释,请查看以下链接,了解有关差异的更多详细信息: Difference between static and non-static lock