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

Java 从另一个线程初始化同一类的静态字段时访问静态方法,java,multithreading,static,static-initialization,Java,Multithreading,Static,Static Initialization,我遇到了一个非常奇怪的问题,除了把问题分成两类之外,我无法解决这个问题 我想知道是否有一个不拆分类的解决方案,更重要的是,我想知道是否有人知道为什么Java引擎决定这样做 问题是: 我有一个带有静态方法、静态字段和构造函数的类。静态字段初始化为类本身的实例。在实例初始化期间,我想访问前面提到的静态方法。请参阅以下代码: public class Simple { public Simple() { int count = 4; for (int i =

我遇到了一个非常奇怪的问题,除了把问题分成两类之外,我无法解决这个问题

我想知道是否有一个不拆分类的解决方案,更重要的是,我想知道是否有人知道为什么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();
    }

}
输出是在调用
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();
    }

}
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();
    }

}
这个很好用。输出如下:

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

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

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();
    }

}
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();
    }

}

答复:

安德烈亚斯解释了正在发生的事情

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


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

在正确创建对象之前,不会启动线程。考虑下面的片段:

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() {

    }
}
如输出所示,线程在构造函数中启动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();
    }
}
publicstaticvoidrun(){
整数计数=4;
倒计时闩锁=新倒计时闩锁(4);
for(int i=0;i{
试一试{
《睡眠》(2000年);
倒计时();
}捕获(中断异常例外){
Logger.getLogger(Parallel.class.getName()).log(Level.SEVERE,null,ex);
}
});
System.out.println(“起始线程”);
t、 start();
}
试一试{
System.out.println(“当前计数:+latch.getCount());
satch.wait();
System.out.println(“当前计数:+latch.getCount());
}捕捉(中断异常e){
//TODO自动生成的捕捉块
e、 printStackTrace();
}
}
调用
run()
时,当前线程将开始类初始化。任何引用类的代码,例如调用
isFlag()
,都需要初始化类

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

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

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


如果需要,您可以在类初始化期间启动线程,但您不能等待它们完成(如果它们也访问您的类,那么它们没有访问的意义是什么?)。

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

  • 首先,我使lambda成为
    并行的静态内部类。。。以防万一;这没有改变任何事情
  • 由于您评论说线程被卡在了
    Parallel.isFlag()
    上,所以我尝试用
    true
    替换调用。。。成功了 所以,我做了一点研究,发现了这一点,这听起来像是对正在发生的事情的一个有希望的解释:

    具体而言,本部分:

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

  • 同步C的初始化锁LC。这包括等待当前线程可以获取LC

  • 如果C的类对象指示其他线程正在进行C的初始化,则释放LC并阻止当前线程,直到通知正在进行的初始化已完成为止,此时重复此步骤

  • (重点补充。)因此,这将提出以下建议:

  • 主线程在计算
    private static Parallel i=new Parallel()时启动类初始化并启动线程。然后它在
    锁存器上等待。等待()
    。并行
    的类对象应指示初始化“正在进行”。
    Inside constructor.
    Starting thread.
    Starting thread.
    Starting thread.
    Starting thread.
    Sleeping 2 seconds.
    Leaving constructor.
    Running thread.
    Running thread.
    Running thread.
    Running thread.
    
    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();
        }
    }