单线程场景下是否需用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机制,程序会出现什么问题?
咱们从两个核心问题来分析:
数据越界与逻辑异常:
- 当进度条已经填满(
barSize == maxBarSize)时,如果用户继续按键,fill方法会直接执行barSize++,导致barSize超过最大值。此时计算进度条宽度会得到超出组件范围的值,绘制出的进度条会“溢出”原本的区域。 - 反过来,当进度条已经空了(
barSize == 0)时,消费线程还会继续执行barSize--,让barSize变成负数。虽然代码里有if (width == 0) return的判断,但负数的barSize需要先回到0才能正常增长,逻辑上已经出现异常。
- 当进度条已经填满(
无效操作与资源浪费:
barSize是多个线程共享的变量:一个是处理按键事件的AWT事件调度线程,另一个是ConsumerThread。虽然方法加了synchronized保证原子性,但没有wait/notify的话,线程会在条件不满足时依然执行无效操作——比如反复尝试填满已经满的进度条,或者消耗空的进度条,不仅浪费CPU资源,还可能因为线程交替执行导致barSize的状态出现不可预测的情况。
问题2:当前程序仅存在一个ConsumerThread线程,是否仍需要使用wait和notify?若需要,原因是什么?
当然需要!别忽略了,这里不止有ConsumerThread一个线程——还有处理按键事件的AWT事件调度线程,它和ConsumerThread是两个独立的线程,共享MyLabel里的barSize变量,必须靠wait/notify来协调执行:
避免无效空转,节省资源:
- 当进度条已经填满时,事件线程调用
fill就没必要继续执行递增操作了,通过wait()让它暂停,直到消费线程消耗了一部分进度条(调用notify()唤醒)再继续填充,避免空转浪费CPU。 - 同理,当进度条为空时,消费线程调用
consume也应该暂停,直到事件线程填充了进度条再被唤醒,不然它会每隔200ms就执行一次无效的递减操作。
- 当进度条已经填满时,事件线程调用
保证逻辑的绝对正确性:
wait/notify是线程间的等待通知机制,能确保只有当条件满足时(比如进度条没满才能填充,没空才能消耗),线程才会执行对应的操作,从根本上避免了barSize越界的问题,让进度条的填充和消耗逻辑完全符合预期。
配合
synchronized避免死锁:- 虽然
synchronized已经保证了barSize的原子性和可见性,但wait/notify是这套同步机制的关键补充——如果没有wait,线程会一直持有锁,导致另一个线程无法执行对应的操作,反而引发死锁风险。
- 虽然
内容的提问来源于stack exchange,提问作者decussat
相关产品推荐
相关产品推荐

