我很好奇下一个代码片段是否是线程安全的,特别是关注带有复合对象的synchronized关键字。如果 updateAge 发生在 getB 之前,第二个来电者会收到更新的年龄值吗?
如果答案是肯定的,请解释JVM如何执行该操作? (我假设JVM代码在将synchronized方法/块退出到主内存时必须刷新访问的对象,JVM代码是从根对象中提取所有引用的对象吗?)
public class A {
private B b;
public B getB() { return b; }
public void setB(B b) { this.b = b; }
}
public class B {
private String name;
private Integer age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
}
public class Main {
private A a;
public Main() {
a = new A();
B b = new B();
b.setName("name");
b.setAge(10);
a.setB(b);
}
public synchronized void updateAge(Integer age){ a.getB().setAge(age); }
public synchronized B getB() { return a.getB(); }
}
更新1:
替代类是否等同于上面的原始Main类?由于ConcurrentMap在put期间执行同步。忽略大小写是2个并发线程调用 updateAge 方法。
public class Main2 {
private ConcurrentMap<String, A> store = new ConcurrentHashMap<>();
public Main2() {
A a = new A();
B b = new B();
b.setName("name");
b.setAge(10);
a.setB(b);
store.put("id", a);
}
public void updateAge(Integer age){
A a = store.get("id");
a.getB().setAge(age);
store.put("id", a);
}
public B getB() { return store.get("id").getB(); }
}
答案 0 :(得分:1)
这取决于线程安全的含义。如果系统只包含这两种方法,那么答案是肯定的,这是线程安全的,因为updateAge()
的任何更改对于getB()
的调用者都是可见的。
但是,由于getB()
返回B
的可变实例,所以没有什么可以阻止我写这样的内容:
Main main = ...;
main.updateAge(42); // we change the age of B in a synchronized block
B myLittleB = main.getB(); //this is synchronized to the same object, so it's all fine
myLittleB.setName("Boaty McBoatface"); //this isn't synchronized so if another thread calls main.getB().getName(), all bets are off
更新:如何实现可见性保证取决于VM实施和体系结构,但有几种可用的替代策略,例如运行时代码分析以确定哪些变量可能在同步块中发生更改,或者甚至不加歧视地冲洗一切。