【问题标题】:Should you synchronize access to properties in Java? [duplicate]是否应该同步对 Java 中的属性的访问? [复制]
【发布时间】:2018-05-27 12:04:37
【问题描述】:

我最近偶然发现了一篇标题为 Synchronize access to mutable fields 的文章。它声称:

例如,在多线程环境中,可变字段的所有getset 方法通常应该是synchronized 方法。 这包括原始字段

我的问题是为什么?同步getId 方法有什么用?或者如果我不同步它会发生什么。

例如,我将 UserContext 传递给 Spring Service 函数并在函数内调用 getUserId。如果getUserId 没有同步,这会不会是个问题?

【问题讨论】:

  • 如果它是可变的,那么是的,它可能是个问题。它是否真的一个问题取决于你的代码的细节。
  • 如果用户 ID 是可变的,那么就没有人明白 ID 的用途。
  • 任何时候你不遵守 JMM 都会有问题。 “会发生什么?”这个问题的实际答案。取决于平台(例如缓存一致性协议、CPU 存储缓冲区的工作方式、JIT 编译器生成的代码、是否放置了适当的 内存屏障 等...)。

标签: java spring multithreading synchronized


【解决方案1】:

我的问题是为什么?

确保跨线程内存可见性

同步getId方法有什么用?

每个同步的getId 调用都保证给定的 id 在操作结束时是最新的(释放锁)。

或者如果我不同步会发生什么?

一个陈旧的getId 值将用于更新将影响正确性的其他变量。


每个使用变量共享状态操作的方法都需要某种同步。 getter 提供状态,因此需要同步。 setter 改变状态,也需要同步。

想象一个改变 id 的线程(写者)和一个读取该 id 的线程(读者)。如果这些线程没有通过同一个锁来同步它们的操作,那么当线程同时运行时可能会发生奇怪的事情:

  1. 读取器在写入器执行过程中读取变量。
  2. 阅读器可以看到部分(或错误)初始化/设置的值。
  3. 对于读者来说,状态可能已经过时了。

【讨论】:

  • 是的,但是如果id 真的会改变它会随着其他值一起改变,所以同步改变函数会更有意义,而不是 getter 对吧?
  • @Nux,我更新了答案。您所说的“id 实际上会改变它会随着其他值一起改变”是什么意思?
  • 如果您更改 id ,可以安全地假设您正在加载整个记录。因此,同步整个更改而不是单个 setId 调用似乎更好。
  • @Nux 假设它是单个 id 更改并且 setId 没有在其他地方使用,是的,它会起作用。
【解决方案2】:

同步指定对变量的任何访问一次仅限于一个线程。所以它会在功能完成过程中锁定以防止其他人对其进行修改。您可以在此处查看更多详细信息以及官方文档:What does 'synchronized' mean?

就可能发生的情况而言,有一个相当简单的示例,但不使用 ID,因为它不应该改变。假设您有一个名为“cost”(名称无关紧要)的属性,其值为 10,并且有两个线程同时运行可以访问该属性。

进程不同步:

流程 A - 将成本更改为 20 的调用。

流程 B - 调用以获取成本

流程 B - 获取成本 = 10

流程 A - 完成修改成本。

因此,即使线程 A 更改它的成本为 20,但在 B 检索其值之前,该属性并未更新,从而导致接收到错误的信息。

【讨论】:

  • 因此,即使线程 A 更改它的成本为 20,但在 B 检索其值之前该属性未更新,导致接收到错误的信息。 那是不好的例子。如果 B 提前 1 毫秒调用并且没有重叠,你会说 10 是“正确的”,但是当 B 使用 10 时,对象中保存的值仍然是 20。你真正要防止的是 B 得到 -99995123因为当 B 读取它时,该值仅被部分修改。
  • 那么更糟糕的情况是进程 B 获得了“缓存”值?或者得到一些完全不同的东西(就像 Andrew 建议的那样)。
  • 也可以。这完全取决于线程的时间安排如何。您可能会获得正确的值,但也可能会获得缓存的值或无意义的值(即就像 Andrew 所说的那样)。线程时序由操作系统决定,它不知道程序的目的,只知道它需要做的操作队列。所以它在不知道最终结果的情况下以它认为最好的任何顺序执行它们
  • 在大多数情况下,部分修改不是问题,因为对于引用和除longdouble 之外的所有原语,读写都是原子的。你可以看到一个旧值,但不是一个写一半的值。
【解决方案3】:

synchronized 关键字有两种作用:

  1. 只有一个Thread 可以进入给定的方法/代码块。这对于复杂的计算很有用。
  2. 一个Thread 所做的更改将被另一个Thread可见。默认情况下它不是真的。 jvm / 硬件可能会为某些Threads 缓存某些值。您可以在此here 上阅读更多信息。

注意:当且仅当synchronized 块引用同一个对象时,上述陈述才是正确的。由于我们讨论的是 getter 和 setter,这将是正确的:它们将锁定同一个对象。

所以是的,我们需要使用synchronized getter 和 setter。不过有几种选择:

  1. 对于原语,我们可以使用 volatile 关键字,例如private voltaile int sum;。对于 volatile 原语,我们可以使用普通的 getter/setter。
  2. 对于final 原语,我们可以使用普通的setter - 没有getter。感谢您指出:)
  3. 我们可以使用Atomic 值,例如AtomicIntegerAtomicLongAtomicReference,...

【讨论】:

  • 我认为我们不需要 final 原语的设置器;)
猜你喜欢
  • 1970-01-01
  • 2010-09-21
  • 1970-01-01
  • 1970-01-01
  • 2012-12-16
  • 1970-01-01
  • 2020-06-25
  • 2015-12-23
  • 1970-01-01
相关资源
最近更新 更多