【问题标题】:Thread has its own copy of data?线程有自己的数据副本吗?
【发布时间】:2020-04-15 17:45:58
【问题描述】:

我在某处读到每个线程都有自己的共享状态副本,即使我使用同步或锁来修改变量,什么保证更改的状态将被刷新到主内存而不是线程自己的内存中高速缓存。

我知道 volatile 保证并证明上述情况是合理的,即使我也知道同步证明也是合理的。

同步如何保证更改值发生在主内存而不是线程缓存内存中。

前线程 1

synchronized(this)
{
int a = 0;
a = 5;
}  ----> the value might got changed in thread's cache memory another thread entering the block could read a value as 0

volatilte int a = 0;
a = 5; ----> another executing thread will read a values as 5

【问题讨论】:

  • 请注意,@CodeScale 的答案没有使用“自己的副本”这个短语。 Java 语言规范 (JLS) 也没有。它也没有说“缓存”或“主内存”。 JLS 描述了一种理想化的虚构 Java 机器,其中每个变量都只存在于一个地方。但是当两个或多个线程共享变量时,JLS 只承诺如果程序遵循某些规则,他们将“看到”它们的一致视图。在任何真正的 Java 实现中,变量的缓存副本都是真实存在的,但是在决定程序是否遵守其规则时,您应该采用 JLS 的语言。

标签: java multithreading concurrency synchronization


【解决方案1】:

在考虑 java 中的多线程代码时,您必须根据 happens-before 进行推理,这是 JLS 使用的,也是您应该使用的。期间。

以您的示例为例,您将volatilesynchronized 一起咀嚼,就好像他们做同样的事情一样,有点-他们没有。即使您的示例已损坏,“其他”线程也可以看到有保证的a = 5,它必须相同的锁上同步,而你没有。一个 jcstress 测试证明你错了(我会让你弄清楚如何准确地运行它)

@JCStressTest
@State
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "SURPRISE")
@Outcome(id = "0, 1", expect = Expect.ACCEPTABLE, desc = "whatever")
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE, desc = "whatever")
@Outcome(id = "1, 1", expect = Expect.ACCEPTABLE, desc = "whatever")
public class DifferentSynchronizedObjects {

    int x, y;
    private Object lock = new Object();

    @Actor
    public void actor1() {
        synchronized (lock) {
            x = 1;
            y = 1;
        }
    }

    @Actor
    public void actor2(II_Result r) {
        r.r1 = x;
        r.r2 = y;
    }
} 

即使你看不懂代码,它的主要“卖点”是这样的:

@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "SURPRISE")

您可以将其解读为:“当您处于同步(锁定){....} 中时,其他线程来读取 x 和 y”。那个阅读线程看到1, 0x = 1y = 0)现在想想。您在synchronized 块中,为什么某些线程读取x = 1y = 0,您不是“受保护”吗?不,你不是。如果你运行它 - 你会得到1, 0

编辑以回复评论

认为您正在保护对xy 的写入-但是由于JLS 没有做出这样的保证,这是您对事物的理解,这是错误的。就如此容易。您实际获得的唯一保护是您的作者 阅读器是否使用相同的锁。

优化器可能会“看到”您仅在方法内部使用 lock,因此将该代码转换为(至少在理论上):

@Actor
public void actor1() {

    Object lock = new Object(); // < --  make lock local       

    synchronized (lock) {
        x = 1;
        y = 1;
    }
}

既然lock 现在是一个方法的本地,那么拥有它有什么意义呢?没有人可以访问它,并且完全忽略它。因此,您将获得执行两次独立写入的完全不受保护的代码。

结论是:你没有遵守JLS 给你的规则——准备好得到奇怪的结果。

【讨论】:

  • 这可能是因为正在执行同步块的线程还没有到达语句y=1,当时另一个线程读取了y as 0的值,这是可能的as per my limited knowledge ;但是另一个线程读取了x = 1的值;同步块如何帮助在关系之前发生?通过将更新推送到所有线程共有的内存中?还是cache coherency protocol will take care of happen before有点困惑?
  • @Shelly 但不是synchronized 块中的线程吗?当他们都受到那个锁的保护时,有人怎么能在这两个写入之间查看呢?你看到这里的讽刺了吗?
  • 好的!明白了,但这怎么可能? thread 1 executes sync block; after that thread 2 enters the block 在这里的输出怎么可能是 1 和 0 发生在关系完成之前?
  • 我在上面的上下文中得到了你真正想要解释的东西,在方法本身中使用 Object lock = new Object(); 语句没有任何好处,但是如果所有线程都锁定在同一个不同的对象上,结果是保证对吗?如果是,我是否应该考虑其他线程能够看到更新的结果,因为发生在关系之前?我应该假设线程没有更新其缓存中的值吗?所以所有线程都在引用一些公共内存,所有共享对象都保存在那里并且所有更改都在那里发生?
  • @Shelly 相同的 distinct 对象?这些是相反的术语。如果您真的想要“相同”,那么是的:如果读取和写入线程确实使用相同的锁,则将存在“先发生”规则。这是如何在内部发生的,是您不应该关心的实现细节(无论如何它非常复杂)。
【解决方案2】:

如果不使用 synchronized 关键字(或 volatile 关键字),则无法保证当一个线程更改与其他线程共享的变量的值时,其他线程可以看到更改的值。无法保证一个线程保存在 CPU 寄存器中的变量何时“提交”到主内存,也无法保证其他线程何时从主内存“刷新”保存在 CPU 寄存器中的变量。

使用synchronized 关键字,当线程进入同步块时,它将刷新线程可见的所有变量的值(在同一个锁/对象上)。当线程退出同步块时,对线程可见的变量的所有更改以及相同的锁将被推送/更新到主内存。 其实和volatile的作品是一样的。

【讨论】:

  • 仅对线程在离开同步块之前执行的写入以及随后进入同步块为同一对象执行的写入提供可见性保证。因此,不需要提交或重新读取所有变量,因为只有两个线程都知道的变量会受到影响。并且当 synchronized(...) 中使用的对象不为其他线程所知时,根本不会影响任何变量。此外,使可见并不一定意味着访问主内存。
  • 当线程退出同步块时,对线程可见的变量的所有更改都将被推送/更新到主内存this is wrong。您需要明确指出这是用于同一个锁,而且当某些东西进入主内存时也很少出现这种情况;在大多数情况下,缓存一致性协议会以不同的方式处理这个问题。
  • 感谢指正。关于这个it is very rarely the case when something goes to main memory; in most general case cache coherency protocol will take care of this in different ways.,它是否改变了进入同步块的下一个线程将始终看到对象/锁变量的“新值”这一主要思想?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多