OK so I have a GUI Java application that permits the user to select and launch between 5-500 software devices each represented by a Thread. The Threads continuously update the GUI with information.
The user can select threads and pause, resume or kill them.
All the information on threads and thread pools, interrupts, Future.... I just got lost and wanted to set fire to the internet =)
What I need is some clear guidance on the standard way forward for this. I have below the outline of my thread class here.
Is this a good template to start with ? If not please edit.
package ACsimForm;
import java.util.Random;
import javax.swing.SwingUtilities;
public class Squeak implements Runnable {
private String name = "";
private javax.swing.JTextArea ScroolPage;
Squeak (String name, javax.swing.JTextArea MW )
{
this.value = true;
this.name = name;
this.ScroolPage = MW;
}
Random r = new Random();
int num = r.nextInt(10-1) + 1;
@Override
public void run ()
{
updateGUI("Thread "+name+" Loaded and Running");
while(true)
{
updateGUI("New Data");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
updateGUI("Thread "+name+" Exiting!");
//return exits the method killing the thread
return;
}
}
}
//this is the new way to communicate back to the GUI
private void updateGUI(final String foo) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
ScroolPage.append(foo+"\r\n");
}
});
}
}
What is the best way to keep tack of up 500 threads in a way that permits you to kill or pause/resume them from the GUI?
Many Thanks.
I think your Runnable
is almost there. Abandon the idea of a magic Boolean
flag - this is plagued with issues (read up on volatile
). Use the interrupt itself as a signal. This is why it's there!
Here is a simple example of a system with an arbitrary number of Thread
s:
private static final class Printer implements Runnable {
private final String printMe;
public Printer(String printMe) {
this.printMe = printMe;
}
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + "says: " + printMe);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException ex) {
return;
}
}
}
}
public static void main(String[] args) throws Exception {
final ExecutorService executorService = Executors.newCachedThreadPool();
final Map<Integer, Future<?>> futures = new HashMap<>();
for (int i = 0; i < 10; ++i) {
futures.put(i, executorService.submit(new Printer("Printer" + i)));
}
final Scanner scanner = new Scanner(System.in);
while (true) {
final String input = scanner.nextLine();
if ("EXIT".equalsIgnoreCase(input)) {
break;
}
final Integer threadToStop;
try {
threadToStop = Integer.parseInt(input);
} catch (NumberFormatException ex) {
System.out.println("Not a number");
continue;
}
final Future<?> f = futures.remove(threadToStop);
if (f == null) {
System.out.println("Not a valid thread");
continue;
}
f.cancel(true);
}
executorService.shutdownNow();
}
We use an ExecutorService
to manage the Thread
s - you should never use Thread
s directly, this is again plagued with land mines.
The ExecutorService
is asked to run an arbitrary number of tasks - you can see how to add tasks in the for
loop.
Then ExecutorService
returns a Future
for each task added - these are handles to the tasks. They allow us to check if they are still going/done or have encountered an error.
We Map
the Future
s to task names and than allow the user to selectively kill tasks from input.
You cannot "pause" and "resume" a Thread
(well you can, but you are better off not worrying about that). You simply cancel a task when you want it paused and then reissue it to restart it. The good thing is that the ExecutorService
will recycle the Thread
that the task was running on so you won't loose performance.
If you are issuing commands to the Map
of Future
s from multiple threads you would need a ConcurrentHashMap
.
A common pitfall is assuming that your application will exit cleanly with the ExecutorService
running. This is not the case. The ExecutorService
spawns non-daemon threads and so the application cannot exit until they are all done. You have two options; the first is a bit hacky - just give the ExecutorService
your own ThreadFactory
and make the Thread
s daemon, the second is to shutdown the ExecutorService
when you are done with it as I have in the example.
This is not a complete answer but a few tips to get things done.
You should use an ExecutorService
for your threads, and .submit()
them. In order to register them and have them available, say by name, create a Map<String, Future<?>>
.
Some pseudo code, could be improved:
@GuardedBy("this") // see JSR 305
final Map<String, Future<?>> allSqueaks
= new HashMap<String, Future<?>>();
final ExecutorService service
= Executors.newCachedThreadPool();
// Registering:
public synchronized void registerSqueak(final Squeak squeak)
{
allSqueaks.put(squeak.getName(), service.submit(squeak));
}
// Cancelling:
public synchronized void cancelSqueakByName(final String name)
{
// Note: deal with non existing name if it can happen
final Future<?> victim = allSqueaks.remove(name);
victim.cancel(true);
}