5

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

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,例如,

AtomicReference<Shared> shared = ....

然后作者将交换参考:

Shared prev = shared.get(); 
Shared newValue = new Shared("state",1);
while (!shared.compareAndSet(prev, newValue)) 

这种方法正确吗?谢谢!

更新在我的设置中,共享对象是从 a 中检索的ConcurrentHashMap<Id,Shared>,因此评论一致认为,要走的路要么使用不可变方法,要么通过将 shared 上的更新同步在一起。但是,为了完整起见,很高兴知道上面的解决方案ConcurrentHashMap<Id,AtomicReference<Shared>>是可行的还是错误的或者只是多余的。谁能解释一下?谢谢!

4

4 回答 4

1

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

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; }

否则,读者可以“看到”具有新名称但具有旧 SSN 的对象。

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

于 2014-01-22T16:08:09.337 回答
1

首先,您应该使Shared不可变:

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

如果你只有一个 writer,你可以安全地使用 volatile,在 AtomicRefference 中就不需要了。在更新信息时,不应修改旧对象,而是创建一个新对象并将其分配给 volatile 引用。

于 2014-01-22T12:50:24.757 回答
1

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

例如

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;
  } 
}
于 2014-01-22T15:28:42.417 回答
1

将字段标记为 volatile 或使方法同步并不能确保原子性。

作家应该注意原子性。

Writer 应该调用同步块内的所有设置器(应该以原子方式更新)。同步(共享){ shared.setA() shared.setB() ... }

为此,共享对象中的所有 getter 也应该同步。

于 2014-01-22T16:31:40.750 回答