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

Java中synchronized使用及多线程同步等待问题求助

用线程间等待/通知机制解决你的同步问题

嘿,你遇到的是典型的线程间时序同步问题——不需要靠sleep()这种笨办法,Java本身就提供了完美的信号传递机制:wait()/notify()或者更灵活的Condition接口,能让第二个线程乖乖等第一个线程修改完变量再执行检查。

先理清楚问题根源

你现在的代码里,eating()方法的synchronized只保护了「检查变量」那一瞬间,但第一个线程释放锁后去执行goEat()修改count的过程,和第二个线程进入eating()检查变量的过程完全脱节了。两个线程没有任何通信,自然会出现“第二个线程先检查,第一个线程后修改”的时序问题。

我们要做的就是给这两个线程加个“通信协议”:第一个线程改完变量后发个信号,第二个线程没收到信号就等着,收到了再检查。

方案一:用Object的wait/notify实现(最简单)

我们可以把共享资源EatingRoom作为锁对象,让所有线程的等待和通知都基于它,这样就能保证同步。直接改你的代码:

修改后的Animal类

abstract class Animal {
    protected House house;
    abstract boolean eating();
    @Override
    public void run() {
        try {
            while(!Thread.interrupted()) {
                if(eating()) {
                    goEat(); // 这里修改house.eatingRoom.count
                    // 改完了!通知所有等着的线程
                    synchronized (house.eatingRoom) {
                        house.eatingRoom.notifyAll();
                    }
                    Thread.sleep(1000);
                    goback();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

修改后的Cat类

class Cat extends Animal {
    @Override
    boolean eating() {
        synchronized (house.eatingRoom) {
            // 如果count不是0,就等着第一个线程的信号
            while (house.eatingRoom.count.size() != 0) {
                try {
                    // 释放锁,进入等待状态,直到被唤醒
                    house.eatingRoom.wait();
                } catch (InterruptedException e) {
                    // 中断后要重置中断状态,避免线程异常退出
                    Thread.currentThread().interrupt();
                    return false;
                }
            }
            return true;
        }
    }
}

几个重要细节

  • 为啥用while循环检查条件,而不是if?因为线程可能会被「虚假唤醒」——比如JVM或者操作系统的原因,线程没收到notify()也醒了,这时候必须重新检查条件,不然会出现错误。
  • 锁必须绑定到同一个对象上:这里我们用eatingRoom作为锁,因为它是共享变量count的持有者,所有操作count的线程都要基于这个锁同步。
  • notifyAll()会唤醒所有等着的线程,如果你只需要唤醒特定线程,可以用notify(),但notifyAll()更安全,避免线程永远等待。

方案二:用Condition接口(更灵活)

如果以后你需要更精细的线程控制(比如只唤醒猫线程,不唤醒狗线程),可以用Lock搭配Condition,代码结构更清晰:

先改EatingRoom类,把等待/通知逻辑封装进去

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class EatingRoom {
    final Set<Animal> count = new HashSet<>();
    private final Lock lock = new ReentrantLock();
    // 专门创建一个Condition用来通知count更新的事件
    private final Condition countUpdated = lock.newCondition();

    void add(Cat c) {
        lock.lock();
        try {
            count.add(c);
        } finally {
            // 一定要在finally里释放锁,避免异常导致锁永远持有
            lock.unlock();
        }
    }

    // 对外提供检查是否能进食的方法,内部包含等待逻辑
    boolean canEat() throws InterruptedException {
        lock.lock();
        try {
            while (count.size() != 0) {
                // 等待count更新的信号
                countUpdated.await();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

    // 对外提供发送信号的方法
    void notifyCountUpdated() {
        lock.lock();
        try {
            countUpdated.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

然后修改Cat类的eating()方法

class Cat extends Animal {
    @Override
    boolean eating() {
        try {
            // 直接调用EatingRoom封装好的方法
            return house.eatingRoom.canEat();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
}

最后修改Animal类的run方法

abstract class Animal {
    protected House house;
    abstract boolean eating();
    @Override
    public void run() {
        try {
            while(!Thread.interrupted()) {
                if(eating()) {
                    goEat();
                    // 发送变量已修改的信号
                    house.eatingRoom.notifyCountUpdated();
                    Thread.sleep(1000);
                    goback();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

为啥绝对不要用sleep()?

  • sleep()是固定时间阻塞,完全不知道第一个线程什么时候改完变量——设短了还是会出现时序问题,设长了浪费系统资源。
  • 等待/通知机制是事件驱动的,只有当第一个线程完成变量修改这个“事件”发生后,第二个线程才会被唤醒,效率和准确性都拉满。

内容的提问来源于stack exchange,提问作者MUST_DIE

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:35:29