【问题标题】:Why no deadlock on the same object in this program - Java Multithreading为什么这个程序中同一个对象没有死锁 - Java Multithreading
【发布时间】:2018-03-28 18:56:54
【问题描述】:

我有以下三个课程。 Main、两个线程和方法同步的 Obj

public class DeadLocks {

    public static void main(String[] args) {
        SyncObj so = new SyncObj();
        ThreadObj to = new ThreadObj(so);
        ThreadObj1 to1 = new ThreadObj1(so);

        to.start();
        to1.start();
    }
}

class SyncObj {
    synchronized void foo() {
        System.out.println("Foo Started");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        bar();
    }

    synchronized void bar() {
        System.out.println("bar started");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        foo();
    }

}

class ThreadObj1 extends Thread {
    SyncObj so;

    public ThreadObj1(SyncObj so) {
        this.so = so;
    }

    @Override
    public void run() {
        so.bar();
    }
}

class ThreadObj extends Thread {
    SyncObj so;

    public ThreadObj(SyncObj so) {
        this.so = so;
    }

    @Override
    public void run() {
        so.foo();
    }
}

在上面的代码中,我在同一个对象上调用同步方法。两个方法同时执行和调用,不存在死锁情况。谁能解释为什么?抱歉问了这么愚蠢的问题。

【问题讨论】:

  • 同步块是可重入的。一旦一个线程在SynckObj 上获得锁定,它就可以调用相同对象的同步方法而无需等待锁定。
  • 这里只有一把锁。你不能只用一把锁死锁,至少需要两把。所以两个线程只是轮流访问单个对象。
  • @markspace 根据我的理解,同步关键字用于锁定方法而不是对象。在这里,我锁定了 2 种方法。访问同一个对象可以同时对
  • 你的理解是错误的,它锁定了对象。这里只有一把锁。请注意下面 Khanna11 的回复。

标签: java multithreading synchronized


【解决方案1】:

据我所知,您在这两种情况下都使用相同的对象 (so)。所以不存在死锁的情况。您需要锁定两个或多个对象,其中每个关键部分都需要锁定而不是它所持有的锁定。另一个锁由“另一个”线程持有。

困惑,这会启发:“https://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html

在您描述的场景中,您会never ever deadlock。在这种情况下,只有一个 so 正在共享并与之同步。

我举个例子来说明:

假设安迪和桑迪正在玩两个足球B1B2

现在进一步假设 Andy 和 Sandy 分别拥有球 B1B2。例如,安迪有球B1,而桑迪有球B2

现在他们设计了一个游戏,其中每个人都需要两个球。 现在桑迪也想要球B1,同时安迪想要B2

而且他们俩都不能放弃他们持有的球。安迪不会放弃B1,反之亦然。

So both of them cannot continue and are stuck. We call this a deadlocked situation.

希望这个例子有所帮助。您可以利用您的想象力将比赛中的球数增加到 3 个或 4 个(等等)和/或增加球员的数量。

【讨论】:

  • 除了你的足球例子之外,僵局的经典例证是餐饮哲学家的问题:en.wikipedia.org/wiki/Dining_philosophers_problem
  • 是的,正确的@markspace。这是一个定义明确的例子。我做了一个规则较少的粗略例子。
  • @markspace:感谢您提出这个问题 - 在这次讨论中很有价值。
【解决方案2】:

您不会遇到死锁,因为您的程序不符合形成它的four necessary conditions 中的两个:

  • 互斥 - 是的,
  • 保留部分分配 - 不,
  • 不抢占 - 是的,
  • 循环依赖 - 否

您至少需要两个资源“A”和“B”来形成死锁。一个线程应该抓取“A”并尝试抓取“B”,而另一个线程应该抓取“B”并尝试抓取“A”。

【讨论】:

    【解决方案3】:

    我不知道您为什么期望这里会出现僵局。确实只有一个对象可以访问同步部分,但是它可以随心所欲地访问。您可以修改代码以使其清晰:

    public class DeadLocks {
    
         public static void main(String[] args) {
             SyncObj so = new SyncObj();
             ThreadObj to = new ThreadObj(so);
             ThreadObj1 to1 = new ThreadObj1(so);
    
             to.start();
             to1.start();
         }
     }
    
     class SyncObj {
         synchronized void foo(String msg) {
             System.out.println("Foo Started: " + msg);
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             bar(msg);
         }
    
         synchronized void bar(String msg) {
             System.out.println("bar started: " + msg);
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             foo(msg);
         }
    
     }
    
     class ThreadObj1 extends Thread {
         SyncObj so;
    
         public ThreadObj1(SyncObj so) {
             this.so = so;
         }
    
         @Override
         public void run() {
             so.bar("TO1");
         }
     }
    
     class ThreadObj extends Thread {
         SyncObj so;
    
         public ThreadObj(SyncObj so) {
             this.so = so;
         }
    
         @Override
         public void run() {
             so.foo("TO");
         }
     }
    

    可以看到如下输出:

    Foo Started: TO
    bar started: TO
    Foo Started: TO
    bar started: TO
    Foo Started: TO
    bar started: TO
    

    您可以认为“同步”表示锁定(this),其中“this”是一个 SyncObj 实例。所以只有一个锁,不可能得到死锁。

    【讨论】:

      【解决方案4】:

      虽然其他人已经指出,只有当您有两个资源并且每个线程锁定一个然后等待另一个时才会发生死锁,但我认为缺少一个关键点,您的整个困惑可能来自:

      synchronize 在方法上不会为该特定方法创建锁,它会为其所属的整个对象创建锁。因此,您的课程等同于:

      class SyncObj {
          void foo() {
              synchronized(this) {
                  System.out.println("Foo Started");
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  bar();
              }
          }
      
          void bar() {
              synchronized(this) {
                System.out.println("bar started");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                foo();
              }
          }
      }
      

      现在应该更清楚了,为什么你没有遇到死锁。

      您可以通过引入两种资源(每种方法一个资源)轻松地修改您的代码,使其容易出现死锁:

      class SyncObj {
          private Object foolock = new Object();
          private Object barlock = new Object();
      
          void foo() {
              synchronized(foolock) {
                  System.out.println("Foo Started");
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  bar();
              }
          }
      
          void bar() {
              synchronized(barlock) {
                System.out.println("bar started");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                foo();
              }
          }
      }
      

      【讨论】:

        【解决方案5】:

        使用单个实例不可能出现死锁,因为同一对象的两个线程不能访问多个同步方法。 在上面的例子中,如果线程 1 正在访问 foo 方法。线程 2 无法访问 foo 或 bar 方法。直到线程 1 完成它的任务

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-18
          • 2012-02-21
          • 2021-06-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多