Java:当锁为空时,为什么会出现死锁?

3
我在我的代码中遇到了死锁的情况。我使用以下测试代码重现了这种情况(请注意行号注释)。
我有一个 ExecuterService,它接受 3 个任务,其中 2 个任务对同一个未初始化的对象执行同步块,这个对象应该具有 null 值。 synchronized(null){} 在 Java 中不被允许,因此我预计会在 line-2 处出现错误。然而,这个程序执行时发生了死锁。也就是说,前两个任务被第三个任务阻塞了。(请参见输出-1)
现在,如果我将 line-1 更改为 Object lock = new Object();,那么代码将无故障地运行,没有任何死锁(输出-2)。我只是想知道当我们提供一个未初始化的互斥对象时,JVM 到底会发生什么?看起来 JVM 创建了一个静态副本,由所有任务共享。因此导致了死锁。
public class Test {
    Object lock; //line -1
    Integer a = 10;

    public static void main(String[] arg) {
        new Test().doIt();
    }

    public void doIt() {
        ExecutorService service = Executors.newFixedThreadPool(3);

        service.submit(new Runnable() {
            public void run() {
                synchronized (Test.this.lock) { //line -2
                    try {
                        Thread.sleep(1000 * 5);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    Test.this.a = (int) Thread.currentThread().getId();
                    System.out.println(a + " " + Thread.currentThread().getId());

                }
            }
        });

        service.submit(new Runnable() {
            public void run() {
                synchronized (Test.this.lock) {
                    try {
                        Thread.sleep(1000 * 5);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    Test.this.a = (int) Thread.currentThread().getId();
                    System.out.println(a + " " + Thread.currentThread().getId());

                }
            }
        }

        );

        service.submit(new Runnable() {
            public void run() {
                Test.this.a = (int) Thread.currentThread().getId();
                while (true) {
                    try {
                        Thread.sleep(1 * 1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(a + " Main");
                }
            }
        });

    }
}

输出 -1

11 Main
11 Main
11 Main
11 Main
11 Main
11 Main
11 Main
11 Main
11 Main
11 Main
11 Main
.
.
.

输出-2

11 Main
11 Main
11 Main
11 Main
9 9
9 Main
9 Main
9 Main
9 Main
9 Main
10 10
10 Main
10 Main
.
.
.
2个回答

8
你没有死锁 - 就像你预期的那样,出现了错误。第一个和第二个任务将抛出 NullPointerException,只剩下第三个任务在运行。
如果你在 run() 方法的代码块中使用 try/catch 语句,你可以看到这一点。

啊,我明白了,那个异常从来没有被捕获和打印出来。好的,我懂了。这太蠢了... - Mangat Rai Modi
很遗憾它这么静默,这会引发问题,我认为这是个坑。如果有一种简单的方法可以注册错误监听器什么的就更好了。你可以检查通过submit返回的Future是否完成,但无法检查错误,当然你可以调用get()获得错误信息。 - Jon Skeet
非常感谢,未经检查的异常总是很痛苦。 - Mangat Rai Modi

3

异常是在执行线程上抛出的,除非你捕获或查询它,否则不可见。例如,您可以编写以下代码:

Future<?> f = service.submit(new Runnable() { ... }

最后,在main函数的结尾处:

try {
  f.get();
} catch (ExecutionException | InterruptedException e) {
  e.printStackTrace();
}

那将显示NPE错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接