Usually when multithreading in Swing is discussed, then authors are regarding trivial issue of using ”SwingWorker” and such constructs for running worker threads from EDT (Event Dispatch Thread”.
However, Swing design includes one rather different multithreading thing. Namely it’s possible to start multiple AppContext instances, and execute multiple EDT threads in single JVM.
So lets do simple test:
First we need main of the system, which handles bookkeeping for the all running ”applications”.
[code lang=”java”]
package org.kari.test.multithread;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.SwingUtilities;
import sun.awt.AppContext;
import sun.awt.SunToolkit;
import sun.java2d.opengl.OGLRenderQueue;
/**
* Start of whole system.
*
* @author kari
*/
public class Launcher
implements
Runnable
{
private static Launcher mInstance;
private final ThreadGroup mThreadGroup;
private Set
private List
public static Launcher getInstance() {
return mInstance;
}
public static void setInstance(Launcher pInstance) {
mInstance = pInstance;
}
public Launcher() {
mInstance = this;
mThreadGroup = Thread.currentThread().getThreadGroup();
}
public void run() {
// ”Root” AppContext
SunToolkit.createNewAppContext();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
OGLRenderQueue.getInstance();
}
});
// Start one starting point app
startApp(”App 1”);
// Wait and kill stopped apps
boolean alive = true;
List
while (alive) {
synchronized (mKilled) {
try {
mKilled.wait();
} catch (InterruptedException e) {
// Ignore
}
killed.addAll(mKilled);
mKilled.clear();
}
for (MultiApp app : killed) {
AppContext ctx = app.getAppContext();
System.out.println(”Kill: ” + ctx);
ctx.dispose();
}
int count;
synchronized (mAlive) {
mAlive.removeAll(killed);
count = mAlive.size();
}
alive = count > 0;
System.out.println(”Remaining apps=” + count);
killed.clear();
}
System.out.println(”Last app killed”);
System.exit(0);
}
public void killApp(MultiApp pApp) {
synchronized (mKilled) {
mKilled.add(pApp);
mKilled.notify();
}
}
public static void main(String[] args) {
new Thread(new Launcher()).start();
}
public MultiApp startApp(String pName) {
MultiApp app = new MultiApp(this, pName, mThreadGroup);
app.start();
synchronized (mAlive) {
mAlive.add(app);
}
return app;
}
}
[/code]
Secondly, we need simple test application what we want to multithread, with some actiosn to proof that logic really works.
[code lang=”java”]
package org.kari.test.multithread;
import javax.swing.SwingUtilities;
import sun.awt.AppContext;
import sun.awt.SunToolkit;
public class MultiApp
implements
Runnable
{
private static final ThreadLocal
new InheritableThreadLocal
private final Launcher mLauncher;
private final String mName;
private final ThreadGroup mThreadGroup;
private AppContext mAppContext;
/**
* Get local application instance
*/
public static MultiApp getInstance() {
return mInstance.get();
}
public MultiApp(Launcher pLauncher, String pName, ThreadGroup pParentGroup) {
mLauncher = pLauncher;
mName = pName;
mThreadGroup = new ThreadGroup(pParentGroup, mName);
}
public AppContext getAppContext() {
return mAppContext;
}
public void run() {
// MUST create app context first
mAppContext = SunToolkit.createNewAppContext();
mInstance.set(this);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
create();
}
});
}
void create() {
MultiAppFrame frame = new MultiAppFrame();
frame.setTitle(mName + ” – ” + Thread.currentThread().getThreadGroup().getName());
frame.setVisible(true);
}
/**
* Launch application
*/
public void start() {
new Thread(mThreadGroup, this)
.start();
}
}
[/code]
And finally simple test frame testing the logic
[code lang=”java”]
package org.kari.test.multithread;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.PrintStream;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import sun.awt.AppContext;
/**
* Frame to test multi-EDT
*
* @author kari
*/
public class MultiAppFrame extends JFrame {
static int mIndex;
public MultiAppFrame() {
setSize(new Dimension(400, 300));
setContentPane(createContentPanel());
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent pE) {
Launcher.getInstance().killApp(MultiApp.getInstance());
}
});
}
private JComponent createContentPanel() {
JPanel panel = new JPanel(new BorderLayout());
JButton button1 = new JButton(”Click me”);
button1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent pE) {
PrintStream out = System.out;
Thread thread = Thread.currentThread();
out.println(”KICKED BUTTON:”);
out.println(”Thread=” + thread.getName());
out.println(”ThreadGroup=” + thread.getThreadGroup().getName());
out.println(”context=” + AppContext.getAppContext());
}
});
JButton button2 = new JButton(”New App”);
button2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent pE) {
Launcher.getInstance().startApp(”Another – ” + (mIndex++));
}
});
panel.add(button1, BorderLayout.NORTH);
panel.add(button2, BorderLayout.SOUTH);
return panel;
}
}
[/code]
After starting Launcher we start initially one ”MultiApp” to have some starting point. From here we can launch as many new multiapps, with separate EDTs as we want. To verify that each new MultiAppFrame is really running in separate EDT -thread, click ”Click Me” -button, which dumps thread and app context info into stdout.
You may ask what is the point of this whole exercise? Answer to that is that this allows incredible extra flexibility. Just imagine running in same JVM multiple instances of application which are sharing resources, while still completely retaining their own context.
So why it works? AppContext establishes separate application context for each EDT -thread, thus allowing multiple ”sandboxes” applications to run in same JVM. Each application is running in separate ThreadGroup, into which AppContext is bound.
Now the magic here is when either ”SwingUtilities.invokeLater()” or ”new Thread(..).start()” is done inside the threadgroup it’s automatically bound into threadgroup. Thus invokeLater() knows to invoke itself in the EDT of the corresponding threadgroup. And threads are parented into threadgroup; thus they are bound into AppContext.
UPDATE: 2009-05-06
”OGLRenderQueue.getInstance();” was required in Linux, to ensure proper initialization order.
NOTE: I later on found out that having multiple EDT threads is *NOT* working, since there is bugs in Swing paint logic, which assume that there is only single EDT thread.