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
相关产品推荐
相关产品推荐

