被Java迷惑:Thread中的boolean赋值失败

Puzzled by Java: boolean assignment fails in Thread

我对 Java 中的某些行为感到非常困惑,我想知道是否有人可以提供解释。我试图将 boolean 值设置为 true 以停止线程,但赋值失败。考虑以下示例:

public class Temp {

public class Unstoppable implements Runnable {
    public boolean stop=false;
    private int ctr=0;
    @Override
    public void run() {
        while(!stop) {
            stop |= doSomething();
        }
    }

    public boolean doSomething() {
        System.out.println("Still running "+ctr++);

        // some other logic here could decide that it's time to stop
        // especially if Unstoppable would be an abstract class and doSomething() an abstract function  
        return false;
    }

    public void stop() {
        stop=true;
    }
}

public void start() {
    // start thread with Unstoppable
    Unstoppable st = new Unstoppable();
    new Thread(st).start();

    // wait for a while
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    // try to stop the thread
    st.stop(); // assignment fails, variable 'stop' is still false after this call so Unstoppable never stops
}

public static void main(String[] args) {
    Temp t = new Temp();
    t.start();
}
}

尝试在 stop() 函数中分配值 true 完全失败,线程保持 运行。我发现将代码更改为以下可以解决问题:

@Override
    public void run() {
        while(!stop) {
            // without   stop |=   the thread DOES stop
            doSomething();
        }
    }

但我不明白为什么。

更奇怪的是,下面的代码更改也解决了问题:

    @Override
    public void run() {
        while(!stop) {
            stop |= doSomething();

            // printing here does also result in the thread stopping!
            System.out.println("Still running "+ctr++);
        }
    }

    public boolean doSomething() {
        // some other logic here could decide that it's time to stop
        // especially if Unstoppable would be an abstract class and doSomething() an abstract function  
        return false;
    }

虽然我可以解决问题,但我想了解这里发生了什么。谢谢!

编辑 再说明一下,我将代码更改为以下内容:

public class Temp {

public class Unstoppable implements Runnable {
    private volatile boolean stop=false;

    @Override
    public void run() {
        while(!stop) {
            System.out.println("A) stop="+stop);
            stop |= doSomething();
            System.out.println("C) stop="+stop);
        }
    }

    public boolean doSomething() {
        while(!stop) {
        }
        System.out.println("B) stop="+stop);
        // some other logic here could decide that it's time to stop
        // especially if Unstoppable would be an abstract class and doSomething() an abstract function  
        return false;
    }

    public void setStop(boolean stop) {
        System.out.println("D) stop="+stop);
        this.stop=stop;
        System.out.println("E) stop="+stop);
    }
}

public void start() {
    // start thread with Unstoppable
    Unstoppable st = new Unstoppable();
    Thread t = new Thread(st);
    t.start();

    // wait for a while
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    // try to stop the thread
    st.setStop(true); // assignment fails, variable 'stop' is still false after this call so Unstoppable never stops
}

public static void main(String[] args) {
    Temp t = new Temp();
    t.start();
}
}

这会在控制台上产生以下语句:

A) stop=false
D) stop=true
E) stop=true
B) stop=true
C) stop=false
A) stop=false

疑惑在语句C) stop=false 上。在 B) 它是真的,然后函数结果是假的,我希望 true |= false 会导致 true...

然而,正如 slim 所示,在调用 doSomething() 之前,Java 已经计算了 |= 的左侧。将代码更改为:

@Override
public void run() {
    while(!stop) {
        boolean stopNow = doSomething();
        stop |= stopNow;
    }
}

是否导致线程停止。

stop |= foo()

... 是以下的缩写:

boolean x = foo();
boolean y = stop || x;
stop = y;

现在考虑两个线程:

     Thread A                |  Thread B
1    boolean x = foo();      |
2    boolean y = stop || x;  |  
3                            |  stop = true;
4    stop = y                |
5    if(stop) { ... }

如果 yfalse,那么,当事情按此顺序发生时,线程 B 对 stop (3) 的赋值被线程 A 的赋值 (4) 替换,之前测试 (5).

即使 stop 是易变的,即使您忽略线程之间变量可见性的 "weirdness",也会发生这种竞争情况。

关键是 stop |= foo() 不是 原子的 ,因此在执行期间可能会发生一些事情,从而搞砸了明显的逻辑。这就是为什么我们有 类 之类的 AtomicBoolean,它们提供有保证的原子操作,您可以将其用于此目的。

  AtomicBoolean stop = new AtomicBoolean();
  ...
  while(! stop.get()) {
      ...
      stop.compareAndSet(false, foo());
  }

或者,您可以将 |= 放入 synchronized 方法中,并使其成为您分配 stop 唯一方法 :

   private synchronized stopIf(boolean doStop) {
        this.stop |= doStop;
   }