【问题标题】:Need of synchronization in getters and settersgetter 和 setter 需要同步
【发布时间】:2013-12-29 06:20:53
【问题描述】:

可能是一个非常愚蠢的问题。只是想确认一下我的理解。

class Test
{
       private volatile String id;

       public void setID(String id)
       {
             this.id = id;
       }

       public String getID()
       {
             return id;
       }
}

假设上述类的对象可以被多个线程访问。我的理解是,在像上面这样简单的 getter 和 setter 的情况下(进行简单的初始化),我不需要使这些方法同步,对吗?
我想 volatile 是需要的,否则 id 的值可能会在不同的线程中过时。
那么,如果我们没有同步这些方法,任何人都可以看到任何问题吗?

【问题讨论】:

  • 也许这个问题对你有帮助:*.com/questions/15828067/…
  • 不,你也必须在你的方法中同步它
  • 这需要对 volatile 关键字进行同步,没有它,如果 POJO 不是单例或通过所有线程共享,则不需要同步。

标签: java multithreading


【解决方案1】:

我的理解是,如果像上面这样简单的getter和setter(做简单的初始化),我不需要让这些方法同步,对吗?

正确,因为它们获取和设置的内容(对象引用)由 JVM 原子处理。

如果您使用的是longdouble 并且您没有将其标记为@,那么答案将是“不,您确实需要同步” 987654324@.

JLS 中涵盖了这两个方面,§17.7

17.7。双长非原子处理

出于 Java 编程语言内存模型的目的,对非易失性 long 或 double 值的单次写入被视为两次单独的写入:每个 32 位一半。这可能会导致线程从一次写入中看到 64 位值的前 32 位,而从另一次写入中看到后 32 位。

volatile long 和 double 值的写入和读取始终是原子的。

对引用的写入和读取始终是原子的,无论它们是作为 32 位还是 64 位值实现的。

【讨论】:

  • 只是longs?还是这也适用于其他原语?
  • 谢谢,所以在这种情况下,我什至不需要 volatile,对吗?
  • @snow_leopard:不是对象引用,不;你会为longdouble。 (请查看更新的答案——花了很长时间才能修复,愚蠢的宽带掉线了。我意识到你会放volatile,但我没有看到它。volatile 解决了longdouble 的问题。更新后的答案应该更清楚。)
  • @ChrisKnight:longdouble
【解决方案2】:

如果您在multi-threaded 环境中。几个线程可以访问您的数据。读取值(get)很好。但是想想写入(set),那么你的数据就会变得不一致。所以你必须Synchronized

【讨论】:

  • 是的,我忘了补充一点,在我的例子中,只有一个线程会实际设置 ID 并且多个线程可以读取该值。
  • @Ruchira: synchronized 在这里根本没有区别,没有它,该值由 JVM 原子设置。
  • @T.J.Crowder 如果我们有一个synchronized 设置器,那么一次只有一个线程能够设置值,而其他线程将等待
  • @Ruchira:对。这正是没有同步设置器会发生的情况,因为JVM 将自动设置值。差异为零。
【解决方案3】:

您不需要使任何这些函数同步,也不需要使用volatile 关键字,设置引用始终是原子的。然而,非同步/非挥发性会引起其他问题。

首先:线程 A 通过 getID 读取的内容可能不是线程 B 通过 setID 写入的内容,因为线程 A 太早或...

第二:线程 A 准时,但由于缺少 volatile,它正在读取 缓存的线程变量,而不是实际值。

虽然第一个问题只能通过外部线程同步或代码架构来解决,但第二个问题可能会导致基于 happens-before 问题的问题。举个例子:

线程 A:

myId.setId(3);
idSet = true;

线程 B:

if (idSet) {
  accessData(myId.getId());
}

这看起来是正确的——而且有点像——但是在 JVM 优化步骤中可能发生的是首先执行 idSet = true,然后执行 myId.setId(3)。所以在最坏的情况下,线程 B 在 if 子句上成功,但随后读取了错误的值。将 id 标记为 volatile 将解决该问题,因为它保证每当 id 被修改时,之前发生的所有事情都已实际发生。

解决这个问题的另一种方法是使用不可变类,因此没有 setter,id 是 final 并通过构造函数设置。

【讨论】:

    【解决方案4】:
    My understanding is that in case of simple getter and setters like above
    (doing simple initialization), I do not need to make these methods
     synchronized, correct ? 
    

    使变量 volatile 只是确保不会出现竞争条件。所有线程都将读取相同的变量值,因为易失性变量直接存储在主内存中,而不是在线程本地缓存中。

    但是有可能一个线程进入public String getID() 函数。此时其他线程很可能通过执行public void setID(String id) 方法来改变变量的值。第一个线程将看到这个修改后的值。

    所以不要混淆使用原子变量和同步函数。

    【讨论】:

    • 虽然没有同步线程 A 可能进入getID 是真的,但线程 B 可能会在线程 A 读取它之前更改值,使线程 A 返回新值而不是旧值,但它不会操作上的差异,因为所有getID 所做的都是读取并返回值。无论哪种方式,这都是一场比赛。同步 这些特定的 getter 和 setter 的唯一原因是数据完整性(确保值是原子读/写的,所以我们不会得到一半旧值和一半新值),那就是在这种情况下不需要。