Java线程似乎正在跳过条件语句

Java线程似乎正在跳过条件语句,java,multithreading,Java,Multithreading,对于我正在编写的一个最近的库,我编写了一个无限循环的线程。在这个循环中,我从一个条件语句开始检查线程对象的属性。然而,无论属性具有什么初始值,即使在更新后也会返回什么 除非我做了一些干扰,比如Thread.sleep或print语句 不幸的是,我真的不知道该怎么问这个问题。否则,我将查看Java文档。我将代码归结为一个简单的例子,用简单的术语解释了这个问题 公共类应用程序{ 公共静态无效字符串[]args{ 应用程序=新应用程序; } 类测试实现可运行{ 公共布尔标志=false; 公开募捐{

对于我正在编写的一个最近的库,我编写了一个无限循环的线程。在这个循环中,我从一个条件语句开始检查线程对象的属性。然而,无论属性具有什么初始值,即使在更新后也会返回什么

除非我做了一些干扰,比如Thread.sleep或print语句

不幸的是,我真的不知道该怎么问这个问题。否则,我将查看Java文档。我将代码归结为一个简单的例子,用简单的术语解释了这个问题

公共类应用程序{ 公共静态无效字符串[]args{ 应用程序=新应用程序; } 类测试实现可运行{ 公共布尔标志=false; 公开募捐{ whiletrue{ //试一试{ //线程1; //}捕获中断异常e{} 如果这是.flag{ System.out.printlnTrue; } } } } 公共应用程序{ 试验t=新试验; 螺纹=新螺纹T; System.out.PrintLn起始线程; thread.start; 试一试{ 线程1000; }捕获中断异常e{} t、 flag=true; System.out.printlnew标志值:+t.flag; } } 现在,我假设在我们改变运行线程上flag属性的值之后,我们会立即看到大量“True”向终端吐出。但是,我们没有

如果我在线程循环中取消对Thread.sleep行的注释,程序将按预期工作,并且在更改App对象中的值后,我们会看到许多行“True”被打印出来。另外,任何代替Thread.sleep的print方法都可以工作,但一些简单的赋值代码不能。我假设这是因为它在编译时被作为未使用的代码取出


因此,我的问题是:为什么我必须使用某种中断来让线程正确地检查条件?

因为该线程中没有修改变量,JVM可以自由地有效地优化检查。要强制进行实际检查,请使用volatile关键字:

public volatile boolean标志=false;
因为该线程中没有修改变量,所以JVM可以自由地有效地优化check-away。要强制进行实际检查,请使用volatile关键字:

public volatile boolean标志=false; 所以,我的问题是:为什么我必须使用某种中断来让线程正确地检查条件

你不必这么做。至少有两种方法可以在不使用中断的情况下实现这个特定示例

若您将标志声明为volatile,那个么它将工作。 如果您将标志声明为私有,编写同步的getter和setter方法,并对所有访问使用这些方法,它也会起作用

public class App {
    public static void main(String[] args) {
        App app = new App();
    }

    class Test implements Runnable {
        private boolean flag = false;

        public synchronized boolean getFlag() {
            return this.flag;
        }

        public synchronized void setFlag(boolean flag) {
            return this.flag = flag;
        }

        public void run() {
            while(true) {
                if (this.getFlag()) {   // Must use the getter here too!
                    System.out.println("True");
                }
            }
        }
    }

    public App() {
        Test t = new Test();
        Thread thread = new Thread(t);
        System.out.println("Starting thread");
        thread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {}

        t.setFlag(true);
        System.out.println("New flag value: " + t.getFlag());
}
但你为什么要这么做

因为除非您使用volatile或synchronized并正确使用synchronized,否则一个线程不能保证看到另一个线程所做的内存更改

在您的示例中,子线程看不到flag的最新值。并不是条件本身不正确或不起作用。他们实际上得到了过时的输入。这是垃圾进来,垃圾出去

Java语言规范精确地规定了一个线程保证看到另一个线程以前写的代码的条件。规范的这一部分被称为Java内存模型,它位于。Brian Goetz等人在实践中对Java并发性有一个更容易理解的解释

请注意,意外行为可能是由于JIT决定将标志保留在寄存器中。也可能是JIT编译器决定不需要强制内存缓存直写,等等。JIT编译器不希望强制执行对每个字段的每次内存写入。这将对多核系统的性能产生重大影响。。。最现代的机器是什么

Java中断机制是处理此问题的另一种方法。您不需要任何同步,因为该方法调用该同步。此外,当您试图中断的线程当前正在等待或阻止可中断操作时,中断将起作用;e、 g.在对象中::等待调用

所以,我的问题是:为什么我必须使用某种中断来让线程正确地检查条件

你不必这么做。至少有两种方法可以在不使用中断的情况下实现这个特定示例

若您将标志声明为volatile,那个么它将工作。 如果您将标志声明为私有,编写同步的getter和setter方法,并对所有访问使用这些方法,它也会起作用

public class App {
    public static void main(String[] args) {
        App app = new App();
    }

    class Test implements Runnable {
        private boolean flag = false;

        public synchronized boolean getFlag() {
            return this.flag;
        }

        public synchronized void setFlag(boolean flag) {
            return this.flag = flag;
        }

        public void run() {
            while(true) {
                if (this.getFlag()) {   // Must use the getter here too!
                    System.out.println("True");
                }
            }
        }
    }

    public App() {
        Test t = new Test();
        Thread thread = new Thread(t);
        System.out.println("Starting thread");
        thread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {}

        t.setFlag(true);
        System.out.println("New flag value: " + t.getFlag());
}
但你为什么要这么做

贝考 se除非您使用volatile或synchronized并且正确使用synchronized,否则不能保证一个线程看到另一个线程所做的内存更改

在您的示例中,子线程看不到flag的最新值。并不是条件本身不正确或不起作用。他们实际上得到了过时的输入。这是垃圾进来,垃圾出去

Java语言规范精确地规定了一个线程保证看到另一个线程以前写的代码的条件。规范的这一部分被称为Java内存模型,它位于。Brian Goetz等人在实践中对Java并发性有一个更容易理解的解释

请注意,意外行为可能是由于JIT决定将标志保留在寄存器中。也可能是JIT编译器决定不需要强制内存缓存直写,等等。JIT编译器不希望强制执行对每个字段的每次内存写入。这将对多核系统的性能产生重大影响。。。最现代的机器是什么


Java中断机制是处理此问题的另一种方法。您不需要任何同步,因为该方法调用该同步。此外,当您试图中断的线程当前正在等待或阻止可中断操作时,中断将起作用;e、 g.在对象::等待调用中。

使用volatile。public volatile boolean标志=false;尝试将volatile关键字添加到变量中。记住Java多线程要求您注意内存可见性语义。对Java字段或其他共享内存的更改可能对其他线程不可见,除非在触发器出现之前发生某种情况,例如volatile或synchronized关键字或使用Java.util.concurrent中的某个类。如果您想了解更多信息,Brian Goetz的《Java并发实践》是Java程序员关于多线程的圣经。您还应该阅读第17章“线程和锁”的开头部分,其中讨论了所有多线程。@AdamJarvis在设计Java时,他们可以做出两种选择:1假设任何对象都可以在任何时候被任何线程修改,并且在99.8%的时间里,会丢失大量有价值的优化,而这些优化没有发生。2需要访问可能被其他线程修改的对象的代码,以使用某种形式的指示,表明这将发生,因此优化只在需要的0.2%时间内被禁用。-他们明智地选择了选项2:使用volatile。public volatile boolean标志=false;尝试将volatile关键字添加到变量中。记住Java多线程要求您注意内存可见性语义。对Java字段或其他共享内存的更改可能对其他线程不可见,除非在触发器出现之前发生某种情况,例如volatile或synchronized关键字或使用Java.util.concurrent中的某个类。如果您想了解更多信息,Brian Goetz的《Java并发实践》是Java程序员关于多线程的圣经。您还应该阅读第17章“线程和锁”的开头部分,其中讨论了所有多线程。@AdamJarvis在设计Java时,他们可以做出两种选择:1假设任何对象都可以在任何时候被任何线程修改,并且在99.8%的时间里,会丢失大量有价值的优化,而这些优化没有发生。2需要访问可能被其他线程修改的对象的代码,以使用某种形式的指示,表明这将发生,因此优化只在需要的0.2%时间内被禁用。-他们明智地选择了选项2。