Java困惑:布尔分配在Thread中失败

时间:2017-08-16 15:10:12

标签: java multithreading variable-assignment

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

尝试在true函数中分配值stop()只是失败并且线程一直在运行。我发现将代码更改为以下内容可以解决问题:

@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 ......

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

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

导致线程停止。

1 个答案:

答案 0 :(得分:3)

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不稳定,也会发生这种竞争条件,即使您忽略了线程之间变量可见性的“怪异”。

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

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

或者,您可以将|=置于synchronized方法中,然后将唯一的方式分配给stop

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