【问题标题】:Starting and Stopping Threads with Buttons使用按钮启动和停止线程
【发布时间】:2012-06-17 18:22:14
【问题描述】:

我有一个我认为很简单的问题,但我还没有找到一个好的解决方案:我希望能够通过点击 Swing 界面上的按钮来暂停和取消暂停线程中发生的活动面板。

具体来说,我想用一个线程实时接收音频帧;第二个线程对这些帧执行魔术处理;第三个线程将结果序列化并通过套接字发送到其他地方。更重要的是,根据我们使用的魔法品牌,第二个线程中的每帧处理可能需要比实际数据收集更长的时间来执行,并且数据可能会在一段时间后堆积起来。

作为非常粗略的原型解决方法,我们认为我们应该添加一个带有按钮的 GUI,以打开和关闭音频收集过程和一个状态栏(稍后实现),以便用户可以密切关注缓冲区(链接阻塞队列)的填充程度。

这比我预期的要难。我将问题简化为一个玩具版本:一个可以存储 50 个整数的链接阻塞队列、一个 GUI、两个线程(以不同的速率添加到队列中和从队列中删除)和一个包裹在布尔值周围的 Token 对象。它看起来像这样,它有点工作:

Test.java

public class Test {
  public static void main(String[] args) throws IOException {

    Token t1 = new Token();
    Token t2 = new Token();
    LinkedBlockingQueue<Integer> lbq = new LinkedBlockingQueue<Integer>(50);

    startFill sf = new startFill(t1, lbq);
    startEmpty se = new startEmpty(t2, lbq);

    TestUI testUI = new TestUI(t1, t2, lbq);
    testUI.setVisible(true);

    sf.start();
    se.start();
  }
}

TestUI.java

public class TestUI extends JFrame implements ActionListener {
  private JToggleButton fillStatus, emptyStatus;
  public boolean filling, emptying;
  public Token t1, t2;
  public LinkedBlockingQueue<Integer> lbq;

  public TestUI(Token t1, Token t2, LinkedBlockingQueue<Integer> lbq) {
    this.t1 = t1;
    this.t2 = t2;
    this.lbq = lbq;
    initUI();
  }

  public synchronized void initUI() {
    JPanel panel = new JPanel();
    panel.setLayout(null);

    filling = false;
    fillStatus = new JToggleButton("Not Filling");
    fillStatus.setBounds(20, 20, 150, 25);
    fillStatus.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        if (filling == false) {
          fillStatus.setText("Filling");
        } else {
          fillStatus.setText("Not Filling");
        }
        filling = !filling;
        t1.flip();
        System.out.println("fill button press");
      }
    });

// Similar code for actionListener on Empty button, omitted

    panel.add(fillStatus);
    panel.add(emptyStatus);
    add(panel);
    setTitle("Test interface");
    setSize(420, 300);
    setLocationByPlatform(true);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
  }

  public void actionPerformed(ActionEvent e) {
  }
}

startFill.java

public class startFill extends Thread {
  public Token token;
  public LinkedBlockingQueue<Integer> lbq;

  public startFill(Token token, LinkedBlockingQueue<Integer> lbq) {
    this.token = token;
    this.lbq = lbq;
  }

  public void run() {
    int count = 0;
    while (true) {
      while (!token.running()) {
        try {
          sleep(200);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }

      while (token.running()) {
        try {
          lbq.put(count);
          System.out.println("queue size = " + lbq.size());
          count++;
          sleep(100);
        } catch (InterruptedException e1) {
          e1.printStackTrace();
        }
      }
    }
  }
}

还有一个 startEmpty.java 的工作方式大致相同,还有一个 Token.java 是一个布尔状态变量的包装器,为了仁慈的简洁而省略。

这样可行,但代价是在while (!token.running()) 循环中进行轮询。 我尝试使用锁和条件,但失败了,总是得到 IllegalMonitorStateExceptions。

我查看了这个similar question 并设法让它工作,但代价是使用了在 Java 5 和 Java 6 之间明显不同的 yield() 方法,并且似乎非常不鼓励。

所以我的问题是:是否有一些正确或明显更好的方法来做我想做的事情?似乎应该有一种方法可以在没有轮询且可靠的情况下实现这一点方法。

更新:我不确定能否解决以某种方式为应用程序控制音频捕获循环的问题。无论是人类按下按钮,还是基于其他因素做出决策的内部逻辑,我们确实需要能够关闭该死的东西并根据命令使其恢复活力。

【问题讨论】:

