我必须将线程之间的访问同步到共享对象,其状态由多个字段组成。说:
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<Id,Shared>
检索共享对象,因此评论同意要采用不可变方法或通过同步共享上的更新来实现一起。但是,为了完整性,很高兴知道上面的ConcurrentHashMap<Id,AtomicReference<Shared>>
解决方案是可行的还是错误的还是多余的。谁能解释一下?谢谢!
答案 0 :(得分:1)
首先,你应该Shared
不可变:
class Shared{
private final String a;
private final int b;
//constructor, getters and NO setters
}
如果你只有一个编写器可以安全地使用volatile,那么AtomicRefference就没有必要了。在更新信息时,不应修改旧对象,而是修改新对象并将其分配给易失性参考。
答案 1 :(得分:1)
正如@Mikhail在他的回答中所说,使Shared
不可变并替换整个对象是一个很好的方法。如果您由于某种原因不想或“不能”使用该方法,您可以确保Shared
上的所有字段都受到同一锁的保护,并且它们只能一起修改(在我的例子中看到update
,那么就不可能看到它们处于不一致的状态。
e.g。
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 :(得分:1)
如果您需要同时编写A和B以保持一致,例如它们是名称和社会安全号码,一种方法是在任何地方使用synchronized
并编写单一,组合设置器。
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的对象。
不可变的方法也是有道理的。
答案 3 :(得分:1)
将字段标记为volatile或使方法同步不会确保原子性。
作家应该注意原子性。
Writer应该调用synchronized块中的所有setter(应该以原子方式更新)。 synchronized(shared){ shared.setA() shared.setB() ... }
为此,共享对象中的所有getter也应该同步。