使用按钮启动和停止线程

时间:2012-06-17 18:22:15

标签: java multithreading swing concurrency

我认为这是一个简单的问题,我还没有找到一个很好的解决方案:我希望能够通过点击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())循环中的轮询。 我尝试使用Locks and Conditions,但失败了,总是得到IllegalMonitorStateExceptions。

我查看了这个similar question并设法使其工作,但代价是使用了yield()方法,这种方法在Java 5和Java 6之间显然存在显着差异,并且似乎非常气馁。< / p>

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

更新:我不确定是否可以解决以某种方式为应用程序控制音频捕获循环的问题。无论是人类按下按钮,还是根据其他一些因素做出内部逻辑决策,我们真的需要能够关闭这些令人失望的东西并将其恢复到命令状态。

3 个答案:

答案 0 :(得分:3)

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

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

像这样的设置会自动减慢生产者的运行速度,而不是消费者。

答案 1 :(得分:0)

为什么不实施ArrayBlockingQueue.

更好地使用 ArrayBlockingQueue 类,它存在于 java.util.concurrent包中,它是线程安全的。

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

答案 2 :(得分:0)

这是我尝试做的一种方法:正确使用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) {...}上下文块。