Java内存模型与并发读

Java内存模型与并发读,java,multithreading,java-memory-model,Java,Multithreading,Java Memory Model,有没有可能o2==null&&o1!=空值? 为什么? 为了明确我的意思,我编辑了: 如果出现以下情况,该怎么办: C c = new C(); Thread#1 Object o1 = c.get(); // 1 Object o2 = c.get(); // 2 Thread#2 c.set(new Object()); 这是不可能的,尽管事实上你有一个数据竞赛 数据竞争是因为围绕着o的get和set没有同步,这意味着在对它们进行排序之前不会发生任何事情。您可以通过使这两种方法同

有没有可能
o2==null&&o1!=空值
? 为什么?


为了明确我的意思,我编辑了:

如果出现以下情况,该怎么办:

C c = new C();  

Thread#1
Object o1 = c.get(); // 1
Object o2 = c.get(); // 2

Thread#2
c.set(new Object());

这是不可能的,尽管事实上你有一个数据竞赛

数据竞争是因为围绕着
o
的get和set没有同步,这意味着在对它们进行排序之前不会发生任何事情。您可以通过使这两种方法同步,或者使
o
不稳定,或者通过其他一些方法来解决这个问题

在没有同步的情况下,JVM可以像其他线程一样对事件重新排序。从Thread1的角度来看,您有以下事件(为简单起见,方法内联):

幸运的是,有两个限制使这项工作正常进行:

  • c.c=null
    发生在所有其他操作之前(请参阅)
  • 从给定线程的角度来看,在该线程上发生的操作总是按照它们在代码中出现的相同顺序发生(也是JLS 17.4.5)。因此,对于Thread1,
    o1=c.o
    发生在
    o2=c.o
    之前。(Thread2不必按该顺序查看这些读取……但它根本看不到
    o1
    o2
    ,因此没有问题。)
  • 其中第一个意味着我们不能执行
    c.o=null
    操作并在
    c.c=newobject()之后对其排序。第二个意思是,从Thread1的角度来看,您在文章底部提到的重新排序是不允许的(当然,Thread1是唯一一个看到o1或o2的线程)

    结合这两个限制,我们可以看到,如果Thread1曾经看到
    c.o
    为非null,那么它将永远不会看到它再次恢复为null。如果o1不为空,那么o2也必须为空

    请注意,这是相当易变的。例如,假设Thread2不只是设置
    c.o
    一次,而是设置两次:

    c.o = null; // initial value
    o1 = c.o;
    o2 = c.o;
    c.o = new Object(); // from Thread2
    
    在这种情况下,可以看到
    o1
    为“二”,而
    o2
    为“一”。这是因为那里的业务有:

    c.set("one");
    c.set("two");
    
    JVM可以按照自己认为合适的方式从Thread2对项目重新排序,只要它们不在Thread2之前
    c.c=null
    。特别是,这是有效的:

    c.o = null; // initial value
    o1 = c.o;
    o2 = c.o;
    c.c = "one"; // from Thread2
    c.c = "two"; // from Thread2
    

    通过将get和set同步到
    o
    ,删除数据争用将解决这一问题。

    尽管存在数据争用,但这是不可能的

    数据竞争是因为围绕着
    o
    的get和set没有同步,这意味着在对它们进行排序之前不会发生任何事情。您可以通过使这两种方法同步,或者使
    o
    不稳定,或者通过其他一些方法来解决这个问题

    在没有同步的情况下,JVM可以像其他线程一样对事件重新排序。从Thread1的角度来看,您有以下事件(为简单起见,方法内联):

    幸运的是,有两个限制使这项工作正常进行:

  • c.c=null
    发生在所有其他操作之前(请参阅)
  • 从给定线程的角度来看,在该线程上发生的操作总是按照它们在代码中出现的相同顺序发生(也是JLS 17.4.5)。因此,对于Thread1,
    o1=c.o
    发生在
    o2=c.o
    之前。(Thread2不必按该顺序查看这些读取……但它根本看不到
    o1
    o2
    ,因此没有问题。)
  • 其中第一个意味着我们不能执行
    c.o=null
    操作并在
    c.c=newobject()之后对其排序。第二个意思是,从Thread1的角度来看,您在文章底部提到的重新排序是不允许的(当然,Thread1是唯一一个看到o1或o2的线程)

    结合这两个限制,我们可以看到,如果Thread1曾经看到
    c.o
    为非null,那么它将永远不会看到它再次恢复为null。如果o1不为空,那么o2也必须为空

    请注意,这是相当易变的。例如,假设Thread2不只是设置
    c.o
    一次,而是设置两次:

    c.o = null; // initial value
    o1 = c.o;
    o2 = c.o;
    c.o = new Object(); // from Thread2
    
    在这种情况下,可以看到
    o1
    为“二”,而
    o2
    为“一”。这是因为那里的业务有:

    c.set("one");
    c.set("two");
    
    JVM可以按照自己认为合适的方式从Thread2对项目重新排序,只要它们不在Thread2之前
    c.c=null
    。特别是,这是有效的:

    c.o = null; // initial value
    o1 = c.o;
    o2 = c.o;
    c.c = "one"; // from Thread2
    c.c = "two"; // from Thread2
    

    通过将get和set同步到
    o
    ,删除数据争用,可以解决这个问题。

    我不明白
    get()
    如何可能会返回除
    null
    之外的任何内容
    o
    永远不会初始化,
    set()
    仅在参数为
    null
    No时修改
    o
    ,因为c1是在c2之前检索的,在任何其他线程中都无法将其还原为null。但是,c2可能是非空的,而c1是空的。我不知道
    get()
    如何可能返回除
    null
    之外的任何内容
    o
    永远不会初始化,
    set()
    仅在参数为
    null
    No时修改
    o
    ,因为c1是在c2之前检索的,在任何其他线程中都无法将其还原为null。但是,当c1为空时,c2可以为非空null@Gilgamesz有趣的是,您编辑了它,但仍然没有解决这样一个事实,即键入
    get()
    是为了返回对象,但是当您调用它时,您尝试将其分配给一个C。我是否误解了它应该是
    Object c1=C.get()
    ?@Gilgamesz我编辑了我的答案来解释为什么它仍然是安全的。“在线程上发生的操作总是按照它们在代码中出现的相同顺序(也就是JLS 17.4.5)在之前正式发生,所以o1=c.o发生在o2=c.o之前。”你错了。请考虑简单程序的输出:int x,y;螺纹#1:x=1;y=1;线程2:不同步打印(y);在您的示例中,线程1将看到对x的写入发生在写入之前