1. GUI 应用程序
- 几乎所有的GUI工具集都是单线程化的子系统,意味着所有GUI的活动都被限制在一个单独的线程中。
- 我们应该避免在事件线程中执行耗时操作,以免UI失去响应。
- Swing 的数据结构不是线程安全的,所以在使用它时必须小心的把他们限制在事件线程中。
2. 为什么 GUI 是单线程化的
- 早期的 GUI 应用程序是单线程化的,GUI事件在 “主事件循环” 进行处理。
- 现代的GUI框架使用了一个略微不同的模型:模型创建了一个专门的线程,事件派发线程(event dispatch thread, EDT) 来处理 GUI 事件。
2.1 顺序时间处理
- GUI 应用程序总要去处理精细的事件,比如点击鼠标、按下键盘或者定时器到时等等。
- 因为只有唯一的一个线程在处理 GUI 任务,所以它会依次处理 GUI 任务。
- 依次执行任务有些问题是避免不了的。如果一个任务执行要花费的时间长,其他任务也要等到它结束。
2.2 Swing 中的线程限制
- 所有的 Swing 组件(比如 JButton 和 JTable) 和数据模型(比如 TableModel 和 TreeModel)都被限制于事件线程中,所以任何访问它们的代码必须在事件线程中运行。
- GUI 对象不用同步,仅仅依靠线程限制来保持一致性。
- 优点:运行于事件线程的任务,在访问表现对象(presentation objects)时不必担心同步的问题。
- 缺点:完全无法从事件线程之外的地方访问表现对象。
Swing 的单线程规则:Swing 的组件和模型只能在事件分派线程中被创建、修改和请求。
2. 短期的 GUI 任务
- 在 GUI 应用程序中,事件起源于事件线程,冒泡似的传递到达应用程序提供的监听器,监听器进而可能会执行一些影响表现模型的运算。
- 为了简单起见,短期的任务可以把全部动作留在事件线程中完成;而对于耗时的任务,则应该将一些工作负荷分压到另一个线程中。
使用 Executor 实现的 SwingUtilities
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import java.lang.reflect.InvocationTargetException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory;
public class SwingUtilities { private static final ExecutorService exec = Executors.newSingleThreadExecutor(new SwingThreadFactory()); private static volatile Thread swingThread;
private static class SwingThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { swingThread = new Thread(r); return swingThread; } }
public static boolean isEventDispatchThread() { return Thread.currentThread() == swingThread; }
public static void invokeLater(Runnable task) { exec.execute(task); }
public static void invokeAndWait(Runnable task) throws InterruptedException, InvocationTargetException { Future<?> f = exec.submit(task); try { f.get(); } catch (ExecutionException e) { throw new InvocationTargetException(e); } } }
|
构建于 SwingUtilities 之伤的 Executor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import java.util.List; import java.util.concurrent.AbstractExecutorService; import java.util.concurrent.TimeUnit;
public class GuiExecutor extends AbstractExecutorService { private static final GuiExecutor instance = new GuiExecutor();
private GuiExecutor() { }
public static GuiExecutor instance() { return instance; }
public void execute(Runnable r) { if (SwingUtilities.isEventDispatchThread()) { r.run(); } else { SwingUtilities.invokeLater(r); } }
public void shutdown() { throw new UnsupportedOperationException(); }
public List<Runnable> shutdownNow() { throw new UnsupportedOperationException(); }
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { throw new UnsupportedOperationException(); }
public boolean isShutdown() { return false; }
public boolean isTerminated() { return false; } }
|
简单的时间监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
| import javax.swing.JButton; import javax.swing.JLabel; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future;
public class ListenerExamples { private static final ExecutorService EXEC = Executors.newCachedThreadPool(); private final JButton colorButton = new JButton("Change color"); private final Random random = new Random();
private void backgroundRandom() { colorButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { colorButton.setBackground(new Color(random.nextInt())); } }); }
private final JButton computeButton = new JButton("Big computation");
private void longRunningTask() { computeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { EXEC.execute(new Runnable() { public void run() { } }); } }); }
private final JButton button = new JButton("Do"); private final JLabel label = new JLabel("idle");
private void longRunningTaskWithFeedback() { button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { button.setEnabled(false); label.setText("busy"); EXEC.execute(new Runnable() { public void run() { try { } finally { GuiExecutor.instance().execute(new Runnable() { public void run() { button.setEnabled(true); label.setText("idle"); } }); } } }); } }); }
private final JButton startButton = new JButton("Start"); private final JButton cancelButton = new JButton("Cancel"); private Future<?> runningTask = null;
private void taskWithCancellation() { startButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (runningTask != null) { runningTask = EXEC.submit(new Runnable() { public void run() { while (moreWork()) { if (Thread.currentThread().isInterrupted()) { cleanUpPartialWork(); break; } doSomeWork(); } } private boolean moreWork() { return false; } private void cleanUpPartialWork() { } private void doSomeWork() { } }); } ; } });
cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { if (runningTask != null) { runningTask.cancel(true); } } }); }
private void runInBackground(final Runnable task) { startButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { class CancelListener implements ActionListener { BackgroundTask<?> task; public void actionPerformed(ActionEvent event) { if (task != null) { task.cancel(true); } } } final CancelListener listener = new CancelListener(); listener.task = new BackgroundTask<Void>() { public Void compute() { while (moreWork() && !isCancelled()) { doSomeWork(); } return null; }
private boolean moreWork() { return false; }
private void doSomeWork() { }
public void onCompletion(boolean cancelled, String s, Throwable exception) { cancelButton.removeActionListener(listener); label.setText("done"); } }; cancelButton.addActionListener(listener); EXEC.execute(task); } }); } }
|
这里用到了 【3. 共享数据模型】 中的 Demo
3. 共享数据模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException;
public abstract class BackgroundTask <V> implements Runnable, Future<V> { private final FutureTask<V> computation = new Computation();
private class Computation extends FutureTask<V> { public Computation() { super(new Callable<V>() { public V call() throws Exception { return BackgroundTask.this.compute(); } }); }
protected final void done() { GuiExecutor.instance().execute(new Runnable() { public void run() { V value = null; Throwable thrown = null; boolean cancelled = false; try { value = BackgroundTask.this.get(); } catch (ExecutionException e) { thrown = e.getCause(); } catch (CancellationException e) { cancelled = true; } catch (InterruptedException consumed) { } finally { onCompletion(value, thrown, cancelled); } }; }); } }
protected abstract V compute() throws Exception;
protected void onCompletion(V result, Throwable exception, boolean cancelled) { }
protected void onProgress(int current, int max) { } public boolean cancel(boolean mayInterruptIfRunning) { return computation.cancel(mayInterruptIfRunning); }
public V get() throws InterruptedException, ExecutionException { return computation.get(); }
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return computation.get(timeout, unit); }
public boolean isCancelled() { return computation.isCancelled(); }
public boolean isDone() { return computation.isDone(); }
public void run() { computation.run(); } }
|
4. 其他形式的单线程子系统
线程限制不仅仅限制在 GUI 系统。无论何时,线程限制都可以用作实现单线程化子系统的便利工具。
对避免同步与死锁束手无策的时候,使用线程限制成为了我们不得不使用的办法。
比如,一些原生库(native librarie)要求所有对库的访问,甚至 System.LoadLibrary 加载库时,必须在同一个线程中运行。