我遇到了一个非常特殊的问题,除了将问题分成两类之外我无法解决。
我想知道是否有可能没有拆分类的解决方案,我更想知道是否有人知道为什么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();
,而其他线程卡在 。编辑:如下面的Jaims所示,线程甚至都没有开始。Parallel.isFlag()
这对我没有任何意义。为什么这不起作用,但第一种情况是?基本上他们也在做同样的事情。
我想知道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提供了规范的链接和摘录,因此被标记为正确答案,因为这是我想要的。
答案 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)
我尝试了你的代码并做了两件事:
Parallel
的静态内部类......以防万一;这并没有改变任何事情。Parallel.isFlag()
上,我尝试用true
替换呼叫......并且它有效!所以,我做了一些研究,我找到了这个,这听起来像是一个有希望的解释:http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2
特别是这部分:
对于每个类或接口C,都有一个唯一的初始化锁定LC。从C到LC的映射由Java虚拟机实现决定。初始化C的过程如下:
在C的初始化锁定LC上同步。这涉及等到当前线程可以获取LC。
- 醇>
如果C的Class对象表示某个其他线程正在为C进行初始化,则释放LC并阻止当前线程,直到通知正在进行的初始化 ,在这个时候重复这一步。
(强调补充。)所以这将表明以下内容:
private static Parallel i = new Parallel();
时启动了类初始化并启动了线程。然后它等待latch.await()
。 Parallel
的类对象应指示初始化为&#34;正在进行中。&#34; Parallel
的静态成员。每个线程都看到初始化正在进行中,并决定等待主线程(现在正在等待线程倒计数锁存器)完成。显然这是一个僵局。