需要在getter和setter中进行同步的原因

6

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

class Test
{
       private volatile String id;

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

       public String getID()
       {
             return id;
       }
}

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

也许这个问题可以帮到你:https://dev59.com/rWUo5IYBdhLWcg3wtRQx - pad
不,你必须在你的方法中同步它。 - Java_Alert
这需要使用volatile关键字进行同步,如果不使用它,如果POJO不是单例或共享给所有线程,它将不需要同步。 - RamonBoza
4个回答

8
我的理解是,如果像上面那样只进行简单的初始化操作,那么我不需要将这些方法同步,对吗?
正确,因为JVM会原子地处理它们获取和设置的对象引用。
如果您正在使用long或double,并且没有将其标记为volatile,则答案将是“不,您需要同步”。
这两个方面都在JLS§17.7中有所涉及。 17.7. double和long的非原子处理

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

易失性(long或double)值的读写始终是原子性的。

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


只是“long”类型吗?还是其他原始数据类型也适用? - Chris Knight
谢谢,所以在这种情况下我甚至不需要volatile,对吗? - snow_leopard
@snow_leopard:不是针对对象引用,你需要使用 longdouble。 (请参见更新的答案-花了很长时间才修复,愚蠢的宽带断开了。我意识到你已经放置了 volatile 但我没有看到。volatile 解决了 longdouble 的问题。更新后的答案应该更清晰。) - T.J. Crowder
@ChrisKnight:longdouble - T.J. Crowder

1
你不需要使这些函数同步,也不需要使用volatile关键字,设置引用始终是原子的。然而,非同步/非易失性可能会导致其他问题。
第一:通过getID读取的内容可能与通过setID写入的内容不同,因为Thread A要么太早了,要么...
第二:Thread A按时了,但由于缺乏易失性,它正在读取一个缓存线程变量而不是真实值。
虽然第一个只能通过外部线程同步或代码结构来解决,但第二个可能会基于happens-before问题引起问题。看下面的例子:
Thread 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并通过构造函数设置。


1
如果您处于一个多线程环境中,多个线程可以访问您的数据。读取值(get)是没问题的。但是考虑写入(set),那么您的数据将变得不一致。因此,您需要同步(Synchronized)。

是的,我忘记在我的情况下只有一个线程会设置ID,而多个线程可以读取该值。 - snow_leopard
@Ruchira:在这里synchronized没有任何作用,JVM会原子地设置值而不需要它。 - T.J. Crowder
如果我们有一个“同步”的设置器,那么只有一个线程能够在任一时刻设置该值,而其他线程将等待。 - Ruchira Gayan Ranaweera
@Ruchira:没错。如果没有同步的setter,JVM会原子地设置值,这正是会发生的情况。没有任何区别。 - T.J. Crowder

0
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只是确保不会出现竞争条件。所有线程都将读取同一变量的值,因为volatile变量直接存储在主内存中而不是线程本地缓存中。

然而,可能存在一个线程进入public String getID()函数,在这个时间点上,其他线程可能通过执行public void setID(String id)方法来更改变量的值。第1个线程将看到这个修改后的值。

因此,请不要混淆使用原子变量和同步函数。


虽然没有同步,线程A可能会进入getID,然后线程B可能会在线程A读取它之前更改该值,使得线程A返回新值而不是旧值,但这根本没有任何操作上的区别,因为getID所做的就是读取并返回该值。无论哪种方式都是竞争。仅在这些特定的getter和setter中进行同步的唯一原因是数据完整性(确保以原子方式读取/写入值,以便我们不会获得旧值的一半和新值的一半),而在这种情况下不需要。 - T.J. Crowder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接