java - 类初始化时创建新线程导致的死锁

标签 java multithreading concurrency thread-safety

我刚刚注意到,在类的静态初始化期间创建和启动多个线程会导致死锁,并且没有线程启动。如果我在类初始化后动态运行相同的代码,这个问题就会消失。这是预期的行为吗?

简短的示例程序:

package com.my.pkg;

import com.google.common.truth.Truth;
import org.junit.Test;

import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class MyClass {
    private static final Collection<Integer> NUMS = getNums();

    @Test
    public void fork_doesNotWorkDuringClassInit() {
        // This works if you also delete NUMS from above: 
        // Truth.assertThat(getNums()).containsExactly(0, 1, 2, 3, 4);
        Truth.assertThat(NUMS).containsExactly(0, 1, 2, 3, 4);
    }

    private static Collection<Integer> getNums() {
        return IntStream.range(0, 5)
                        .mapToObj(i -> fork(() -> i))
                        .map(MyClass::get)
                        .collect(Collectors.toList());
    }

    public static <T> FutureTask<T> fork(Callable<T> callable) {
        FutureTask<T> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();
        return futureTask;
    }

    public static <T> T get(Future<T> future) {
        try {
            return future.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

最佳答案

是的,这是预期的行为。

这里的基本问题是您试图在类初始化完成之前从另一个线程访问该类。它恰好是您在类初始化期间启动的另一个线程,但这没有任何区别。

在 Java 中,类是在首次引用时延迟初始化的。当一个类还没有完成初始化时,引用该类的线程会尝试获取类初始化锁。第一个获得类初始化锁的线程初始化该线程,并且该初始化必须在其他线程可以继续之前完成。

此时,fork_doesNotWorkDuringClassInit()开始初始化,获得类初始化锁。但是,初始化会产生额外的线程,这些线程会尝试调用 lambda 可调用对象 () -> i。可调用对象是类的成员,因此这些线程会被类初始化锁阻塞,该锁由启动初始化的线程持有。

不幸的是,您的初始化过程需要其他线程的结果才能完成初始化。它会阻塞那些结果,而这些结果又会在初始化完成时被阻塞。线程最终陷入僵局。

更多关于类初始化的信息:

http://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4.2

一般来说,Java 的初始化器和构造器在它们能做的事情上是有限的——比 C++ 中的情况要多得多。这可以防止某些类型的错误,但也会限制您可以执行的操作。这是其中一个限制的示例。

关于java - 类初始化时创建新线程导致的死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45246122/

相关文章:

java - java中根据条件动态添加执行器

java - Exchanger 如何支持无 GC Java?

spring-boot - 如何在 Http 入站网关中同时接受固定数量的请求?

.net - 我可以从该字典的枚举循环中删除 ConcurrentDictionary 中的项目吗?

java - 通过调用 super 来重写函数只是为了添加一点功能是不好的做法吗?

java - 从线程启动一个 Activity

java - 抽象类的 Lambda 表达式

java - 如何释放已关闭以进行垃圾收集的执行程序服务的内存?

java - Recycleviewer - 如何重用布局?

java - Selenium:如何在不包含选项的情况下查找具有特定文本的元素