You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何同步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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:16:54