如何同步Getter方法避免状态不一致?添加synchronized仍出现异常组合
解决对象状态不一致的同步问题
嘿,这个问题我之前也碰到过!核心原因是:你虽然给每个get方法和set方法都加了synchronized,但单独调用getFirstName()和getLastName()是两个独立的同步操作——这两个操作之间没有原子性保证。比如,线程刚拿到firstName是"John",还没来得及拿lastName的时候,另一个线程执行了set("Jane", "Odd"),这时候再拿lastName就变成了"Odd",自然就出现了"John Odd"这种异常组合。
下面给你几个可行的解决方案:
方案1:提供原子性的完整状态获取方法
不要让调用者分别获取两个属性,而是在类里新增一个同步方法,一次性返回完整的姓名信息。这样就能保证获取操作是原子的,不会被打断:
public class HonorBoard { private volatile String firstName; private volatile String lastName; public void set(String firstName, String lastName) { synchronized (this) { this.firstName = firstName; this.lastName = lastName; } } // 新增:原子获取完整姓名的方法 public synchronized String getFullName() { return firstName + " " + lastName; } // 或者返回一个不可变的姓名对象(更推荐,避免字符串拼接的开销) public synchronized Name getName() { return new Name(firstName, lastName); } @Override public synchronized String toString() { return firstName + ' ' + lastName; } // 自定义不可变姓名类 static class Name { private final String firstName; private final String lastName; public Name(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } @Override public String toString() { return firstName + " " + lastName; } } public static void main(String[] args) { HonorBoard board = new HonorBoard(); new Thread(() -> { for (;;) { board.set("John", "Doe"); } }).start(); new Thread(() -> { for (;;) { board.set("Jane", "Odd"); } }).start(); new Thread(() -> { for (;;) { // 调用原子方法获取完整姓名 System.out.println(board.getFullName()); // 或者用Name类:System.out.println(board.getName()); } }).start(); } }
方案2:在调用端手动加锁包裹获取操作
如果不想修改HonorBoard类的结构,可以在打印线程里直接对board对象加锁,把两个get操作放到同一个同步块里:
new Thread(() -> { for (;;) { synchronized (board) { // 和set方法用同一个锁,保证互斥 System.out.println(board.getFirstName() + ' ' + board.getLastName()); } } }).start();
这样在同一个锁块内执行两个get操作,中间不会被set线程干扰,自然就能拿到一致的姓名组合。
方案3:用不可变对象作为状态载体(最优雅的方式)
把firstName和lastName封装成一个不可变的对象,每次set的时候直接替换整个对象——因为不可变对象一旦创建就不会被修改,天生线程安全,而且volatile可以保证对象引用的可见性:
public class HonorBoard { // volatile保证name引用的可见性 private volatile Name name; public void set(String firstName, String lastName) { // 直接替换整个不可变对象,原子操作 this.name = new Name(firstName, lastName); } public Name getName() { // 直接返回,不可变对象无需同步 return name; } @Override public String toString() { Name currentName = name; return currentName.firstName + " " + currentName.lastName; } // 不可变姓名类 static class Name { private final String firstName; private final String lastName; public Name(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } } public static void main(String[] args) { HonorBoard board = new HonorBoard(); new Thread(() -> { for (;;) { board.set("John", "Doe"); } }).start(); new Thread(() -> { for (;;) { board.set("Jane", "Odd"); } }).start(); new Thread(() -> { for (;;) { Name currentName = board.getName(); System.out.println(currentName.firstName + " " + currentName.lastName); } }).start(); } }
这种方式不需要任何synchronized,利用不可变对象的特性保证状态一致性,代码更简洁高效。
内容的提问来源于stack exchange,提问作者B.E2s
相关产品推荐
相关产品推荐

