Java并发编程实战:第9章 GUI应用程序

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 的组件和模型只能在事件分派线程中被创建、修改和请求。

  • 有非常少量的的 Swing 方法可以安全地被任意线程调用:

    1
    2
    3
    4
    5
    SwingUtilities.isEventDispatchThread();

    SwingUtilities.invokeLater();

    SwingUtities.invokeAndWait();

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 {
// Singleton 包含一个私有的构造函數和一个公共的工厂
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() { /* do something */ }
});
}
;
}
});

// 用于接收动作事件的监听器
cancelButton.addActionListener(new ActionListener() {
// 发生动作时调用
public void actionPerformed(ActionEvent event) {
if (runningTask != null) {
runningTask.cancel(true);
}
}
});
}

/** 在 BackgroundTask 中启动一个耗时的、可取消的任务 */
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();

/** 计算 task */
private class Computation extends FutureTask<V> {
public Computation() {
// 创建一个 task,该 FutureTask.get() 返回 BackgroundTask.compute() 方法的计算结果
super(new Callable<V>() {
public V call() throws Exception {
return BackgroundTask.this.compute();
}
});
}

/** 当此任务转换到状态 isDone 时调用 */
protected final void done() {
// GuiExecutor 实例获取计算结果
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 加载库时,必须在同一个线程中运行。

Java并发编程实战:第9章 GUI应用程序

https://osys.github.io/posts/97b4.html

作者

Osys

发布于

2022年08月29日

更新于

2022年08月29日

许可协议

评论