Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 即使尝试从JVM静态初始化过程获取相同的锁,线程也不会停止_Java_Multithreading_Concurrency_Thread Safety_Static Initialization - Fatal编程技术网

Java 即使尝试从JVM静态初始化过程获取相同的锁,线程也不会停止

Java 即使尝试从JVM静态初始化过程获取相同的锁,线程也不会停止,java,multithreading,concurrency,thread-safety,static-initialization,Java,Multithreading,Concurrency,Thread Safety,Static Initialization,在Java并发实践手册中,它说 静态初始值设定项在类加载后的类初始化时由JVM运行 但是在类被任何线程使用之前。因为JVM在初始化[JLS 12.4.2]和 每个线程至少获取一次该锁,以确保类已加载, 静态初始化期间进行的内存写入对所有线程自动可见。”(Goetz 16.2.3) 想法1:首次解释 我首先认为这意味着JVM决定看到某个类使用静态字段,临时让所有线程尝试获取静态初始化使用的锁,如果该锁从未释放,那么它将停止所有线程,永远等待该锁 想法2:可能的解释更有意义,特别是对于示例代码的行为

在Java并发实践手册中,它说

静态初始值设定项在类加载后的类初始化时由JVM运行 但是在类被任何线程使用之前。因为JVM在初始化[JLS 12.4.2]和 每个线程至少获取一次该锁,以确保类已加载, 静态初始化期间进行的内存写入对所有线程自动可见。”(Goetz 16.2.3)

想法1:首次解释

我首先认为这意味着JVM决定看到某个类使用静态字段,临时让所有线程尝试获取静态初始化使用的锁,如果该锁从未释放,那么它将停止所有线程,永远等待该锁

想法2:可能的解释更有意义,特别是对于示例代码的行为

如果只有在初始化了静态字段之后,JVM才让所有线程尝试获取静态初始化所使用的锁,那么这样就可以了。其他不是第一个使用静态字段的线程将很好,不会因为没有等待锁而停止。然而,我不确定情况是否如此。有人能证实想法2是正确的解释吗

最后是一个程序,它看起来像这样,并继续打印thread-0和thread-1

public class StaticBlockAndLineInterpretation {
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> keepLooping()).start();
        new Thread(() -> keepLooping()).start();
        Thread.sleep(2500);
        int x = AllThreadsStopper.threadStopper;
    }

    static void keepLooping() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}
            System.out.println("This is thread " + Thread.currentThread().getName());
        }
    }
}

class AllThreadsStopper {
    static int threadStopper;

    static {
        try {
            threadStopper = haltAllThreadsAndNeverReturn();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static int haltAllThreadsAndNeverReturn() throws InterruptedException {
        System.out.println("haltAllThreadsAndNeverReturn called");
        new CountDownLatch(1).await();
        return 0;
    }
}


console output snippet:
    This is thread Thread-0
    This is thread Thread-1
    This is thread Thread-0
    This is thread Thread-1
    haltAllThreadsAndNeverReturn called
    This is thread Thread-0
    This is thread Thread-1
    This is thread Thread-0
    This is thread Thread-1
    This is thread Thread-0 and so forth...

当然,引用的部分是关于使用该类的线程的,否则,如果没有共享数据,就没有必要讨论线程安全性

描述作为类初始化过程的一部分获取锁,其中线程在检测到另一个线程当前正在执行初始化时必须进入阻塞状态:

对于每个类或接口
C
,都有一个唯一的初始化锁
LC
。从
C
LC
的映射由 Java虚拟机实现。初始化
C
的步骤如下 如下:

  • 对于
    C
    ,在初始化锁
    LC
    上同步。这包括等待,直到当前线程可以获取
    LC
  • 如果
    C
    Class
    对象指示其他线程正在对
    C
    进行初始化,则释放
    LC
    并阻止当前线程,直到通知正在进行的初始化已完成,此时重复此步骤
  • 如果
    C
    Class
    对象指示当前线程正在对
    C
    进行初始化,则这必须是一个递归的初始化请求。释放
    LC
    并正常完成
  • 如果
    C
    Class
    对象指示
    C
    已初始化,则无需进一步操作。释放
    LC
    并正常完成
  • 请注意,这意味着如果类已经初始化(如4所示),则不执行任何操作,但仍然获取并释放锁,这是内存可见性约束的正式定义,发生在Brian Goetz提到的关系之前

    但作为类初始化的正式定义的一部分,它仅适用于实际触发类初始化的代码,如中所述:

    类或接口类型
    T
    将在第一次出现以下任一情况之前立即初始化:

    • T
      是一个类,并创建了
      T
      的实例
    • 调用由
      T
      声明的
      static
      方法
    • 分配了由
      T
      声明的
      静态
      字段
    • 使用由
      T
      声明的
      静态
      字段,该字段不是常量变量(§4.12.4)
    • T
      是一个顶级类(§7.6),执行
      T
      (§8.1.3)中词汇嵌套的
      assert
      语句(§14.10)
    当一个类被初始化时,它的超类被初始化(如果它们以前没有被初始化过的话)以及任何超接口 (§8.1.5)声明任何默认方法(§9.4.3)(如果之前未初始化)

    因为在您的情况下,这两个线程没有执行任何指定的操作,甚至没有间接执行,所以它们没有触发类初始化,因此也没有尝试获取类初始化锁

    通过从列表中插入操作,可以很容易地导致示例线程阻塞,例如

    static void keepLooping() {
        while (true) {
            try {
                Thread.sleep(1000);
                new AllThreadsStopper();
            } catch (InterruptedException e) {}
            System.out.println("This is thread " + Thread.currentThread().getName());
        }
    }
    
    由于创建类的实例会触发类的初始化,因此线程现在会被阻塞

    为了完整性,§12.4.2。还提到:

    当一个实现可以确定类的初始化已经完成时,它可以通过省略步骤1中的锁获取(并在步骤4/5中释放)来优化该过程,前提是,就内存模型而言,所有这些都发生在获取锁时将存在的排序之前,在执行优化时仍然存在

    这就是Brian Goetz在书中所说的“这种技术可以与JVM的惰性类加载相结合,创建一种惰性初始化技术,它不需要在公共代码路径上进行同步”。这是非常有效的,因为一旦初始化完成,线程可以访问初始化的类而不需要同步开销。在您的示例中,在初始化从未完成的情况下,这种优化是不可能的,如果线程正在使用clas,则必须获取锁