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

单线程场景下是否需用wait/notify?移除该机制的影响分析

Java进度条线程同步问题解答

先给大家简单介绍下这段代码的功能:它实现了一个可交互的进度条——按下任意按键时进度条会填充,没有按键输入时进度条会自动缩减,核心是通过线程同步来协调进度条的填充和消耗逻辑。

完整代码

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.*; 

class MyLabel extends JLabel { 
    int barSize = 0; 
    int maxBarSize; 

    MyLabel(int maxBarSize) { 
        this.maxBarSize = maxBarSize; 
    } 

    public void paintComponent(Graphics g) { 
        super.paintComponent(g); 
        g.setColor(Color.magenta); 
        int width = (int) (((double) (this.getWidth())) / maxBarSize * barSize); 
        if (width == 0) return; 
        g.fillRect(0, 0, width, this.getHeight()); 
    } 

    synchronized void fill() { 
        if (barSize == maxBarSize) { 
            try { 
                wait(); 
            } catch (InterruptedException e) { 
                return; 
            } 
        } 
        barSize++; 
        repaint(); 
        notify(); 
    } 

    synchronized void consume() { 
        if (barSize == 0) { 
            try { 
                wait(); 
            } catch (InterruptedException e) { 
                return; 
            } 
        } 
        barSize--; 
        repaint(); 
        notify(); 
    } 
} 

class ConsumerThread extends Thread { 
    MyLabel bar; 

    ConsumerThread(MyLabel bar) { 
        this.bar = bar; 
    } 

    public void run() { 
        while (true) { 
            try { 
                sleep(200); 
                bar.consume(); 
            } catch (InterruptedException e) { 
                return; 
            } 
        } 
    } 
} 

public class TabAndThreadEx extends JFrame { 
    MyLabel bar = new MyLabel(100); 

    TabAndThreadEx(String title) { 
        super(title); 
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        Container c = getContentPane(); 
        c.setLayout(null); 
        bar.setBackground(Color.orange); 
        bar.setOpaque(true); 
        bar.setLocation(20, 50); 
        bar.setSize(300, 20); 
        c.add(bar); 
        c.addKeyListener(new KeyAdapter() { 
            public void keyPressed(KeyEvent e) { 
                bar.fill(); 
            } 
        }); 
        setSize(350, 200); 
        setVisible(true); 
        c.requestFocus(); 
        ConsumerThread th = new ConsumerThread(bar); 
        th.start(); 
    } 

    public static void main(String[] args) { 
        new TabAndThreadEx("press any key"); 
    } 
}

问题1:移除fill和consume方法中的wait与notify机制,程序会出现什么问题?

咱们从两个核心问题来分析:

  1. 数据越界与逻辑异常:

    • 当进度条已经填满(barSize == maxBarSize)时,如果用户继续按键,fill方法会直接执行barSize++,导致barSize超过最大值。此时计算进度条宽度会得到超出组件范围的值,绘制出的进度条会“溢出”原本的区域。
    • 反过来,当进度条已经空了(barSize == 0)时,消费线程还会继续执行barSize--,让barSize变成负数。虽然代码里有if (width == 0) return的判断,但负数的barSize需要先回到0才能正常增长,逻辑上已经出现异常。
  2. 无效操作与资源浪费:

    • barSize是多个线程共享的变量:一个是处理按键事件的AWT事件调度线程,另一个是ConsumerThread。虽然方法加了synchronized保证原子性,但没有wait/notify的话,线程会在条件不满足时依然执行无效操作——比如反复尝试填满已经满的进度条,或者消耗空的进度条,不仅浪费CPU资源,还可能因为线程交替执行导致barSize的状态出现不可预测的情况。

问题2:当前程序仅存在一个ConsumerThread线程,是否仍需要使用wait和notify?若需要,原因是什么?

当然需要!别忽略了,这里不止有ConsumerThread一个线程——还有处理按键事件的AWT事件调度线程,它和ConsumerThread是两个独立的线程,共享MyLabel里的barSize变量,必须靠wait/notify来协调执行:

  1. 避免无效空转,节省资源:

    • 当进度条已经填满时,事件线程调用fill就没必要继续执行递增操作了,通过wait()让它暂停,直到消费线程消耗了一部分进度条(调用notify()唤醒)再继续填充,避免空转浪费CPU。
    • 同理,当进度条为空时,消费线程调用consume也应该暂停,直到事件线程填充了进度条再被唤醒,不然它会每隔200ms就执行一次无效的递减操作。
  2. 保证逻辑的绝对正确性:

    • wait/notify是线程间的等待通知机制,能确保只有当条件满足时(比如进度条没满才能填充,没空才能消耗),线程才会执行对应的操作,从根本上避免了barSize越界的问题,让进度条的填充和消耗逻辑完全符合预期。
  3. 配合synchronized避免死锁:

    • 虽然synchronized已经保证了barSize的原子性和可见性,但wait/notify是这套同步机制的关键补充——如果没有wait,线程会一直持有锁,导致另一个线程无法执行对应的操作,反而引发死锁风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 07:32:32