Reputation: 4783
I have what I thought was a simple problem, which I have yet to find a good solution to: I would like to be able to pause and unpause the activity taking place in a thread, by hitting a button on a Swing interface panel.
Specifically, I would like to use one thread to take in audio frames in real time; a second thread to perform magic processing on those frames; and a third thread to serialize the results and send over a socket somewhere else. The kicker is that depending on the brand of magic we employ, the processing in the second thread might take longer per frame to perform than the actual data collection, and the data might pile up after a while.
As a very crude prototype workaround we thought we'd add a GUI with a button to turn the audio collection process on and off and a status bar (to be implemented later) so that a user could sort of keep an eye on how full the buffer (a linked blocking queue) happened to be.
This is harder than I anticipated. I've stripped the problem down to a toy version: A linked blocking queue that can store 50 Integers, a GUI, two threads (adding to and removing from the queue at different rates) and a Token object wrapped around a boolean. It looks like this, and it sorta works:
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();
}
}
}
}
}
There is also a startEmpty.java that works about the same way, and a Token.java that's a wrapper for a boolean state variable, omitted for merciful brevity.
So that works, but at the expense of polling in the while (!token.running())
loop.
I tried using Locks and Conditions, but failed, always getting IllegalMonitorStateExceptions.
And I looked at this similar question and managed to get that working, but at the expense of using the yield() method which apparently differs significantly between Java 5 and Java 6, and seems to be highly discouraged.
So my question: Is there some correct, or significantly better way to do what I am trying to do? It seems like there should be a way to make this happen without the polling and with reliable methods.
Update: I'm not sure I can get around the issue of controlling the audio capture loop in some way for the application. Whether it is a human pressing a button, or internal logic making decisions based on some other factors, we really need to be able to shut the darn thing down and bring it back to life on command.
Upvotes: 1
Views: 1745
Reputation: 4783
Here is one way to do what I was trying to do: Properly use wait() and notify(), synchronized on the Token objects, like such:
startFill.java run() method
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");
}
});
This works rather nicely, without polling while the thread is turned off.
My initial attempts at this failed due to bad syntax-- I neglected the synchronized (token) {...}
context block around the wait() and notify() statements.
Upvotes: 0
Reputation: 33534
Why dont you implement ArrayBlockingQueue.
Its Better use ArrayBlockingQueue class which is present in java.util.concurrent package, which is Thread Safe.
BlockingQueue<String> queue = new ArrayBlockingQueue<String>(100);
Upvotes: 0
Reputation: 23373
Instead of handling the synchronisation between your 3 worker processes by hand via a GUI, you could also setup a factory lineup between the workers:
wait()
on a queue to block your thread and notifyAll()
on that queue after adding or removing a message from a queue.A setup like this automatically slows down producers running faster than their consumers.
Upvotes: 3