【问题标题】:multiple fields: volatile or AtomicReference?多个字段:volatile 还是 AtomicReference?
【发布时间】:2014-02-12 12:46:42
【问题描述】:

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

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)) 

这种方法正确吗?谢谢!

更新在我的设置中,共享对象是从ConcurrentHashMap&lt;Id,Shared&gt; 检索的,因此 cmets 同意要走的路是使用不可变方法或通过将共享上的更新同步在一起。但是,为了完整起见,很高兴知道上面使用 ConcurrentHashMap&lt;Id,AtomicReference&lt;Shared&gt;&gt; 的解决方案是可行的、错误的还是多余的。谁能解释一下?谢谢!

【问题讨论】:

  • 如果你必须原子地写入a和b,那么解决方案确实是使用不可变的方法。你不需要 AtomicReference,只需要一个简单的 volatile 字段到你当前的 Shared 实例。
  • 如果您使Shared 类不可变并且您只有一个线程编写,那么您不需要原子compareAndSet,只需volatile Shared shared; 就可以完成这项工作。
  • ReadWriteLock怎么样?
  • 我看到了 volatile 的意义,但是如果我没有这样的引用但我从 map 检索共享对象怎么办?我如何在这里使用易失性?我应该将 map> 与不可变的方法一起使用吗?

标签: java multithreading concurrency atomic atomicreference


【解决方案1】:

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

例如

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

【讨论】:

    【解决方案2】:

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

    Writer 应该注意原子性。

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

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

    【讨论】:

    • IMO,最好制作一个单一的、组合的同步设置器,这样作者就不会出错。
    • 同意.. 但是开发人员可能并不总是有权直接修改 bean。
    【解决方案3】:

    如果您需要将 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 的对象。

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

    【讨论】:

      【解决方案4】:

      首先你应该让Shared不可变:

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

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

      【讨论】:

      • 你的意思是声明 volatile shared 然后用一个新的替换它?但是,如果我没有这样的参考,但我从 map> 中检索共享怎么办?
      • 是的,就是这样。当多个线程尝试同时写入引用时,需要 AtomicRefference 和 CAS。您正在做的事情称为发布,它可能只是 volatile。
      • 好的,我明白了。至于我上面的编辑:如果我没有这样的参考,但我从 map 检索共享对象怎么办?我应该在这里使用 map> 吗?
      • 然后使用 ConcurrentHashMap
      • 感谢 Mikhail,请多多包涵 :) 我实际上使用 ConcurrentHashMap。现在,假设一个读者确实进入地图并阅读,并且就在作者确实为相同的 id 放入一个新对象之后。阅读器就像一个旧副本,不是吗?
      猜你喜欢
      • 2010-09-21
      • 1970-01-01
      • 2019-10-06
      • 2015-06-22
      • 1970-01-01
      • 1970-01-01
      • 2019-04-13
      • 2010-12-09
      • 1970-01-01
      相关资源
      最近更新 更多