    标签: java multithreading swing concurrency


    【解决方案1】:

    除了通过 GUI 手动处理 3 个工作进程之间的同步之外,您还可以在工作人员之间设置工厂阵容:

    • 在您的工作人员之间添加 2 个队列
    • 在队列状态条件下阻塞线程;
      • 读取器(消费者)阻塞空队列
      • 编写者(生产者)在队列已满时阻塞(比如 2n 条消息,其中 n 是该队列的消费者数量。)
    • wait() 在队列上阻止您的线程,notifyAll() 在队列中添加或删除消息后在该队列上。

    这样的设置会自动减慢生产者比消费者运行得更快的速度。

    【讨论】:

    • +1 但是 1) while (true) { 是无限循环,没有任何机会 break; 2) 我会使用 util.Timer 而不是 Thread.sleep() 3) 我会使用 @ 987654327@ 而不是普通的Thread
    • +1 使用生产者消费者模式比手工编写这个逻辑要好,因为很难做到正确。此外,您的代码将更容易理解和调试。
    • 我需要考虑这一点(无论如何都会 +1),但应用程序的性质是,我认为我们确实需要在音频捕获例程中进行某种程度的实际控制。 Swing 按钮是待实现的自动控制的替身。)
    【解决方案2】:

    为什么不实现ArrayBlockingQueue.

    最好使用 java.util.concurrent 包中的 ArrayBlockingQueue 类,它是线程安全的。

    BlockingQueue<String> queue = new ArrayBlockingQueue<String>(100);
    

    【讨论】:

      【解决方案3】:

      这是我尝试做的一种方法:正确使用 wait() 和 notify(),在 Token 对象上同步,如下所示:

      startFill.java run() 方法

        public synchronized void run() {
          int count = 0;
          try {
            // token initializes false
            // wait until notification on button press
            synchronized (token) {
              token.wait();
            }
      
            // outer loop
            while (true) {
              // inner loop runs as long as token value is true
              // will change to false on button press
              while (token.running()) {
                lbq.put(count);
                System.out.println("queue size = " + lbq.size());
                count++;
                sleep(100);
              }
      
              // wait until notification on button press, again      
              synchronized (token) {
                token.wait();
              }
            }
          } catch (InterruptedException e2) {
            e2.printStackTrace();
          }
        }
      

      TestUI.java ActionListener:

      fillStatus.addActionListener(new ActionListener() {
        // t1 was initialized false
        public void actionPerformed(ActionEvent event) {
          if (filling == false) {
            fillStatus.setText("Filling");
            // if false, change t1 status to true
            t1.flip();
            // and send the notification to the startFill thread that it has changed
            synchronized (t1) {
              t1.notify();
            }
          } else {
            fillStatus.setText("Not Filling");
            // if true, change t1 status to false
            t1.flip();
            // no notification required due to polling nature of startFill's active thread
          }
          filling = !filling;
          System.out.println("fill button press");
        }
      });
      

      这很好用,在线程关闭时无需轮询。

      由于语法错误,我最初的尝试失败了——我忽略了 wait() 和 notify() 语句周围的 synchronized (token) {...} 上下文块。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-13
        • 1970-01-01
        • 1970-01-01
        • 2014-06-12
        • 2023-04-07
        相关资源
        最近更新 更多