从另一个线程初始化相同类的静态字段时访问静态方法

时间:2016-08-17 17:04:25

标签: java multithreading static static-initialization

我遇到了一个非常特殊的问题,除了将问题分成两类之外我无法解决。

我想知道是否有可能没有拆分类的解决方案,我更想知道是否有人知道为什么Java引擎决定按照它的方式行事。

问题: 我有一个带静态方法,静态字段和构造函数的类。静态字段初始化为类本身的实例。在实例初始化期间,我想访问前面提到的静态方法。请参阅以下代码:

public class Simple {
    public Simple() {
        int count = 4;

        for (int i = 0; i < count; i++) {
            System.out.println("Simple: " + Simple.isFlag()); 
        }

    }

    private static Simple i = new Simple();

    public static boolean isFlag() {
        return true;
    }

    public static void run() {

    }
}

public class Main {

    public static void main(String[] args) {
        Simple.run();
    }

}

此代码运行绝对正常。输出如下:

Simple: true
Simple: true
Simple: true
Simple: true

在调用run()方法之后生成输出,因为stativ字段i仅在我访问该类的第一个静态成员后初始化。

我现在想要完成同样的事情,除了多线程。见这里:

public class Parallel {
    public Parallel() {
        int count = 4;

        CountDownLatch latch = new CountDownLatch(4);
        for (int i = 0; i < count; i++) {
            Thread t = new Thread(() -> {
                System.out.println("Parallel: " + Parallel.isFlag());
                latch.countDown();

                Thread.currentThread().interrupt();
            });

            t.start();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    private static Parallel i = new Parallel();

    public static boolean isFlag() {
        return true;
    }

    public static void run() {

    }
}

public class Main {

    public static void main(String[] args) {
        Parallel.run();
    }

}

这会返回。主线程卡在latch.await();,而其他线程卡在 Parallel.isFlag() 。编辑:如下面的Jaims所示,线程甚至都没有开始。

这对我没有任何意义。为什么这不起作用,但第一种情况是?基本上他们也在做同样的事情。

我想知道Java Engine如何决定何时等待以及何时等待。可以在代码中的某处更改吗?

此外,这与CountDownLatch无关,而仅与多线程有关。看看最后的样本:

public class NonParallel {
    public NonParallel() {
        int count = 4;

        CountDownLatch latch = new CountDownLatch(4);
        for (int i = 0; i < count; i++) {
            System.out.println("NonParallel: " + NonParallel.isFlag());
            latch.countDown();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private static NonParallel i = new NonParallel();

    public static boolean isFlag() {
        return true;
    }

    public static void run() {

    }
}

public class Main {

    public static void main(String[] args) {
        NonParallel.run();
    }

}

这很好用。输出如下:

NonParallel: true
NonParallel: true
NonParallel: true
NonParallel: true

编辑:当对象initlization不是类初始化的一部分时,这些都不适用。这纯粹是关于类初始化,只有在使用此问题中描述的静态对象时才会发生。见这里:

public class NonStaticParallel {
    public NonStaticParallel() {
        int count = 4;

        CountDownLatch latch = new CountDownLatch(4);
        for (int i = 0; i < count; i++) {
            Thread t = new Thread(() -> {
                System.out.println("NonStaticParallel: " + isFlag());
                latch.countDown();

            });

            t.start();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }


    public  static  boolean isFlag() {
        return true;
    }

    public static void run() {
        new NonStaticParallel();
    }

}

这个没有任何问题:

Parallel: true
Parallel: true
Parallel: true
Parallel: true

数目:

Andreas提供了有关正在发生的事情的解释。

Jaims是正确的,因为线程甚至根本没有启动。这可能是因为他们需要初始化类,因此它们会立即被阻止。 (如果我们使用属于他们自己的类而不是lambda或匿名内部类的runnable,那么它们会正常运行,除非他们接受正在初始化的类的任何静态成员)

Yoshi提供了规范的链接和摘录,因此被标记为正确答案,因为这是我想要的。

3 个答案:

答案 0 :(得分:2)

当你调用Fish (String name, int weight) { ------- ------- } bass = new Fish("Bass", 10); ArrayList<Fish> fishes = new ArrayList<Fish>(); public void addThisFish(Fish fish) { fishes.add(new Fish(fish.name, fish.weight)); } public void otherMethod() { addThisFish(bass); } 时,当前线程将开始类初始化。任何引用该类的代码,例如对run()的调用也需要进行类初始化。

isFlag()Simple版本中,当前线程正在执行所有操作,并且允许递归类初始化(实际上被忽略),因此即使是类,也会执行NonParallel初始化尚未完成。

然而,在isFlag()版本中,对Parallel的调用是从另一个线程完成的,因此其他线程必须等待该类完全初始化。由于构造函数在线程运行之前不会返回,并且线程在构造函数返回并且完成类初始化之前无法运行,因此您有死锁

结论:您无法并行执行类初始化代码。类初始化必须在单个线程中完成。

如果你愿意,你可以在课堂初始化期间启动线程,但你不能等待它们完成(如果他们也访问你的课程,那么他们没有注意到什么?)

答案 1 :(得分:1)

在正确创建对象之前,您的线程不会启动。请考虑以下代码段:

public class Main {
    public static void main(String[] args) {
        Parallel.run();
    }
}

class Parallel {
    private static Parallel i = new Parallel();
    public Parallel() {
        try {
            System.out.println("Inside constructor.");

            for (int i = 0; i < 4; i++) {
                Thread t = new Thread(() -> {
                    System.out.println("Running thread.");
                });
                System.out.println("Starting thread.");
                t.start();
            }
            System.out.println("Sleeping 2 seconds.");
            Thread.sleep(2000);
            System.out.println("Leaving constructor.");
        } catch (InterruptedException ex) {
            Logger.getLogger(Parallel.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    public static void run() {

    }
}

它将产生以下输出:

Inside constructor.
Starting thread.
Starting thread.
Starting thread.
Starting thread.
Sleeping 2 seconds.
Leaving constructor.
Running thread.
Running thread.
Running thread.
Running thread.

如输出所示,线程在构造函数中启动4次。它开始睡眠2秒钟,离开构造函数,然后运行你的线程。不像你的线程运行需要2秒钟。

因此,您的问题的核心问题是您正在调用latch.await(),但您的线程永远不会有机会实际运行。意味着锁存器没有减少并且只是等待。您可以将逻辑移到run()方法,但我不确定您首先想要实现的目标。 e.g。

public static void run() {
    int count = 4;

    CountDownLatch latch = new CountDownLatch(4);
    for (int i = 0; i < count; i++) {
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(2000);
                latch.countDown();
            } catch (InterruptedException ex) {
                Logger.getLogger(Parallel.class.getName()).log(Level.SEVERE, null, ex);
            }
        });
        System.out.println("Starting thread.");
        t.start();
    }

    try {
        System.out.println("Current count: " + latch.getCount());
        latch.await();
        System.out.println("Current count: " + latch.getCount());
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

答案 2 :(得分:1)

我尝试了你的代码并做了两件事:

  1. 首先,我使lambda成为Parallel的静态内部类......以防万一;这并没有改变任何事情。
  2. 由于您评论线程卡在Parallel.isFlag()上,我尝试用true替换呼叫......并且它有效!
  3. 所以,我做了一些研究,我找到了这个,这听起来像是一个有希望的解释:http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2

    特别是这部分:

      

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

         
        
    1. 在C的初始化锁定LC上同步。这涉及等到当前线程可以获取LC。

    2.   
    3. 如果C的Class对象表示某个其他线程正在为C进行初始化,则释放LC并阻止当前线程,直到通知正在进行的初始化 ,在这个时候重复这一步。

    4.   

    (强调补充。)所以这将表明以下内容:

    1. 主线程在评估private static Parallel i = new Parallel();时启动了类初始化并启动了线程。然后它等待latch.await()Parallel的类对象应指示初始化为&#34;正在进行中。&#34;
    2. 启动线程还尝试引用Parallel的静态成员。每个线程都看到初始化正在进行中,并决定等待主线程(现在正在等待线程倒计数锁存器)完成。显然这是一个僵局。