尝试解决竞争条件而不使用Java中的任何库

最后发布: 2016-05-18 21:45:43


问题

我搜索了“ java竞争条件”,看到了很多文章,但是这些都不是我想要的。

我试图在不使用锁定,同步,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(".");
}

该程序正常终止,并且输出是我所期望的(那些“。”除外)

我知道当2个线程同时执行时,CPU可以在获取下一条机器代码指令之前任意切换到另一个线程。 我认为如果一个线程读取一个变量而另一个线程写入该变量会很好。 而且我真的不明白为什么程序不会正常终止。 我还尝试在myThread1的while循环内添加Thread sleep语句,但该程序仍不会终止。

这个问题困扰了我几个星期,希望任何人都可以帮助我。

java multithreading
回答

尝试将x声明为volatile

   static public volatile boolean x = false;


回答

共享变量x是从多个线程读取和写入的,而没有任何同步,因此只能发生不好的事情。

当您具有以下条件时,

while (!Test.x) {
}

编译器可能将此优化为无限循环,因为在while循环内未更改x( volatile变量),这将防止程序终止。

添加打印语句将增加可见性,因为它具有保护System.out的同步块,这将导致越过memory barrier并获得Test.x的新副本。

不能同步共享的可变状态,而无需使用同步结构。


回答

Test.xvolatile ,因此可能不会在线程之间同步。

无法预测第二个循环中的打印命令如何影响整体行为,但是显然在这种情况下,它会使x同步。

通常,如果您省略了Java的所有与线程相关的功能,那么您将无法生成任何行为明确的代码。 最小的将是由不同线程用作volatile并同步代码段的标记变量,这些变量不能同时运行。


回答

您真正要问的是,“当我添加对println()的调用时,为什么我的程序能按预期工作?”

通常不需要一个线程执行的操作对其他线程可见。 JVM可以自由地将每个线程视为在自己的私有Universe中运行,这通常比尝试使用该私有Universe中的事件更新所有其他线程要快。

如果需要某些线程与其他线程的最新操作保持最新,则必须与这些线程“同步”。 如果不这样做,就无法保证线程会观察到另一个线程的行为。

解决没有记忆障碍的比赛条件是一个荒谬的问题。 没有答案,也没有理由寻找答案。 声明xvolatile字段!

调用System.out.println() ,您正在调用synchronized方法,该方法与volatile一样,充当与其他线程同步的内存屏障。 在这种情况下,这似乎足够了,但总的来说,这还不足以保证您的程序能够按预期运行。 为了保证期望的行为,在将x设置为true之后,第一个线程应获取并释放相同的锁System.out


更新:

埃里克(Eric)问:“我很好奇工作的波动性,其后又做了什么。我认为可以通过加,减,比较,跳转和赋值来创建所有内容。”

易失性写入通过确保将值写入所有读取线程可访问的位置(例如主内存)而不是处理器寄存器或数据高速缓存行之类的位置来工作。

易失性读取的工作方式是确保从共享位置读取值,而不是例如使用缓存在寄存器中的值。

执行Java字节码后,它们将转换为特定于执行处理器的本机指令。 使volatile工作所需的指令会有所不同,但是Java平台的规范要求无论采用哪种实现方式,都必须满足有关可见性的某些保证。


回答

更好的情况是,您可以在Thread2中等待并在线程1中发送通知的LOCK对象。您当前正在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”实例之前完成。