单线程模型
n 是什么?总是通过同一个线程来修改和操纵
n 为什么?开发成本和同步的复杂性
n 事件分发线程的职能:
u 绘制和图形
u 鼠标事件,组件事件,按钮事件
u 所有其他事件
n 问题存在:非 swing 处理的地方出现一个复杂操作,占用了 swing 的工作
u 阻塞状况:
private void searchButton_actionPerformed()
{
outputTA.setText("Searching for: " + searchTF.getText());
//Broken!! Too much work in the Swing
thread String[] results = lookup(searchTF.getText());
outputTA.setText("");
for (int i = 0; i < results.length; i++)
{
String result = results[i];
outputTA.setText(outputTA.getText() + ?? ?? + result);
}
}
u 解决方法:
l 外部线程:
n 例子:
private void searchButton_actionPerformed()
{
outputTA.setText("Searching for: " + searchTF.getText());
//the String[][] is used to allow access to
// setting the results from an inner class
final String[][] results = new String[1][1];
new Thread()
{
public void run()
{
results[0] = lookup(searchTF.getText());
}
}.start();
outputTA.setText("");
for (int i = 0; i < results[0].length; i++)
{
String result = results[0][i];
outputTA.setText(outputTA.getText() + ?? ?? + result);
}
}
n 问题:在匿名内部类中使用的,但在外部环绕类作用域中定义的任何变量都需要定义为 final
n 线程同步问题
l InvokeLater , InvokeAndWait
n 例子
private void searchButton_actionPerformed()
{
outputTA.setText("Searching for: " + searchTF.getText());
final String[][] results = new String[1][1];
new Thread()
{
public void run()
{ //get results.
results[0] = lookup(searchTF.getText())
// send runnable to the Swing thread
// the runnable is queued after the
// results are returned
SwingUtilities.invokeLater(
new Runnable()
{
public void run()
{
// Now we??re in the Swing thread
outputTA.setText("");
for (int i = 0; i < results[0].length; i++)
{
String result = results[0][i];
outputTA.setText( outputTA.getText() + ?? ?? + result);
}
}
}
);
}
}.start();}
n 问题:我们不得不对通过匿名线程执行的顺序,我们还不得不处理困难的 scooping 问题。问题并不少见,并且,这只是一个非常简单的例子,我们已经遇到了作用 域,变量传递,和执行顺序等一系列问题。相像一个更复杂的问题,包含了几层嵌套,共享的引用和指定的执行顺序。这种方法很快就失控了
l Foxtrot
n 例子
public void actionPerformed(ActionEvent e)
{
button.setText("Sleeping...");
String text = null;
try
{
text = (String)Worker.post(new Task() {
public Object run() throws Exception {
Thread.sleep(10000); return "Slept !";
}
}
);
}
n 问题:。 Foxtrot 中的一个问题是异常管理。使用 Foxtrot ,每次调用 Worker 必须捕获 Exception 。这是将执行代理给 Worker 来解决同步对异步问题的一个产物。
u 总结:
l 有前面的这些解决方案都存在一个共同的致命缺陷--企图在持续地改变线程的同时表示一个任务的功能集。但是改变线程需要异步的模型,而线程异步地处理 Runnable 。问题的部分原因是我们在企图在一个异步的线程模型之上实现一个同步的模型。这是所有 Runnable 之间的链和依赖,执行顺序和内部类 scooping 问题的根源。如果我们可以构建真正的异步,我们就可以解决我们的问题并极大地简化 Swing 线程。
l 我们所要解决的问题
n 在适当的线程中执行代码
n 使 invokeLater 异步地执行
l 异步执行导致的问题
n 互相耦合的组件
n 变量传递困难
n 执行的顺序
l JMS: 异步环境中功能组件之间的松散耦合
n 原理:消息系统触发异步事件,感兴趣的参与者监听该事件,并对事件作出响应,通常是执行自己的一些代码。结果是一组模块化的,松散耦合的组件,组件可以添加到或者从系统中去除而不影响其他组件,组件之间的依赖被最小化了,而每一个组件都是良好定义和封装的 — 每一个都仅对自己的工作负责。他们简单的触发消息,其他一些组件将响应这些消息,并对其他组件触发的消息进行响应。
u 将组件解耦并移植到异步环境中。
l 将查找移植到基于事件的模型
class LookupManager {
private String[] lookup(String text) {
String[] results = ... // database lookup code return results
}
}
l 向异步模型转换 — 抽象调用的返回,即方法不能返回任何值。我们将以分辨什么相关的动作是其他类所希望知道的开始,创建一个监听器接口来响应这些事件:
interface LookupListener { public void lookupCompleted(Iterator results);}
l 创建事件类型,这将允许我们在不改变 LookupListener 接口的情况下传递其他信息:
public class LookupEvent {
String searchText;
String[] results;
public LookupEvent(String searchText) {
this.searchText = searchText;
}
public LookupEvent(String searchText, String[] results) {
this.searchText = searchText;
this.results = results;
}
public String getSearchText() {
return searchText;
}
public String[] getResults() {
return results;
}
}
l 现在我们需要在 LookupManager 上调用 lookupComplete() 事件。我们首先要在 LookupManager 上添加一个 LookupListener 的集合:
List listeners = new ArrayList();
并提供在 LookupManager 上添加和去除 LookupListener 的方法:
public void addLookupListener(LookupListener listener){
listeners.add(listener);
}
public void removeLookupListener(LookupListener listener){
listeners.remove(listener);
}
l 当动作发生时,我们需要调用监听者的代码。在我们的例子中,我们将在查找返回时触发一个 lookupCompleted() 事件。这意味着在监听者集合上迭代,并使用一个 LookupEvent 事件对象调用它们的 lookupCompleted() 方法。
把这些代码析取到一个独立的方法 fire[event-method-name] ,其中构造一个事件对象,在监听器集合上迭代,并调用每一个监听器上的适当的方法。这有助于隔离主要逻辑代码和调用监听器的代码。下面是我们的 fireLookupCompleted 方法:
private void fireLookupCompleted(String searchText, String[] results){
LookupEvent event = new LookupEvent(searchText, results);
Iterator iter = new ArrayList(listeners).iterator();
while (iter.hasNext()) {
LookupListener listener = (LookupListener) iter.next();
listener.lookupCompleted(event);
}
}
l 我们将在动作完成时调用 fireLookupCompleted 辅助方法。这是 lookup 方法的返回查询结果的结束处。所以我们可以改变 lookup 方法使其触发一个事件而不是返回字串数组本身。下面是新的 lookup 方法:
public void lookup(String text) {
//mimic the server call delay...
try {
Thread.sleep(5000);
} catch (Exception e){
e.printStackTrace();
}
//imagine we got this from a server
String[] results = new String[]{"Book one", "Book two", "Book three"};
fireLookupCompleted(text, results);
}
l 现在让我们把监听器添加到 LookupManager 。我们希望当查找返回时更新文本区域。以前,我们只是直接调用 setText() 方法。因为 文本区域是和数据库调用一起都在 UI 中执行的。既然我们已经将查找逻辑从 UI 中抽象出来了,我们将把 UI 类作为一个到 LookupManager 的监听 器,监听 lookup 事件并相应地更新自己。首先我们将在类定义中实现监听器接口:
public class FixedFrame implements LookupListener
接着我们实现接口方法:
public void lookupCompleted(final LookupEvent e) {
outputTA.setText("");
String[] results = e.getResults();
for (int i = 0; i < results.length; i++) {
String result = results[i];
outputTA.setText(outputTA.getText() + " " + result);
}
}
最后,我们将它注册为 LookupManager 的一个监听器:
public FixedFrame() {
lookupManager = new LookupManager();
//here we register the listener
lookupManager.addListener(this);
initComponents();
layoutComponents();}
为了简化,我在类的构造器中将它添加为监听器。这在大多数系统上都允许良好。当系统变得更加复杂时,你可能会重构、从构造器中提炼出监听器注册代码,以允许更大的灵活性和扩展性
u 我们已经解决了上面的异步问题;通过监听器使组件脱耦,通过事件对象传递变量,通过事件产生和监听器的注册的组合决定执行的顺序。你看到了所有组件之间的连接,注意职责的分离。用户界面类负责信息的显示--并且仅负责信息的显示。另一方面, LookupManager 类负责所有的 lookup 连接和逻辑。并且, LookupManager 负责在它变化时通知监听器--而不是当变化发生时应该具体做什么。这允许你连接任意多的监听器。现在你已经看到了添加新的事件、创建新的监听器--向您展示了事件驱动方案的灵活性和扩展性。你会发现随着你更多地开发事件集中的程序,你会更加娴熟地在 你的应用中创建通用动作。像其它所有事情一样,这只需要时间和经验。看起来在事件模型上已经做了很多研究,但是你还是需要把它和其它替代方案相比较。考虑 开发时间成本;最重要的,这是一次性成本。一旦你创建好了监听器模型和它们的动作,以后向你的应用中添加监听器将是小菜一蝶。
u 如果你需要在同一个方法中执行大量的 UI 代码和非 UI 代码,很容易将某些代码放错位置。事件驱动的方式将迫使你将代码放在它应该在的地方-- 它仅应该在的地方。如果你在同一个方法中执行数据库调用和更新 UI 组件,那么你就在一个类中写入了太多的逻辑。分析你系统中的事件,创建底层的事件模型将 迫使你将代码放到正确的地方。将费时的数据库调用代码放在非 UI 类中,也不要在非 UI 组件中更新 UI 组件。采用事件驱动的体系, UI 负责 UI 更新,数据库 管理类负责数据库调用。在这一点上,每一个封装的类都只用关心自己的线程,不用担心系统其它部分如何动作。当然,设计、构建一个事件驱动的客户端也很有 用,但是需要花费的时间代价远超过带来的结果系统的灵活性和可维护性的提高

被折叠的 条评论
为什么被折叠?



