Java 多字段:volatile还是AtomicReference?

Java 多字段:volatile还是AtomicReference?,java,multithreading,concurrency,atomicity,atomicreference,Java,Multithreading,Concurrency,Atomicity,Atomicreference,我必须同步线程之间对共享对象的访问,该对象的状态由几个字段组成。说: class Shared{ String a; Integer b; //constructor, getters and setters .... } 我可能有很多线程读这个对象,做 //readers shared.getA(); shared.getB(); 并且只有一个线程将在某一点写入: //writer shared.setA("state"); shared.setB(1); 现在我的问题是如何确保读取线程

我必须同步线程之间对共享对象的访问,该对象的状态由几个字段组成。说:

class Shared{
String a; Integer b;
//constructor, getters and setters
....
}
我可能有很多线程读这个对象,做

//readers
shared.getA();
shared.getB();
并且只有一个线程将在某一点写入:

//writer
shared.setA("state");
shared.setB(1);
现在我的问题是如何确保读取线程不会发现处于不一致状态的共享对象

我读过很多答案,说为了线程之间的一致性,volatile是解决方案,但我不确定它如何在多个字段上工作。例如,这足够了吗

volatile  String a; volatile Integer b;
另一个解决方案是使共享对象不可变并使用原子引用,例如

AtomicReference<Shared> shared = ....
这种方法正确吗?谢谢


更新在我的设置中,共享对象是从一个
ConcurrentHashMap
中检索的,因此评论一致认为,要么使用不可变的方法,要么通过同步所有共享对象上的更新。然而,为了完整性,最好知道上面使用
ConcurrentHashMap
的解决方案是可行的还是错误的,或者只是多余的。有人能解释吗?谢谢

首先,您应该使
共享
不可变:

class Shared{
   private final String a; 
   private final int b;
   //constructor, getters and NO setters
}

如果只有一个编写器可以安全地使用volatile,那么就不需要原子引用。在信息更新时,不应修改旧对象,而应创建一个新对象并将其分配给易失性引用。

正如@Mikhail在其回答中所说,使
共享
不可变并替换整个对象是一种很好的方法。如果出于某种原因您不想或“不能”使用这种方法,您可以确保
Shared
上的所有字段都受同一个锁的保护,并且它们只能一起修改(在我的示例中,请参见
update
),那么它们不可能处于不一致的状态

e、 g


如果您需要将A和B写在一起以保持它们的一致性,例如它们是一个名称和一个社会保险号,一种方法是在任何地方使用
synchronized
并编写一个单一的组合setter

否则,读者可以使用新名称但使用旧SSN“查看”对象


不可变的方法也很有意义。

将字段标记为易变的或使方法同步不会确保原子性

作者应注意原子性

Writer应该调用同步块中的所有setter(应该进行原子更新)。 同步(共享){ shared.setA() shared.setB() ... }


要使其正常工作,共享对象中的所有getter也应该同步。

如果必须以原子方式写入a和b,那么解决方案确实是使用不可变方法。那么,您不需要原子引用,只需要当前共享实例的一个简单volatile字段。如果您使
Shared
类不可变,并且只有一个线程写入,那么您不需要原子比较数据集,只需要
volatile Shared可以完成这项工作。那么使用呢?我知道volatile的意义,但是如果我没有这样的引用,但我从地图检索共享对象呢?我在这里怎么用?我应该用不变的方法使用map吗?你的意思是声明volatile shared,然后用一个新的替换它?但是,如果我没有这样的引用,但我从地图中检索到共享,那该怎么办呢?是的,就是这样。当多个线程试图同时写入引用时,需要AtomicRefreference和CAS。你所做的就是发布,它可能只是volatile。至于我上面的编辑:如果我没有这样的引用,但我从地图中检索共享对象怎么办?我要用这里的地图吗?那就用ConcurrentHashMap吧。谢谢米哈伊尔,请容忍我:)我实际上用的是ConcurrentHashMap。现在,假设一个读卡器登上地图并阅读,就在一个写卡器为同一个id放入一个新对象之后。读卡器作为一个旧副本,不是吗?我认为,最好创建一个单一的、组合的同步设置器,这样写卡器就不会出错。同意。。但是开发人员可能并不总是拥有直接修改bean的权限。
class Shared{
   private final String a; 
   private final int b;
   //constructor, getters and NO setters
}
class Shared {
  private String a;
  private String b;
  public synchronized String getA() {
    return a;
  }
  public synchronized String getB() {
    return b;
  }
  public synchronized void update(String a, String b) {
    this.a = a;
    this.b = b;
  } 
}
public synchronized void setNameAndSSN(String name, int ssn) {
   // do validation checking etc...
   this.name = name;
   this.ssn = ssn;
}

public synchronized String getName() { return this.name; }

public synchronized int getSSN() { return this.ssn; }