`
bjxagu
  • 浏览: 165326 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

对Swing线程的再思索

阅读更多

不正确的Swing线程是运行缓慢、无响应和不稳定的Swing应用的主要原因之一。这是许多原因造成的,从开发人员对Swing单线程模型的误解,到保证正确的线程执行的困难。即使对Swing线程进行了很多努力,应用线程逻辑也是很难理解和维护的。本文阐述了如何在开发Swing应用中使用事件驱动编程,以大大简化开发、维护,并提供高灵活性。

背景


既然我们是要简化Swing应用的线程,首先让我们来看看Swing线程是怎么工作的,为什么它是必须的。Swing API是围绕单线程模型设计的。这意味着Swing组件必须总是通过同一个线程来修改和操纵。为什么采用单线程模型,这有很多原因,包括开发成本和同步Swing的复杂性--这都会造成一个迟钝的API。为了达到单线程模型,有一个专门的线程用于和Swing组件交互。这个线程就是大家熟知的Swing线程,AWT(有时也发音为“ought”)线程,或者事件分派线程。在本文的下面的部分,我选用Swing线程的叫法。
既然Swing线程是和Swing组件进行交互的唯一的线程,它就被赋予了很多责任。所有的绘制和图形,鼠标事件,组件事件,按钮事件,和所有其它事件都发生在Swing线程。因为Swing线程的工作已经非常沉重了,当太多其它工作在Swing线程中进行处理时就会发生问题。会引起这个问题的最常见的位置是在非Swing处理的地方,像发生在一个事件监听器方法中,比如JButton的ActionListener,的数据库查找。既然ActionListener的actionPerformed()方法自动在Swing线程中执行,那么,数据库查找也将在Swing线程中执行。这将占用了Swing的工作,阻止它处理它的其它任务--像绘制,响应鼠标移动,处理按钮事件,和应用的缩放。用户以为应用死掉了,但实际上并不是这样。在适当的线程中执行代码对确保系统正常地执行非常重要。 

既然我们已经看到了在适当的线程中执行Swing应用的代码是多么重要,现在让我们如何实现这些线程。我们看看将代码放入和移出Swing线程的标准机制。在讲述过程中,我将突出几个和标准机制有关的问题和难点。正如我们看到的,大部分的问题都来自于企图在异步的Swing线程模型上实现同步的代码模型。从那儿,我们将看到如何修改我们的例子到事件驱动--移植整个方式到异步模型。

通用Swing线程解决方案


让我们以一个最常用的Swing线程错误开始。我们将企图使用标准的技术来修正这个问题。在这个过程中,我们将看到实现正确的Swing线程的复杂性和常见困难。并且,注意在修正这个Swing线程问题中,许多中间的例子也是不能工作的。在例子中,我在代码失败的地方以//broken开头标出。好了,现在,让我们进入我们的例子吧。
假设我们在执行图书查找。我们有一个简单的用户界面,包括一个查找文本域,一个查找按钮,和一个输出的文本区域。这个接口如图1所示。不要批评我的UI设计,这个确实很丑陋,我承认。

 

 

对Swing线程的再思索


 
图 1. 基本查询用户界面
用户输入书的标题,作者或者其它条件,然后显示一个结果的列表。下面的代码例子演示了按钮的ActionListener在同一个线程中调用lookup()方法。在这些例子中,我使用了thread.sleep()休眠5秒来作为一个占位的外部查找。线程休眠的结果等同于一个耗时5秒的同步的服务器调用。

  1. private void searchButton_actionPerformed() {
  2.     outputTA.setText("Searching for: " +
  3.                      searchTF.getText());
  4.     //Broken!! Too much work in the Swing thread
  5.     String[] results = lookup(searchTF.getText());
  6.     outputTA.setText("");
  7.     for (int i = 0; i < results.length; i++) {
  8.         String result = results[i];
  9.         outputTA.setText(outputTA.getText() + 
  10.                         ' ' + result);
  11.     }
  12. }


如果你运行这段代码(完整的代码可以在这儿下载),你会立即发现存在一些问题。图2显示了查找运行中的一个屏幕截图。


 
图 2. 在Swing线程中进行查找
注意Go按钮看起来是被按下了。这是因为actionPerformed方法通知了按钮绘制为非按下外观,但是还没有返回。你也会发现要查找的字串“abcde”并没有出现在文本区域中。searchButton_actionPerformed的第1行代码将文本区域设置为要查找的字串。但是,注意Swing重画并不是立即执行的。而是把重画请求放置到Swing事件队列中等待Swing线程处理。但是这儿,我们因查找处理占用了Swing线程,所以,它还不能马上进行重画。

要修正这些问题,让我们把查找操作移入非Swing线程中。我们第一个想到的就是让整个方法在一个新的线程中执行。这样作的问题是Swing组件,本例中的文本区域,只能从Swing线程中进行编辑。下面是修改后的searchButton_actionPerformed方法:

  1. private void searchButton_actionPerformed() {
  2.     outputTA.setText("Searching for: " +
  3.                      searchTF.getText());
  4.     //the String[][] is used to allow access to
  5.     // setting the results from an inner class
  6.     final String[][] results = new String[1][1];
  7.     new Thread(){
  8.         public void run() {
  9.            results[0] = lookup(searchTF.getText());
  10.         }
  11.     }.start();
  12.     outputTA.setText("");
  13.     for (int i = 0; i < results[0].length; i++) {
  14.         String result = results[0][i];
  15.         outputTA.setText(outputTA.getText() +
  16.                         ' ' + result);
  17.     }
  18. }


这种方法有很多问题。注意final String[][] 。这是一个处理匿名内部类和作用域的不得已的替代。基本上,在匿名内部类中使用的,但在外部环绕类作用域中定义的任何变量都需要定义为final。你可以通过创建一个数组来持有变量解决这个问题。这样的话,你可以创建数组为final的,修改数组中的元素,而不是数组的引用自身。既然我们已经解决这个问题,让我们进入真正的问题所在吧。图3显示了这段代码运行时发生的情况:


 
图 3. 在Swing线程外部进行查找
界面显示了一个null,因为显示代码在查找代码完成前被处理了。这是因为一旦新的线程启动了,代码块继续执行,而不是等待线程执行完毕。这是那些奇怪的并发代码块中的一个,下面将把它编写到一个方法中使其能够真正执行。
在SwingUtilities类中有两个方法可以帮助我们解决这些问题:invokerLater()和invokeAndWait()。每一个方法都以一个Runnable作为参数,并在Swing线程中执行它。invokeAndWait()方法阻塞直到Runnnable执行完毕;invokeLater()异步地执行Runnable。invokeAndWait()一般不赞成使用,因为它可能导致严重的线程死锁,对你的应用造成严重的破坏。所以,让我们把它放置一边,使用invokeLater()方法。
要修正最后一个变量变量scooping和执行顺序的问题,我们必须将文本区域的getText()和setText()方法调用移入一个Runnable,只有在查询结果返回后再执行它,并且在Swing线程中执行。我们可以这样作,创建一个匿名Runnable传递给invokeLater(),包括在新线程的Runnable后的文本区域操作。这保证了Swing代码不会在查找结束之前执行。下面是修正后的代码:

  1. private void searchButton_actionPerformed() {
  2.     outputTA.setText("Searching for: " +
  3.                      searchTF.getText());
  4.     final String[][] results = new String[1][1];
  5.     new Thread() {
  6.         public void run() {
  7.             //get results.
  8.             results[0] = lookup(searchTF.getText());
  9.             // send runnable to the Swing thread
  10.             // the runnable is queued after the
  11.             // results are returned
  12.             SwingUtilities.invokeLater(
  13.                 new Runnable() {
  14.                     public void run() {
  15.                         // Now we're in the Swing thread
  16.                         outputTA.setText("");
  17.                         for (int i = 0;
  18.                              i < results[0].length;
  19.                              i++) {
  20.                             String result = results[0][i];
  21.                             outputTA.setText(
  22.                                 outputTA.getText() +
  23.                                 ' ' + result);
  24.                         }
  25.                     }
  26.                 }
  27.             );
  28.         }
  29.     }.start();
  30. }


这可以工作,但是这样做令人非常头痛。我们不得不对通过匿名线程执行的顺序,我们还不得不处理困难的scooping问题。问题并不少见,并且,这只是一个非常简单的例子,我们已经遇到了作用域,变量传递,和执行顺序等一系列问题。相像一个更复杂的问题,包含了几层嵌套,共享的引用和指定的执行顺序。这种方法很快就失控了。

问题


我们在企图强制通过异步模型进行同步执行--企图将一个方形的螺栓放到一个圆形的空中。只有我们尝试这样做,我们就会不断地遭遇这些问题。从我的经验,可以告诉你这些代码很难阅读,很难维护,并且易于出错。
这看起来是一个常见的问题,所以一定有标准的方式来解决,对吗?出现了一些框架用于管理Swing的复杂性,所以让我们来快速预览一下它们可以做什么。
一个可以得到的解决方案是
Foxtrot,一个由Biorn Steedom写的框架,可以在SourceForge上获取。它使用一个叫做Worker的对象来控制非Swing任务在非Swing线程中的执行,阻塞直到非Swing任务执行完毕。它简化了Swing线程,允许你编写同步代码,并在Swing线程和非Swing线程直接切换。下面是来自它的站点的一个例子:

  1. public void actionPerformed(ActionEvent e)
  2. {
  3. button.setText("Sleeping...");
  4.     String text = null;
  5.     try
  6.     {
  7.         text = (String)Worker.post(new Task()
  8.            {
  9.               public Object run() throws Exception
  10.               {
  11.                  Thread.sleep(10000);
  12.                  return "Slept !";
  13.               }
  14.            });
  15.     }
  16.     catch (Exception x) ...
  17.     button.setText(text);
  18.     somethingElse();
  19. }



注意它是如何解决上面的那些问题的。我们能够非常容易地在Swing线程中传入传出变量。并且,代码块看起来也很正确--先编写的先执行。但是仍然有一些问题障碍阻止使用从准同步异步解决方案。Foxtrot中的一个问题是异常管理。使用Foxtrot,每次调用Worker必须捕获Exception。这是将执行代理给Worker来解决同步对异步问题的一个产物。
同样以非常相似的方式,我此前也创建了一个框架,我称它为链接运行引擎(Chained Runnable Engine) ,同样也遭受来自类似同步对异步问题的困扰。使用这个框架,你将创建一个将被引擎执行的Runnable的集合。每一个Runnable都有一个指示器告诉引擎是否应该在Swing线程或者另外的线程中执行。引擎也保证Runnable以正确的顺序执行。所以Runnable #2将不会放入队列直到Runnable #1执行完毕。并且,它支持变量以HashMap的形式从Runnable到Runnable传递。
表面上,它看起来解决了我们的主要问题。但是当你深入进去后,同样的问题又冒出来了。本质上,我们并没有改变上面描述的任何东西--我们只是将复杂性隐藏在引擎的后面。因为指数级增长的Runnable而使代码编写将变得非常枯燥,也很复杂,并且这些Runnable常常相互耦合。Runnable之间的非类型的HashMap变量传递变得难于管理。问题的列表还有很多。
在编写这个框架之后,我意识到这需要一个完全不同的解决方案。这让我重新审视了问题,看别人是怎么解决类似的问题的,并深入的研究了Swing的源代码。

解决方案:事件驱动编程


所有前面的这些解决方案都存在一个共同的致命缺陷--企图在持续地改变线程的同时表示一个任务的功能集。但是改变线程需要异步的模型,而线程异步地处理Runnable。问题的部分原因是我们在企图在一个异步的线程模型之上实现一个同步的模型。这是所有Runnable之间的链和依赖,执行顺序和内部类scooping问题的根源。如果我们可以构建真正的异步,我们就可以解决我们的问题并极大地简化Swing线程。
在这之前,让我们先列举一下我们要解决的问题:

1.    在适当的线程中执行代码 
2.    使用SwingUtilities.invokeLater()异步地执行. 

异步地执行导致了下面的问题:

1.    互相耦合的组件 
2.    变量传递的困难 
3.    执行的顺序 

让我们考虑一下像Java消息服务(JMS)这样的基于消息的系统,因为它们提供了在异步环境中功能组件之间的松散耦合。消息系统触发异步事件,正如在
Enterprise Integration Patterns 中描述的。感兴趣的参与者监听该事件,并对事件做成响应--通常通过执行它们自己的一些代码。结果是一组模块化的,松散耦合的组件,组件可以添加到或者从系统中去除而不影响到其它组件。更重要的,组件之间的依赖被最小化了,而每一个组件都是良好定义的和封装的--每一个都仅对自己的工作负责。它们简单地触发消息,其它一些组件将响应这个消息,并对其它组件触发的消息进行响应。
现在,我们先忽略线程问题,将组件解耦并移植到异步环境中。在我们解决了异步问题后,我们将回过头来看看线程问题。正如我们所将要看到的,那时解决这个问题将非常容易。
让我们还拿前面引入的例子,并把它移植到基于事件的模型。首先,我们把lookup调用抽象到一个叫LookupManager的类中。这将允许我们将所有UI类中的数据库逻辑移出,并最终允许我们完全将这两者脱耦。下面是LookupManager类的代码:

  1. class LookupManager {
  2.    private String[] lookup(String text) {
  3.       String[] results = ...
  4.       // database lookup code
  5.       return results
  6.    }
  7. }



现在我们开始向异步模型转换。为了使这个调用异步化,我们需要抽象调用的返回。换句话,方法不能返回任何值。我们将以分辨什么相关的动作是其它类所希望知道的开始。在我们这个例子中最明显的事件是搜索结束事件。所以让我们创建一个监听器接口来响应这些事件。该接口含有单个方法lookupCompleted()。下面是接口的定义:

  1. interface LookupListener {
  2.     public void lookupCompleted(Iterator results);
  3. }



遵守Java的标准,我们创建另外一个称作LookupEvent的类包含结果字串数组,而不是到处直接传递字串数组。这将允许我们在不改变LookupListener接口的情况下传递其它信息。例如,我们可以在LookupEvent中同时包括查找的字串和结果。下面是LookupEvent类:

  1. public class LookupEvent {
  2.     String searchText;
  3.     String[] results;
  4.     public LookupEvent(String searchText) {
  5.         this.searchText = searchText;
  6.     }
  7.     public LookupEvent(String searchText,
  8.                        String[] results) {
  9.         this.searchText = searchText;
  10.         this.results = results;
  11.     }
  12.     public String getSearchText() {
  13.         return searchText;
  14.     }
  15.     public String[] getResults() {
  16.         return results;
  17.     }
  18. }



注意LookupEvent类是不可变的。这是很重要的,因为我们并不知道在传递过程中谁将处理这些事件。除非我们创建事件的保护拷贝来传递给每一个监听者,我们需要把事件做成不可变的。如果不这样,一个监听者可能会无意或者恶意地修订事件对象,并破坏系统。
现在我们需要在LookupManager上调用lookupComplete()事件。我们首先要在LookupManager上添加一个LookupListener的集合:
List listeners = new ArrayList();

并提供在LookupManager上添加和去除LookupListener的方法:

  1. public void addLookupListener(LookupListener listener){
  2.     listeners.add(listener);
  3. }
  4. public void removeLookupListener(LookupListener listener){
  5.     listeners.remove(listener);
  6. }



当动作发生时,我们需要调用监听者的代码。在我们的例子中,我们将在查找返回时触发一个lookupCompleted()事件。这意味着在监听者集合上迭代,并使用一个LookupEvent事件对象调用它们的lookupCompleted()方法。
我喜欢把这些代码析取到一个独立的方法fire[event-method-name] ,其中构造一个事件对象,在监听器集合上迭代,并调用每一个监听器上的适当的方法。这有助于隔离主要逻辑代码和调用监听器的代码。下面是我们的fireLookupCompleted方法:

  1. private void fireLookupCompleted(String searchText,
  2.                                  String[] results){
  3.     LookupEvent event =
  4.         new LookupEvent(searchText, results);
  5.     Iterator iter =
  6.         new ArrayList(listeners).iterator();
  7.     while (iter.hasNext()) {
  8.         LookupListener listener =
  9.             (LookupListener) iter.next();
  10.         listener.lookupCompleted(event);
  11.     }
  12. }



第2行代码创建了一个新的集合,传入原监听器集合。这在监听器响应事件后决定在LookupManager中去除自己时将发挥作用。如果我们不是安全地拷贝集合,在一些监听器应该 被调用而没有被调用时发生令人厌烦的错误。
下面,我们将在动作完成时调用fireLookupCompleted辅助方法。这是lookup方法的返回查询结果的结束处。所以我们可以改变lookup方法使其触发一个事件而不是返回字串数组本身。下面是新的lookup方法:

  1. public void lookup(String text) {
  2.     //mimic the server call delay...
  3.     try {
  4.         Thread.sleep(5000);
  5.     } catch (Exception e){
  6.         e.printStackTrace();
  7.     }
  8.     //imagine we got this from a server
  9.     String[] results =
  10.         new String[]{"Book one",
  11.                      "Book two",
  12.                      "Book three"};
  13.     fireLookupCompleted(text, results);
  14. }



现在让我们把监听器添加到LookupManager。我们希望当查找返回时更新文本区域。以前,我们只是直接调用setText()方法。因为文本区域是和数据库调用一起都在UI中执行的。既然我们已经将查找逻辑从UI中抽象出来了,我们将把UI类作为一个到LookupManager的监听器,监听lookup事件并相应地更新自己。首先我们将在类定义中实现监听器接口:
public class FixedFrame implements LookupListener

接着我们实现接口方法:

  1. public void lookupCompleted(final LookupEvent e) {
  2. outputTA.setText("");
  3.     String[] results = e.getResults();
  4.     for (int i = 0; i < results.length; i++) {
  5.         String result = results[i];
  6.                 outputTA.setText(outputTA.getText() +
  7.                                 "\n" + result);
  8.     }
  9. }



最后,我们将它注册为LookupManager的一个监听器:

  1. public FixedFrame() {
  2.     lookupManager = new LookupManager();
  3.     //here we register the listener
  4.     lookupManager.addListener(this);
  5.     initComponents();
  6.     layoutComponents();
  7. }



为了简化,我在类的构造器中将它添加为监听器。这在大多数系统上都允许良好。当系统变得更加复杂时,你可能会重构、从构造器中提炼出监听器注册代码,以允许更大的灵活性和扩展性。
到现在为止,你看到了所有组件之间的连接,注意职责的分离。用户界面类负责信息的显示--并且仅负责信息的显示。另一方面,LookupManager类负责所有的lookup连接和逻辑。并且,LookupManager负责在它变化时通知监听器--而不是当变化发生时应该具体做什么。这允许你连接任意多的监听器。
为了演示如何添加新的事件,让我们回头添加一个lookup开始的事件。我们可以添加一个称作lookupStarted()的事件到LookupListener,我们将在查找开始执行前触发它。我们也创建一个fireLookupStarted()事件调用所有LookupListener的lookupStarted()。现在lookup方法如下:

  1. public void lookup(String text) {
  2.     fireLookupStarted(text);
  3.     //mimic the server call delay...
  4.     try {
  5.         Thread.sleep(5000);
  6.     } catch (Exception e){
  7.         e.printStackTrace();
  8.     }
  9.     //imagine we got this from a server
  10.     String[] results =
  11.         new String[]{"Book one",
  12.                      "Book two",
  13.                      "Book three"};
  14.     fireLookupCompleted(text, results);
  15. }


我们也添加新的触发方法fireLookupStarted()。这个方法等同于fireLookupCompleted()方法,除了我们调用监听器上的lookupStarted()方法,并且该事件也不包含结果集。下面是代码:

  1. private void fireLookupStarted(String searchText){
  2.     LookupEvent event =
  3.         new LookupEvent(searchText);
  4.     Iterator iter =
  5.         new ArrayList(listeners).iterator();
  6.     while (iter.hasNext()) {
  7.         LookupListener listener =
  8.             (LookupListener) iter.next();
  9.         listener.lookupStarted(event);
  10.     }
  11. }



最后,我们在UI类上实现lookupStarted()方法,设置文本区域提示当前搜索的字符串。

  1. public void lookupStarted(final LookupEvent e) {
  2.     outputTA.setText("Searching for: " +
  3.                     e.getSearchText());
  4. }



这个例子展示了添加新的事件是多么容易。现在,让我们看看展示事件驱动脱耦的灵活性。我们将通过创建一个日志类,当一个搜索开始和结束时在命令行中输出信息来演示。我们称这个类为Logger。下面是它的代码:

  1. public class Logger implements LookupListener {
  2.     public void lookupStarted(LookupEvent e) {
  3.         System.out.println("Lookup started: " +
  4.                            e.getSearchText());
  5.     }
  6.     public void lookupCompleted(LookupEvent e) {
  7.         System.out.println("Lookup completed: " +
  8.                            e.getSearchText() +
  9.                            " " +
  10.                            e.getResults());
  11.     }
  12. }



现在,我们添加Logger作为在FixedFrame构造方法中的LookupManager的一个监听器。

  1. public FixedFrame() {
  2.     lookupManager = new LookupManager();
  3.     lookupManager.addListener(this);
  4.     lookupManager.addListener(new Logger());
  5.     initComponents();
  6.     layoutComponents();
  7. }



现在你已经看到了添加新的事件、创建新的监听器--向您展示了事件驱动方案的灵活性和扩展性。你会发现随着你更多地开发事件集中的程序,你会更加娴熟地在你的应用中创建通用动作。像其它所有事情一样,这只需要时间和经验。看起来在事件模型上已经做了很多研究,但是你还是需要把它和其它替代方案相比较。考虑开发时间成本;最重要的,这是一次性成本。一旦你创建好了监听器模型和它们的动作,以后向你的应用中添加监听器将是小菜一蝶。

线程


到现在,我们已经解决了上面的异步问题;通过监听器使组件脱耦,通过事件对象传递变量,通过事件产生和监听器的注册的组合决定执行的顺序。让我们回到线程问题,因为正是它把我们带到了这儿。实际上非常容易:因为我们已经有了异步功能的监听器,我们可以简单地让监听器自己决定它们应该在哪个线程中执行。考虑UI类和LookupManager的分离。UI类基于事件,决定需要什么处理。并且,该类也是Swing,而日志类不是。所以让UI类负责决定它应该在什么线程中执行将更加有意义。所以,让我们再次看看UI类。下面是没有线程的lookupCompleted()方法:

  1. public void lookupCompleted(final LookupEvent e) {
  2. outputTA.setText("");
  3.         String[] results = e.getResults();
  4.         for (int i = 0; i < results.length; i++) {
  5.             String result = results[i];
  6.                     outputTA.setText(outputTA.getText() +
  7.                                     "\n" + result);
  8.     }
  9. }



我们知道这将在非Swing线程中调用,因为该事件是直接在LookupManager中触发的,这将不是在Swing线程中执行。因为所有的代码功能上都是异步的(我们不必等待监听器方法允许结束后才调用其它代码),我们可以通过SwingUtilities.invokeLater()将这些代码改道到Swing线程。下面是新的方法,传入一个匿名Runnable到SwingUtilities.invokeLater():

  1. public void lookupCompleted(final LookupEvent e) {
  2.   //notice the threading
  3.   SwingUtilities.invokeLater(
  4.         new Runnable() {
  5.             public void run() {
  6.                 outputTA.setText("");
  7.                 Strin

 

 

分享到:
评论

相关推荐

    Swing线程的深入理解和SwingWorker基础知识介绍

    Swing线程的深入理解和SwingWorker基础知识介绍 Swing线程是Java程序设计中的一种重要概念,用于处理图形用户界面(GUI)中的线程问题。在本文中,我们将深入探讨Swing线程的理解和SwingWorker基础知识,并通过实例...

    再论JavaSwing线程.pdf

    Swing API设计为单线程模型,这意味着所有对Swing组件的修改和操作必须在同一个线程中完成,即Swing线程(AWT事件分派线程)。这种设计旨在减少线程同步的复杂性,但同时也引入了挑战,因为不当的线程使用可能导致...

    Swing线程基础经典

    Swing线程基础是Java GUI编程中的重要概念,特别是对于使用Swing库构建桌面应用程序的开发者而言。Swing设计遵循单线程模型,确保UI组件的线程安全性和响应性。以下是Swing线程基础的详细说明: 1. **Swing应用程序...

    Swing线程的最后讨论 -- 利用异步模型

    【Swing线程的最后讨论 -- 利用异步模型】 Swing框架遵循单线程规则,即所有UI组件在同一时间只能被一个线程访问,通常这个线程就是事件派发线程。这一规则是为了避免多线程环境下的竞态条件和同步问题,确保组件的...

    Swing 线程之SwingUtilities invokeLater docx

    Swing 线程之 SwingUtilities invokeLater Swing 线程模型是 Java 中一个非常重要的概念,它是 Java 的图形用户界面(GUI)程序设计中的一部分。Swing 是基于 MVC 模式(Model-View-Controller)设计的,它提供了一...

    基于swing的多线程聊天室

    3. Event Dispatch Thread(事件分发线程):Swing是单线程的,所有对组件的操作必须在EDT上进行,以确保界面的同步更新。 二、多线程技术 1. 用户界面线程:主GUI运行在EDT上,负责处理用户输入和显示UI更新。 2. ...

    Java开发中的线程安全选择与Swing

    Swing组件默认不是线程安全的,因此必须通过`SwingUtilities.invokeLater()`、`SwingUtilities.invokeAndWait()`或使用`SwingWorker`类来确保所有对Swing组件的操作都在事件分发线程中执行。这些技术的应用可以有效...

    java Swing 多线程下载器

    Java Swing多线程下载器是一种利用Java Swing库构建的图形用户界面(GUI)应用程序,它具备多线程下载功能,并支持断点续传。这样的工具类似于我们熟知的迅雷下载管理器,允许用户同时下载多个文件,提高下载速度,...

    Java Swing 线程 下雪效果

    在Java Swing中创建动态、交互式的应用,线程管理是一个关键的概念。当我们谈论"下雪效果"时,这意味着在Swing程序中实现一个模拟下雪场景的动画。 首先,要实现下雪效果,我们需要理解Swing的事件处理机制。Swing...

    swing 线程 实现奖项抽取

    在这个“swing 线程 实现奖项抽取”的项目中,开发者利用Swing来创建一个交互式的抽奖应用,用户可以通过点击按钮触发抽奖过程。这个过程涉及到了多线程的概念,以确保界面的流畅性和数据处理的并发性。 首先,...

    Swing线程基础可用.pdf

    Swing线程基础是Java GUI编程中的重要概念,特别是对于使用Swing库构建的应用程序而言。Swing设计遵循单一线程原则,确保图形用户界面(GUI)的稳定性和响应性。以下是Swing线程基础的详细说明: 1. **初始化线程 ...

    Swing线程基础.pdf

    Swing线程基础是Java GUI编程中的关键概念,主要涉及Swing应用程序中三种类型的线程:初始化线程、UI事件调度线程(EDT,Event Dispatch Thread)和任务线程(Worker Thread)。这些线程在Swing应用中各有其职责,以...

    Swing线程之SwingUtilities.invoke

    Swing线程机制是Swing库中的关键概念,它确保了所有对Swing组件的操作都在正确的线程——事件分发线程(Event Dispatch Thread,简称EDT)上执行,以避免线程安全问题和界面更新不一致的情况。`SwingUtilities....

    JAVASWING多线程产生随机球

    在“JAVASWING多线程产生随机球”的项目中,开发者利用Swing创建了一个互动的应用程序,用户可以通过鼠标点击在界面上生成一个球体,这个球体会以随机的方向和速度在窗口内移动。下面将详细解释这个项目涉及的知识点...

    Swing线程的深入理解和SwingWorker基础知识介绍[参照].pdf

    Swing 线程的深入理解和 SwingWorker 基础知识介绍 Swing 是 Java 中的一种图形用户界面(GUI)工具包,用于创建桌面应用程序。然而,在使用 Swing 时,开发者经常会遇到线程相关的问题。本文将深入了解 Swing 线程...

    JAVA 开发 Swing与多线程

    在Java开发中,Swing库是用来构建图形用户界面(GUI)的工具包,而多线程则是提升程序并发性能和响应能力的关键技术。Swing虽然是Java语言的一部分,但它设计为单线程模型,主要是为了简化GUI编程并避免复杂的同步...

    swing界面socket多线程聊天室

    在本项目中,“swing界面socket多线程聊天室”是一个基于Java Swing的客户端-服务器通信应用,它利用TCP协议来实现实时的聊天功能。这个系统不仅提供了群聊和私聊的功能,还允许用户发送文件,并且具备用户登录与...

    基于java swing的多线程电梯调度模拟

    在本项目"基于Java Swing的多线程电梯调度模拟"中,我们主要探讨的是如何利用Java的多线程特性来实现一个复杂的系统——电梯调度。这个任务是在操作系统课程中的一个典型作业,它要求开发者模拟真实世界中的电梯运行...

Global site tag (gtag.js) - Google Analytics