我搜索了#java; java race condition"并且看了很多文章,但没有一个是我想要的。
我试图解决竞争条件而不使用锁,同步,Thread.sleep其他东西。我的代码在这里:
public class Test {
static public int amount = 0;
static public boolean x = false;
public static void main(String[] args) {
Thread a = new myThread1();
Thread b = new myThread2();
b.start();
a.start();
}
}
class myThread1 extends Thread {
public void run() {
for (int i = 0; i < 1000000; i++) {
if (i % 100000 == 0) {
System.out.println(i);
}
}
while(true){
Test.x = true;
}
}
}
class myThread2 extends Thread {
public void run() {
System.out.println("Thread 2: waiting...");
while (!Test.x) {
}
System.out.println("Thread 2: finish waiting!");
}
}
我希望输出应该是:
Thread 2: waiting...
0
100000
200000
300000
400000
500000
600000
700000
800000
900000
Thread 2: finish waiting!
(Terminated normally)
但实际上是:
Thread 2: waiting...
0
100000
200000
300000
400000
500000
600000
700000
800000
900000
(And the program won't terminate)
我向 myThread2 添加了一条语句后,更改了
while (!Test.x) {
}
到
while (!Test.x) {
System.out.println(".");
}
程序正常终止,输出符合我的预期(除了那些&#34;。&#39;)
我知道当2个线程并发执行时,CPU可以在获取机器代码的下一条指令之前任意切换到另一条线程。 我认为如果一个线程读取一个变量而另一个线程写入该变量则会好的。我真的不明白为什么程序不能正常终止。我还尝试在 myThread1 的while循环中添加一个Thread sleep语句,但程序仍然不会终止。
这个问题困扰了我几个星期,希望任何人都可以帮助我。
答案 0 :(得分:2)
尝试将x声明为volatile
:
static public volatile boolean x = false;
答案 1 :(得分:2)
共享变量x正在从多个线程读取和写入,没有任何同步,因此只会发生不好的事情。
如果您有以下内容,
while (!Test.x) {
}
编译器可能会将其优化为无限循环,因为在while循环中没有更改x(非volatile
变量),这会阻止程序终止。
添加一个print语句会增加更多的可见性,因为它有一个同步块保护System.out
,这将导致越过memory barrier
并获得Test.x
的新副本。
你不能在不使用同步结构的情况下同步共享的可变状态。
答案 2 :(得分:2)
Test.x
不是volatile
,因此可能无法在线程之间同步。
无法预测第二个循环中的print-command如何影响整体行为,但显然在这种情况下会导致x
同步。
一般情况下:如果省略java的所有与线程相关的功能,则无法生成具有明确定义的行为的任何代码。最小值是标记变量,它们被不同的线程用作volatile
并同步代码片段,而不是同时运行。
答案 3 :(得分:0)
您真正要问的是,&#34;为什么在我添加对println()
的通话时我的计划按预期工作?&#34;
一个线程执行的操作通常不需要对其他线程可见。 JVM可以自由地将每个线程视为在其自己的私有Universe中运行,这通常比尝试使用该私有Universe中的事件更新所有其他线程更快。
如果您需要某些线程与另一个线程的某些操作保持同步,那么必须&#34;同步 - &#34;那些线程。如果你没有,那么就不能保证线程会观察到另一个人的行为。
解决没有记忆障碍的竞争条件是一个荒谬的问题。没有答案,也没有理由去寻找答案。将x
声明为volatile
字段!
当您调用System.out.println()
时,您正在调用synchronized
方法,该方法与volatile
一样,充当内存屏障以与其他线程同步。在这种情况下似乎已经足够了,但总的来说,即使这还不足以保证您的程序能够按预期工作。为了保证所需的行为,在将System.out
设置为x
之后,第一个线程应该获取并释放相同的锁true
。
更新
埃里克问,&#34;我很好奇工作有多么不稳定,背后做了什么。我认为一切都可以通过加法,减法,比较,跳跃和赋值来创建。&#34;Volatile写入工作是通过确保将值写入所有读取线程(如主存储器)可访问的位置,而不是像处理器寄存器或数据缓存线那样。
通过确保从该共享位置读取值来实现易失性读取,而不是使用例如寄存器中缓存的值。
执行Java字节代码时,它们将转换为特定于执行处理器的本机指令。使volatile
工作所需的指令会有所不同,但Java平台的规范要求无论实现什么,都要满足某些关于可见性的保证。
答案 4 :(得分:0)
更好的是一个LOCK对象,你可以在Thread2中等待并在线程1中发送一个通知。你当前正在Thread2中等待并消耗大量的CPU资源。
虚拟代码示例:
main() {
Object lock = new Object();
Thread2 t2 = new Thread2(lock);
t2.start();
Thread1 t1 = new Thread1(lock);
t1.start();
...
}
class Thread1 {
Object lock = null;
public Thread1(Object lock) {
this.lock = lock;
...
}
public void run() {
...
synchronized (lock) {
lock.notifyAll();
}
}
} // end class Thread1
// similar Thread2
class Thread2 {
... constructor
public void run()
{
System.out.println("Thread 2: waiting...");
synchronized(lock) {
lock.wait();
}
System.out.println("Thread 2: finish waiting!");
}
....
如果没有在“Thread2”中执行任何操作,此构造不会占用任何CPU周期。您可以创建自定义数量的“Thread2”实例,并等待“Thread1”完成。您应该“仅”在“Thread1”之前启动所有“Thread2”实例。否则“Thread1”可能会在“Thread2”实例启动之前完成。