Java 变量是否应该在两个运行线程之间易变?

Java 变量是否应该在两个运行线程之间易变?,java,multithreading,volatile,Java,Multithreading,Volatile,在这种情况下,inta是否应该是易失性的,以保证线程之间的可见性 private volatile static int a = 0; public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { a = 10; } })

在这种情况下,
inta
是否应该是易失性的,以保证线程之间的可见性

private volatile static int a = 0;

public static void main(String[] args) {
    
    Thread t1 = new Thread(new Runnable() {
        
        @Override
        public void run() {
            a = 10;
        }
    });

    
    Thread t2 = new Thread(new Runnable() {
        
        @Override
        public void run() {
            System.out.println(a);
        }
    });
    
    t1.start();
    t2.start();
    
}
输出

10
,从阅读开始;首先

然后,要完全理解正在发生的事情,你需要知道是什么,以及

要将其简化,请查看以下内容:

private volatile static int a = 0;
private static int b = 0;

public static void main(String[] args) {

    Thread t1 = new Thread(new Runnable() {

        @Override
        public void run() {
            b = 100;
            a = 10;
        }
    });


    Thread t2 = new Thread(new Runnable() {

        @Override
        public void run() {
            if(a == 10){
                System.out.println(b);
            }

        }
    });

    t1.start();
    t2.start();

}
您唯一能保证的是,如果且仅当,
t2
打印某些内容时,它将始终是
100
。这是因为
t2
看到了
a
的易失性写入。这是因为从写入线程到读取线程都建立了一个“之前发生”,并且在
a=10
之前执行的每个操作都保证对看到
a
10
的线程可见


你能再解释一下“以前发生过”吗

关于“以前发生过”最重要的一点是,它是一个。这意味着,如果Java语言规范(JLS)承诺A“发生在”B之前,并且它承诺B“发生在”C之前,那么您可以推断A“发生在”C之前

JLS表示,对某个
volatile
变量的写入“发生在”对同一变量的后续读取之前

嗯,哼!听起来很明显,不是吗

但这并不明显,因为JLS不能为非易失性变量提供相同的保证。如果处理器A将值7写入非易失性int,然后一段时间后处理器B写入5,则JLS不能保证一段时间后,变量的最终值将为5。处理器B将看到5(这是一个不同的“之前发生”承诺,见下文)。处理器A可以看到5或7,任何其他处理器可以看到5或7或变量最初的任何值(例如,0)

易失性承诺如何帮助

假设我们有

 volatile boolean flag = false;
 /*non-volatile*/ int i = 0;
假设线程A执行以下操作:

i = 7;
flag = true;
if (flag) {
    System.out.println(i);
}
else {
    System.out.println("Bleah!");
}
假设线程B执行以下操作:

i = 7;
flag = true;
if (flag) {
    System.out.println(i);
}
else {
    System.out.println("Bleah!");
}
线程B可以打印“7”,也可以打印“Bleah!”,但由于“before”保证,我们完全知道线程B永远不会打印“0”。为什么不呢

在设置前,先执行设置
i=7
,然后再设置
flag=true
。JLS保证,如果单个线程在执行第二条语句之前执行一条语句,那么第一条语句“发生在”第二条语句之前。(这听起来非常明显,但也不应该如此。许多与线程有关的事情并不明显。)

线程B在打印
i
之前测试
flag
。因此,如果*线程一个先前设置的
flag=true
,那么我们知道
i
必须等于
7
:传递性:
i=7
“发生在”
flag=true
之前,而写入
volatile标志
,如果它发生在读取相同的
flag
之前

如果真的发生了

数据竞争和竞争条件

要记住的最大一点是,当JLS承诺A“发生在”B之前时,他们并不是说A实际上总是发生在B之前:他们是说你可以依赖于这种传递关系。他们说如果A确实发生在B之前,那么所有“发生在”A之前的事情肯定也发生在B之前

程序可以打印“Bleah!”,因为在线程A设置
标志之前,没有任何东西可以阻止线程B测试该标志。有些人称之为“数据竞赛”。两个线程“竞赛”以查看哪个线程首先到达
标志,程序的结果取决于哪个线程赢得了这场竞赛


当程序的正确性取决于数据竞争的结果时,我们中的一些人称之为“竞争条件”,这是一个真正令人头痛的问题。无法保证具有竞争条件的程序在测试期间不会做上千次正确的事情,然后在对客户最重要的时候做错误的事情。

1)您知道哪个线程首先启动吗?(提示:您不需要)2)
volatile
是关于两个实体的-当其他线程看到某些写入时,这意味着
在建立之前发生……您能进一步解释一下“之前发生”吗?你没有真正回答在这种情况下是否需要volatile关键字。问题是,在这个级别上,很难了解基础知识并正确应用它。但是在这种情况下,制作一个volatile就可以了(正如Eugene已经指出的那样,它建立了发生之前的关系)。因为hb(b=100,a=10)因为它们是在同一个线程中按程序顺序执行的动作(规则1),而
a==10
意味着hb(a=10,t2),这意味着规则4中的hb(b=100,t2)-对吗?@daniu是的,但当然,您需要保持在
JLS
的适当范围内。我只是在这里把它的表面擦掉了。但真的很开心。