被Synchronized同步块所保护的对象一定是安全的么?

在并发开发中,通常使用Synchronized确保对象线程安全,但并非绝对。文章通过示例展示了即使在同步块中,若调用wait()可能导致锁释放,从而影响事务一致性。为避免此类问题,建议避免在计算过程中中断同步,并在设置值前做乐观检查以确认状态一致。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在做并发开发的时候,为了保证某对象的线程安全,一般都会对其用Synchronized进行保护,比如:

synchronized(obj) {

   // Do something with obj.

}

当所有对obj进行的操作,都用同步块保护时,我们一般认为其是线程安全的。


但这个线程安全到底有多安全呢?请先看看这个简单的例子:

代码实现很简单,两个线程同时修改一个对象的属性,在其属性原值基础上进行计算,然后再回写;

但是这里面故意使用了 obj.wait(),实际上造成对synchronized同步锁的释放。


public class SynchronizedWrongUse {

    public static void main(String[] args) throws Exception {
        // 初始化
        SomeoneObject obj = new SomeoneObject();
        Worker a = new Worker("A", obj); // 负责给obj +50
        Worker b = new Worker("B", obj); // 负责给obj +50
        
        // 启动线程
        a.start();
        b.start();

        // 等待线程启动
        Thread.sleep(100);

        // 唤醒obj
        synchronized (obj) {
            obj.notifyAll();
            System.out.println("NotifyAll done.");
        }
        // 等待线程结束

        a.join();
        b.join();

        //输出结果
        System.out.println("Amount: " + obj.getAmount()); // 只有50
    }

    /**
     * 并发线程,仅负责给obj +50
     */
    static class Worker extends Thread {
        private SomeoneObject obj;

        public Worker(String name, SomeoneObject obj) {
            super(name);
            this.obj = obj;
        }

        public void run() {
            System.out.println("Worker[" + this.getName() + "] started.");
            synchronized (obj) { // 为保证对obj操作的互斥性,加同步锁
                int amount = obj.getAmount();
                amount += 50; // 增加50
                try {
                    obj.wait(); // 等待主线程通知(这里将释放obj的同步锁)
                } catch (InterruptedException e) {
                    return;
                }
                obj.setAmount(amount);
                System.out.println("Worker[" + this.getName() + "] setting done.");
            }
        }
    }

    /**
     * 某对象
     */
    static class SomeoneObject {
        int amount; // 属性

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }
    }
}


两个线程各 +50,期望结果是100;但实际结果仍然只有50。


接触过事务的人应该能很快理解,在synchronized块中,未必能绝对保证事务一致性;因为wait释放了同步锁。


当然,避免这种情况发生一般来说也很简单:

1、尽量不要将基于原有状态的计算过程被中断,也就是你别在 amount += 50; 中间插入wait;

2、可以做乐观检查,也就是set之前重新检查下当前状态是否与原状态保持一致,不一致就要重新发起计算。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值