澳门新葡萄京官网注册 1

澳门新葡萄京官网注册Swing多线程编程(转)

译者注:本文其实是一个各方见解评论的总结,已将其中于此处发布不妥的内容去掉,全文内容可查看原文。

6. Swing应用中的多线程问题

Swing应用运行于多个线程之上。特别是三类线程:
1.起始线程,或主线程,在main()方法中运行,启动GUI的构建和应用的退出;
2.EDT线程;
3.一些处理计算密集型任务及IO的后台线程。

所有的事件处理,绘制和界面刷新代码在单一线程(EDT)中进行,这是为了确保事件处理器在下个处理器启动之前完成执行,且绘制不被事件打断。如果因计算密集型任务造成EDT饥饿,用户界面将会挂起,程序对用户交互无响应。”理想情况下,任何需要花费30-100
ms任务不应该在EDT上运行。否则用户将感觉到输入和UI应答之间的停顿。

再者,所有访问GUI组件的代码应该在EDT上运行,因为这些组件中很多不能确保线程安全,而从相同线程中访问他们能避免多线程问题。

总之,
1.耗时和阻塞IO任务不应在EDT上运行,以避免造成EDT饥饿,进而影响用户交互的事件触发及界面刷新;
2.为了线程安全,Swing组件应仅在EDT上访问。

 

在官方的文档里: 告诉我们如何创建一个gui。

6.1 javax.Swing.SwingUtilities.invokeLater() 和invokeAndWait()

方法invokeLater(Runnable)和
invokeAndWait(Runnable)协调EDT中的Runnable任务。

为避免主线程(运行main()方法)和EDT之间线程问题,推荐使用javax.swing.SwingUtilities.invokeLater(Runnable)在EDT上创建GUI组件,如:

/** The entry main() method */
public static void main(String args[]) {
    // Run the GUI codes on the event-dispatching thread for thread-safety
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            JFrame frame = new JFrame("My Swing Application");
            frame.setContentPane(new MyMainPanel());
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.pack();
            frame.setLocationRelativeTo(null); // center on screen
            frame.setVisible(true);            // show it
        }
    });
}

静态方法SwingUtilities.invokelater()以Runnable对象(作为匿名内部类执行)为参数,并在EDT上制定任务(具体在run()方法中)。可以从任何线程中调用invokeLater()来要求EDT执行特定代码,这些代码可以写在Runnable参数的run()方法中。invokeLater()会立即返回,而不等待EDT执行这些代码。

如果有需要可以使用invokeAndWait(),
这方法会等待EDT执行指定代码才返回。对于applet,推荐(在init())通过invokeAndWait()运行GUI创建代码。这是为了避免init()在GUI创建完成之前退出造成问题。如:

public class SwingTemplateJApplet extends JApplet {
    /** init() to setup the GUI components */
    @Override
    public void init() {
        // Run GUI codes in the Event-Dispatching thread for thread safety
        try {
            // Use invokeAndWait() to ensure that init() exits after GUI construction
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    // Set the content-pane of JApplet to an instance of main JPanel
                    setContentPane(new SwingTemplateJPanel());
                }
            });
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

也可用java.awt.EventQueue.invokeLater()代替javax.swing.SwingUtilities.invokeLater(),javax.swing.SwingUtilities.invokeLater()是
java.awt.EventQueue.invokeLater()的进一步封装。

通过之前例子的追踪可以发现如果没有使用SwingUtilities.invokeLater(),EDT在setVisible()方法之后启动。另一方面,invokerLater()启动EDT。

关键字:

事件分发线程:

6.2 javax.swing.Timer (JDK 1.2)

如果需要在一定时间后或一段频繁时间间隔内更新组件,使用时间类如
javax.swing.Timer(JDK 1.2)或java.util.Timer(JDK 1. 3)。

对于javax.swing.Timer,阅读”animation using javax.swing.Timer”。

Swing,多线程,GUI,SwingWorker

Swing中事件处理和绘画代码都在一个单独的线程中执行,这个线程就叫做事件分发线程。这就确保了事件处理器都能串行的执行,并且绘画过程不会被事件打断。为了避免死锁的可能,你必须极度小心从事件分发线程中创建、修改、查询Swing组件以及模型。

6.3 javax.swing.SwingWorker<T,V> (JDK 1.6)

如之前提到过的,在Swing应用中:

  1. 计算密集型任务不应在EDT上运行,以避免EDT在处理事件和重绘中出现饥饿;
  2. 为了线程安全,Swing组件应仅在EDT上访问。

类javax.swing.SwingWorkder<T,V>
帮助管理唯一的EDT和几个后台工作者线程之间的交互。它可以用来协调后台线程中的计算密集型任务并返回EDT中产生的最终结果或中间结果。

定义 SwingWorker
类如:public abstract class SwingWorker<T,V> implements RunnableFuture

SwingWorker<T,V>是带两种类型参数的抽象类:T
指方法doInBackground()和get()最终结果类型,而V指
方法publish()和process()中间结果类型。

RunnableFuture 接口是两个接口的结合:Runnable 和Future。接口Runnable
声明了抽象方法run();而 Future 声明 get(), cancel(), isDone(),
和isCancelled()。

摘要:

注意:我们过去常说只要你没有修改已经实现过的组件,你就能在主进程中创建GUI。 style=”color: #ff0000;”>[补充:下面页注中的红色字体。]
已实现过的意思是组件已经在屏幕上描绘出来或是准备描绘了。方法setVisible(true)和pack可以实现一个窗口,反过来又可以实现该窗口内
包含的组件。尽管这对大多数应用程序都管用,但这种做法在某些情况下会引起一些问题。在Swing
Tutorial的所有示例中,我们只在ComponentEventDemo中遇到一个问题。在那个样例中,有时候当你载入样例后,它并不会启动。因为
如果在文本域还没实现的时候就去更新会出现死锁,但是其他的时候没有意外的话它也是会正常启动。

为了避免线程问题,建议你使用invokeLater在事件分发线程中为所有新应用程序创建GUI。如果你的现有程序能工作正常,那你可能就会让它保持下去;然而,如果改造起来方便的话,还是希望你能改造一下。

制定后台任务

protected abstract T doInBackground() throws Exception
    // Do this task in a background thread
protected void done()
    // Executes on the Event-Dispatching thread after the doInBackground() method finishes.
public final T get() throws InterruptedException, ExecutionException
    // Waits for doInBackground() to complete and gets the result.
    // Calling get() on the Event-Dispatching thread blocks all events, including repaints,
    //  until the SwingWorker completes.
public final void execute()
    // Schedules this SwingWorker for execution on one of the worker thread.
public final boolean cancel(boolean mayInterruptIfRunning)
    // Attempts to cancel execution of this task.
public final boolean isDone()
    // Returns true if this task has completed (normally or exception)
public final boolean isCancelled()
    // Returns true if this task was cancelled before it completed normally

为在工作者线程中制定一项任务,扩展 SwingWorker<T,V>子类并重写:

  1. doInBackground()指定执行任务,在一个工作者线程中制定并返回类型为T的结果;
  2. done()方法在
    doInBackground()完成后在EDT中运行,用get()方法取得doInBackground()结果
    (类型 T)。

import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.ExecutionException;
import javax.swing.*;

/** Test SwingWorker on the counter application with a compute-intensive task */
@SuppressWarnings("serial")
public class SwingWorkerCounter extends JPanel {
    // For counter
    private JTextField tfCount;
    private int count = 0;
    // For SwingWorker
    JButton btnStartWorker;   // to start the worker
    private JLabel lblWorker; // for displaying the result

    /** Constructor to setup the GUI components */
    public SwingWorkerCounter () {
        setLayout(new FlowLayout());

        add(new JLabel("Counter"));
        tfCount = new JTextField("0", 10);
        tfCount.setEditable(false);
        add(tfCount);

        JButton btnCount = new JButton("Count");
        add(btnCount);
        btnCount.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ++count;
                tfCount.setText(count + "");
            }
        });

        /** Create a SwingWorker instance to run a compute-intensive task 
            Final result is String, no intermediate result (Void) */
        final SwingWorker<String, Void> worker = new SwingWorker<String, Void>() {
            /** Schedule a compute-intensive task in a background thread */
            @Override
            protected String doInBackground() throws Exception {
                // Sum from 1 to a large n
                long sum = 0;
                for (int number = 1; number < 1000000000; ++number) {
                    sum += number;
                }
                return sum + "";
            }

            /** Run in event-dispatching thread after doInBackground() completes */
            @Override
            protected void done() {
                try {
                    // Use get() to get the result of doInBackground()
                    String result = get();
                    // Display the result in the label (run in EDT)
                    lblWorker.setText("Result is " + result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        };

        btnStartWorker = new JButton("Start Worker");
        add(btnStartWorker);
        btnStartWorker.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                worker.execute();                 // start the worker thread
                lblWorker.setText("  Running...");
                btnStartWorker.setEnabled(false); // Each instance of SwingWorker run once
            }
        });
        lblWorker = new JLabel("  Not started...");
        add(lblWorker);

    }

    /** The entry main() method */
    public static void main(String[] args) {
        // Run the GUI construction in the Event-Dispatching thread for thread-safety
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("SwingWorker Test");
                frame.setContentPane(new SwingWorkerCounter());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(300, 150);
                frame.setVisible(true);
            }
        });
    }
}

SwingWorker的实例设计只能运行一次且不能重新启动,需要创建新实例重新执行任务。

本文论述了怎样开发多线程的Swing程序,从而提高Swing程序的响应速度和性能。

你可能已经注意
到,大部分教程中的例子都使用一个标准的主函数,即SwingUtilities的函数invokeLater来保证GUI在事件分发线程中创建。这里有
一个从FocusConceptsDemo例子中提取的主函数的样例。我们还将处理创建GUI事件的主函数都要调用的一个私有静态方法,即
createAndShowGUI 的源代码包含进来了。

发布并处理中间结果

不一定在done()中处理最终结果, 如有需要可以publish()并
process()中间结果。

@SafeVarargs
protected final void publish(V... chunks)
    // Sends data chunks to the process(java.util.List<V>) method.
    // This method shall be called inside the doInBackground() to deliver intermediate results
    //  for processing on the Event-Dispatching thread inside the process() method.
protected void process(List<V> chunks)
    // Receives data chunks from publish() asynchronously on the Event-Dispatching thread.

在doInBackground()中, 用publish(V…)
发布一个或更多类型V中间结果。重写process(List<V>)方法来处理List<V>中公布的结果,process()方法运行在EDT中。

import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.ExecutionException;
import javax.swing.*;

/** Test SwingWorker on the counter application with a compute-intensive task */
@SuppressWarnings("serial")
public class SwingWorkerCounterIntermediateResult extends JPanel {
    // For counter
    private JTextField tfCount;
    private int count = 0;
    // For SwingWorker
    JButton btnStartWorker;   // to start the worker
    private JLabel lblWorker; // for displaying the result

    /** Constructor to setup the GUI components */
    public SwingWorkerCounterIntermediateResult () {
        setLayout(new FlowLayout());

        add(new JLabel("Counter"));
        tfCount = new JTextField("0", 10);
        tfCount.setEditable(false);
        add(tfCount);

        JButton btnCount = new JButton("Count");
        add(btnCount);
        btnCount.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ++count;
                tfCount.setText(count + "");
            }
        });

        /** Create a SwingWorker instance to run a compute-intensive task */
        final SwingWorker<String, String> worker = new SwingWorker<String, String>() {

            /** Schedule a compute-intensive task in a background thread */
            @Override
            protected String doInBackground() throws Exception {
                long sum = 0;
                for (int number = 0; number < 10000000; ++number) {
                    sum += number;
                    publish(sum + ""); // Send "every" intermediate result to process()
                                  // You might not publish every intermediate result
                }
                return sum + "";
            }

            /** Run in event-dispatching thread after doInBackground() completes */
            @Override
            protected void done() {
                try {
                    // Use get() to get the result of doInBackground()
                    String finalResult = get();
                    // Display the result in the label (run in EDT)
                    lblWorker.setText("Final Result is " + finalResult);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }

            /** Run in event-dispatching thread to process intermediate results
                send from publish(). */
            @Override
            protected void process(java.util.List<String> chunks) {
                // Get the latest result from the list
                String latestResult = chunks.get(chunks.size() - 1);
                lblWorker.setText("Result is " + latestResult);
            }
        };

        btnStartWorker = new JButton("Start Worker");
        add(btnStartWorker);
        btnStartWorker.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                worker.execute();  // start the worker thread
                lblWorker.setText("  Running...");
                btnStartWorker.setEnabled(false);  // SwingWorker can only run once
            }
        });
        lblWorker = new JLabel("  Not started...");
        add(lblWorker);

    }

    /** The entry main() method */
    public static void main(String[] args) {
        // Run the GUI construction in the Event-Dispatching thread for thread-safety
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("SwingWorker Test");
                frame.setContentPane(new SwingWorkerCounterIntermediateResult());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(300, 150);
                frame.setVisible(true);
            }
        });
    }
}

   
近期,我将推出一系列研究Swing程序的文章,这也算是为了向Swing这个优秀的GUI库的设计者致敬吧!

/**
 * Create the GUI and show it.  For thread safety,
 * this method should be invoked from the
 * event-dispatching thread.
 */
private static void createAndShowGUI() {
    //Make sure we have nice window decorations.
    JFrame.setDefaultLookAndFeelDecorated(true);

    //Create and set up the window.
    frame = new JFrame("FocusConceptsDemo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    //Create and set up the content pane.
    JComponent newContentPane = new FocusConceptsDemo();
    newContentPane.setOpaque(true); //content panes must be opaque
    frame.setContentPane(newContentPane);

    //Display the window.
    frame.pack();
    frame.setVisible(true);
}

public static void main(String[] args) {
    //Schedule a job for the event-dispatching thread:
    //creating and showing this application's GUI.
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            createAndShowGUI();
        }
    });
}

属性变化事件

doInBackground()发送PropertyChangeEvent给其所有的PropertyChangeListeners
关于边界属性变化。有两个边界属性:”state”
和”progress”。”state”在封装枚举类型SwingWorker.StateValue中定义,StateValue取值为PENDING
(SwingWorker 实例创建), START
(doInBackground启动)和DONE(doInBackground 完成) 。”progress”
是一个整数型,取值范围从0到100。可以在doInBackground()中
通过setProgress()方法改变progress值来发送PropertyChangeEvent给其所有的PropertyChangeListeners。

Swing这种优秀的GUI库一直不能占领桌面市场,实在令人费解,今天,我就用我的努力,为java在桌面市场的成功尽我微薄之力吧!

使用invokeLater方法

你可以从任何线程中调用invokeLater来请求事件分发线程以运行某段代
码。 style=”color: #ff0000;”>你必须将这段代码放入一个Runnable对象的run方法中,并将该指定Runnable对象作为参数传递给invokeLater。
invokeLater函数会立即返回,不会等到事件分发线程执行完这段代码。这里有一个使用invokeLater的例子:

Runnable updateAComponent = new Runnable() {
    public void run() { component.doSomething(); }
};
SwingUtilities.invokeLater(updateAComponent);

invokeLater必须放在run()方法体内。

例子

在本例
doInBackground()方法内,调用setProgess()来改变progress边界特征值(0-100之间),然后发送PropertyChangeEvent。用SwingWorker定义并注册
PropertyChangeListener , 在进度条显示progress值。事件处理器在EDT中运行。

import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import javax.swing.*;

/** Test SwingWorker on the counter application with a compute-intensive task */
@SuppressWarnings("serial")
public class SwingWorkerCounterProgress extends JPanel {
    // For counter
    private JTextField tfCount;
    private int count = 0;
    // For SwingWorker
    JButton btnStartWorker;   // to start the worker
    private JLabel lblWorker; // for displaying the result
    JProgressBar pbWorker;    // progress bar for the worker task

    /** Constructor to setup the GUI components */
    public SwingWorkerCounterProgress () {
        setLayout(new FlowLayout());

        add(new JLabel("Counter"));
        tfCount = new JTextField("0", 10);
        tfCount.setEditable(false);
        add(tfCount);

        JButton btnCount = new JButton("Count");
        add(btnCount);
        btnCount.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ++count;
                tfCount.setText(count + "");
            }
        });

        /** Create a SwingWorker instance to run a compute-intensive task */
        final SwingWorker<String, String> worker = new SwingWorker<String, String>() {

            /** Schedule a compute-intensive task in a background thread */
            @Override
            protected String doInBackground() throws Exception {
                long sum = 0;
                int maxNumber = 10000000;
                for (int number = 0; number < maxNumber; ++number) {
                    sum += number;
                    publish(sum + ""); // send intermediate result to process()
                    // Fire PropertyChangeEvent for the bound-property "progress"
                    setProgress(100 * (number + 1) / maxNumber);
                }
                return sum + "";
            }

            /** Run in event-dispatching thread after doInBackground() completes */
            @Override
            protected void done() {
                try {
                    // Use get() to get the result of doInBackground()
                    String finalResult = get();
                    // Display the result in the label (run in EDT)
                    lblWorker.setText("Final Result is " + finalResult);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }

            /** Run in event-dispatching thread to process intermediate results
                send from publish(). */
            @Override
            protected void process(java.util.List<String> chunks) {
                // Get the latest result from the list
                String latestResult = chunks.get(chunks.size() - 1);
                lblWorker.setText("Result is " + latestResult);
            }
        };

        /** Event handler for the PropertyChangeEvent of property "progress" */
        worker.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getPropertyName().equals("progress")) {  // check the property name
                    pbWorker.setValue((Integer)evt.getNewValue());  // update progress bar
                }
            }
        });

        btnStartWorker = new JButton("Start Worker");
        add(btnStartWorker);
        btnStartWorker.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                worker.execute();  // start the worker thread
                lblWorker.setText("  Running...");
                btnStartWorker.setEnabled(false);  // SwingWorker can only run once
            }
        });
        lblWorker = new JLabel("  Not started...");
        add(lblWorker);
        pbWorker = new JProgressBar();
        add(pbWorker);
    }

    /** The entry main() method */
    public static void main(String[] args) {
        // Run the GUI construction in the Event-Dispatching thread for thread-safety
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("SwingWorker Test");
                frame.setContentPane(new SwingWorkerCounterProgress());
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(300, 150);
                frame.setVisible(true);
            }
        });
    }
}

 

使用invokeAndWait方法

invokeAndWait方法和 invokeLater方法一样,除了invokeAndWait是直到事件分发线程已经执行了指定代码才返回。任何可能的时候,你都应当使用
invokeLater而不是invokeAndWait——因为invokeAndWait很容易引起死锁。如果你使用invokeAndWait,要
保证调用invokeAndWait的线程不持有任何其他线程在调用时刻也会需要的锁。

这里有一个使用invokeAndWait的例子:

void showHelloThereDialog() throws Exception {
    Runnable showModalDialog = new Runnable() {
        public void run() {
            JOptionPane.showMessageDialog(myMainFrame,
                                          "Hello There");
        }
    };
    SwingUtilities.invokeAndWait(showModalDialog);
}

类似地,一个需要获取GUI状态的线程也需要类似的处理,比如包含两个文本域的组件,可能会有如下代码:

void printTextField() throws Exception {
    final String[] myStrings = new String[2];

    Runnable getTextFieldText = new Runnable() {
        public void run() {
            myStrings[0] = textField0.getText();
            myStrings[1] = textField1.getText();
        }
    };
    SwingUtilities.invokeAndWait(getTextFieldText);

    System.out.println(myStrings[0] + " " + myStrings[1]);
}

6.4 总结

线程对构建响应性GUI很有必要,有一些应该使用新线程的典型情形:

  • 在主线程中创建一新线程来处理耗时初始化任务(如:磁盘I/O)以便GUI加载更快;
  • 在EDT中创建一新线程来处理耗时任务(在事件处理器中)以便GUI保持响应;
  • 用计时器处理重复任务,在一定时间间隔或延时一段时间执行;
  • 如果操作需要等待其他线程或程序的信息,创建一个新线程。

此外,计算密集型线程必须协作并将控制交与其他线程。

 

使用线程提高性能

使用恰当的话,线程会是
一个很有用的工具。然而,当在一个Swing程序中使用线程时,你必须谨慎处理。虽然有危险,但线程还是很有用的。你可以使用线程提高你程序的响应性能。
而且,线程有时还能简化程序的代码或结构。这里有一些使用线程的特殊场景:

  • 将一个比较消耗时间的初始化任务移出主线程,可以使GUI出现得更快。耗时任务包括做额外的计算任务以及网络阻塞或硬盘I/O(比如,载入图片)。

  • 将耗时任务移出事件分发线程,从而使GUI同时继续响应用户操作。
  • 为了能重复执行一个操作,通常还需要在操作之间设置一个预置时间段(定时器)。
  • 等待其他程序的消息。

如果你需要创建一个线程,可以通过使用工具类如SwingWorker或是Timer类中的一个实现该线程,这样一来能够避免一些常见的陷阱。SwingWorker对象创建一个线程以执行一个耗时操作。

当该操作结束后,SwingWoker会在事件分发线程中给你提供执行额外代码的选项。Timer类则适合重复执行或需要一段延时执行的操作。如果你需要实现自己的线程,可以在Concurrency中找到相关信息。

可以使用一些技巧来使多线程的Swing程序有更好的性能:

  • 如果当你需要更新一个组件但事件监听器中的代码并没有执行的时候,可以考虑使用这两个方法,SwingUtilities的invokeLater(优先选项)或invokeAndWait方法。
  • 如果你不能确定事件监听器中的代码
    是否已经执行,那你就应当分析程序代码和线程中每个函数的调用文件。如果还是不行,可以使用SwingUtilities的
    isEventDispatchThread方法。当该方法在事件分发线程中执行时返回true。你能在任何线程中安全地调用invokeLater,但
    invokeAndWait会在调用线程不是事件分发线程时抛出异常。
  • 如果你需要在一段延迟之后更新组件(不论你的代码目前是否正在一个事件监听器中运行),那就使用定时器timer吧。
  • 如果你需要在没经过一段规律的时间间隔后更新组件,使用定时器timer。

使用timer定时器的信息和例子,请见如何使用Swing定时器。

7. Thread Pool, Executor, Callable/Future (JDK 1.5)

线程池支持类在JDK1.5 java.lang.concurrent包中引入:

澳门新葡萄京官网注册 1

Swing的单线程开发机制

使用SwingWorker类

注意:SwingWorker类的实现已经有过两次更新了,最近的一次是2000年的2月。第一次更新(在1999年1月)允许程序安全地中断工作线程。最近的一次更新(称作“SwingWorker
3”)修正了一个会引起空指针异常NullPointerException的隐藏线程bug。

类SwingWorker在SwingWorker.java中
实现,它没有在Swing包中发布。使用SwingWorker类之前,需要先创建一个SwingWorker的子类。子类必须要实现构造函数使它能包含
执行耗时操作的代码。当初始化SwingWorker子类的时候,SwingWorker类创建了一个线程但没启动它(截至SwingWorker
3)。然后才是调用SwingWorker对象的启动start方法来启动线程,就是调用构造函数。

这里有一个例子,使用SwingWorker类将耗时任务从动作事件监听器中移动到后台线程中,从而使GUI能保持响应。

//OLD CODE:
public void actionPerformed(ActionEvent e) {
    ...
    //...code that might take a while to execute is here...
    ...
}

//BETTER CODE:
public void actionPerformed(ActionEvent e) {
    ...
    final SwingWorker worker = new SwingWorker() {
        public Object construct() {
            //...code that might take a while to execute is here...
            return someValue;
        }
    };
    worker.start();  //required for SwingWorker 3
    ...
}

构造函数的返回值可以是任意的对象。想获取该返回值,可以调用SwingWorker对象的get函数。对于get函数要小心使用。因为它会阻塞,会引起死锁。如果有必要的话,可以调用SwingWorker的中断函数interrupt中断线程(引起函数返回)。

如果你想在耗时
任务完成的时候更新GUI,可以调用get函数(这个正如需要注意的那样,有些危险)或是重写你SwingWorker子类的finished函数。
Finished函数会在构造函数返回后执行。因为finished函数在事件分发线程中执行,你能安全地使用它更新Swing组件。当然,你最好不要把
耗时操作放进finished函数中。

下面实现finished函数的例子是从IconDemoApplet.java文件中拿来的。若想充分地讨论这个applet,包括如何使用后台线程载入图片以提高响应性能,请看如何使用Icons。

public void actionPerformed(ActionEvent e) {
    ...
    if (icon == null) {     //haven't viewed this photo before
        loadImage(imagedir + pic.filename, current);
    } else {
        updatePhotograph(current, pic);
    }
}
...
//Load an image in a separate thread.
private void loadImage(final String imagePath, final int index) {
    final SwingWorker worker = new SwingWorker() {
        ImageIcon icon = null;

        public Object construct() {
            icon = new ImageIcon(getURL(imagePath));
            return icon; //return value not used by this program
        }

        //Runs on the event-dispatching thread.
        public void finished() {
            Photo pic = (Photo)pictures.elementAt(index);
            pic.setIcon(icon);
            if (index == current)
                updatePhotograph(index, pic);
        }
    };
    worker.start(); 
}

更多使用SwingWorker的例子,请看How to Monitor
Progress。还有,TumbleItem.java,在How
to Make
Applets中讨论过的,既使用了SwingWorker,还使用了计时器Timer。

讨论:

官方解释:

public static void invokeLater(Runnable doRun)

函数public
static void invokeLater(Runnable doRun)引起函数doRun.run()在AWT的事件分发线程中异步执行。这个函数应该在程序线程需要更新GUI的时候调用。下面的例子中,invokeLater调用事件分发线程的Runnable对象doHelloWorld到队列中,然后打印一条信息。

 Runnable doHelloWorld = new Runnable() {
     public void run() {
         System.out.println("Hello World on " + Thread.currentThread());
     }
 };

如果是从事件分发线程调用的invokeLater——比如,从一个JButton的监听器中——doRun.run()会一直延迟到事件队列中的所有待处理事件都处理完才
执行。要注意,当doRun.run()抛出一个未捕获的异常时,事件分发线程会释放掉(不是当前线程)。

更多关于这个函数的文件和例子请参见Java 教程中的How to Use
Threads。

至于1.3部分,这个函数是由java.awt.EventQueue.invokeLater()包装而来。

不像Swing的其他函数,这个函数可以从任意线程中调用。

还可参见:

  • invokeAndWait(java.lang.Runnable)

7.1 线程池(Thread Pool)

线程池是一个能执行任务线程的管理集合。当使用线程池执行大量任务时,效率会因线程循环执行任务而提高,这会降低每个任务调用开销。

可以实现接口ExecutorService来使用线程池,如ThreadPoolExecutor
或ScheduledThreadPoolExecutor,而在Executors
类中提供了更多便捷工厂方法,列举如下:

  • Executors.newSingleThreadExecutor(): 创建单一后台线程;
  • Executors.newFixedThreadPool(int numThreads):
    创建固定大小的线程池;
  • Executors.newCachedThreadPool():
    创建带自动线程回收机制的无界线程池。

使用线程池的步骤:

  1. 写一个实现Runnable接口的工作者线程类,run()方法制定运行线程的行为;
  2. 用Executors
    类提供的工厂方法创建线程池(ExecutorService),该线程池可以只有一个线程,固定数量线程或无限数量线程;
  3. 创建工作者线程类实例。用线程池的方法execute(Runnable
    r)来添加Runnable
    任务到线程池,该任务将被协调并在有空闲线程时得到执行。

多线程开发,显然要比单线程开发有趣、高效、美妙得多。特别是在Java这种天生支持多线程的语言中,更是如此。可是,Java最重要的组成部分Swing确是单线程的!

7.2 接口java.util.concurrent.Executor

Executor对象能执行提交的Runnable 任务,该接口声明了下面抽象方法:
public void execute(Runnable r)

在未来某个时候执行给定任务,任务因Executor执行情况可能在新线程,线程池或当前线程中执行。

并非只有Swing是单线程的,大多数GUI库都是单线程的。因为,在GUI的事件处理中,事件和处理事件的底层资源是如此的复杂,以至于使用多线程开发,很难避免死锁和资源竞争问题的发生。而且,如果锁定太多系统资源,对GUI的系统性能将会造成消极影响。

7.3 接口java.util.concurrent.ExecutorService

接口ExecutorService 声明很多抽象方法,其中比较重要的有:

public void shutdown();
    // Initiates an orderly shutdown of the thread pool.
    // The previously executed/submitted tasks are allowed to complete,
    // but no new tasks will be scheduled.
public <T> Future<T> submit(Callable<T> task);
    // Submit or schedule the callable task for execution, which returns a Future object.

因此,Swing被开发成了一个基于事件队列的单线程编程模型。GUI上的事件,一个个依次在“事件派发线程”上执行,不会发生事件对资源的争夺。

7.4 类java.util.concurrent.Executors

类Executors 提供创建Executor对象的工厂方法。如:

static ExecutorService newSingleThreadExecutor()
static ExecutorService newFixedThreadPool(int nThreads)
static ExecutorService newCachedThreadPool()
static ScheduledExecutorService newSingleThreadScheduledExecutor()
static ScheduledExecutorService newScheduledThreadPool(int size)

public class WorkerThread implements Runnable {
    private int workerNumber;

    WorkerThread(int workerNumber) {
        this.workerNumber = workerNumber;
    }

    public void run() {
        // The thread simply prints 1 to 5
        for (int i = 1; i <= 5; ++i) {
            System.out.printf("Worker %d: %dn", workerNumber, i);
            try {
                // sleep for 0 to 0.5 second
                Thread.sleep((int)(Math.random() * 500));
            } catch (InterruptedException e) {}
        }
    }
}

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolTest {
    public static void main(String[] args) {
        int numWorkers = Integer.parseInt(args[0]);
        int threadPoolSize = Integer.parseInt(args[1]);

        ExecutorService pool = 
                Executors.newFixedThreadPool(threadPoolSize);
        WorkerThread[] workers = new WorkerThread[numWorkers];
        for (int i = 0; i < numWorkers; ++i) {
            workers[i] = new WorkerThread(i+1);
            pool.execute(workers[i]);
        }
        pool.shutdown();
    }
}

//2 threads, 5 workers
> java ThreadPoolTest 5 2
Worker 1: 1
Worker 2: 1
Worker 1: 2
Worker 1: 3
Worker 2: 2
Worker 1: 4
Worker 2: 3
Worker 1: 5
Worker 3: 1
Worker 3: 2
Worker 2: 4
Worker 3: 3
Worker 2: 5
Worker 3: 4
Worker 3: 5
Worker 4: 1
Worker 4: 2
Worker 5: 1
Worker 4: 3
Worker 5: 2
Worker 5: 3
Worker 4: 4
Worker 5: 4
Worker 5: 5
Worker 4: 5

首先安排工作者1和2用线程池中的两个线程执行,接着是工作者3,4和5。占用线程任务完成后,线程返回线程池,然后安排另外一个任务并开始执行。

可以调用pool.shutdown()来关闭线程池中所有线程。

Java.awt.EventQueue类,就执行这个功能。

7.5 接口java.util.concurrent.Callable<V> 和Future<V>

Callable 类似于Runnable,不同的是
Callable提供方法返回结果或Exception到相应的线程中。Callable声明了抽象方法call()(不同于Runnable中的run())。
public V call() // Call() returns a result of type <V>, or throws an exception if unable to do so.

在线程池中,使用submit(Callable r)而非execute(Runnable
r),这会返回一个Future<V>
对象(在ExecutorService接口中声明)。当需要结果时,可以对Future对象调用get()方法加以获取。结果一旦完成便会返回,否则当前线程一直阻塞直到获取结果。

接口Future<V> 声明了下面的抽象方法:

V get()           // wait if necessary, retrieve result
V get(long timeout, TimeUnit unit)
boolean cancel(boolean mayInterruptIfRunning)
boolean isCancelled()
boolean isDone()  // return true if this task completed

import java.util.concurrent.Callable;

public class CallableWorkerThread implements Callable<String> {
    private int workerNumber;

    CallableWorkerThread(int workerNumber) {
        this.workerNumber = workerNumber;
    }

    public String call() {    // use call() instead of run()
        for (int i = 1; i <= 5; ++i) {    // just print 1 to 5
            System.out.printf("Worker %d: %dn", workerNumber, i);
            try {
                Thread.sleep((int)(Math.random() * 1000));
            } catch (InterruptedException e) {}
        }
        return "worker " + workerNumber;
    }
}

import java.util.concurrent.*;

public class CallableThreadPoolTest {
    public static void main(String[] args) {
        int numWorkers = Integer.parseInt(args[0]);

        ExecutorService pool = Executors.newCachedThreadPool();
        CallableWorkerThread workers[] = new CallableWorkerThread[numWorkers];
        Future[] futures = new Future[numWorkers];

        for (int i = 0; i < numWorkers; ++i) {
            workers[i] = new CallableWorkerThread(i + 1);
            futures[i] = pool.submit(workers[i]);
        }
        for (int i = 0; i < numWorkers; ++i) {
            try {
                System.out.println(futures[i].get() + " ended");
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            } catch (ExecutionException ex) {
                ex.printStackTrace();
            }
        }
    }
}

三个工作者线程的输出:
> java CallableThreadPoolTest 3
Worker 1: 1
Worker 3: 1
Worker 2: 1
Worker 3: 2
Worker 1: 2
Worker 2: 2
Worker 2: 3
Worker 2: 4
Worker 2: 5
Worker 1: 3
Worker 3: 3
Worker 3: 4
Worker 3: 5
Worker 1: 4
Worker 1: 5
worker 1 ended
worker 2 ended
worker 3 ended

EventQueue 是一个与平台无关的类,它将来自于基础同位体类和受信任的应用程序类的事件列入队列。

7.6 JDK 1.5中其他新线程特性

  • Lock
  • ReadWriteLock
  • Semaphores
  • Atomics
  • Blocking Queue

Java参考及资源链接

更多参考文献及资源

  1. Swing Tutorial’s “Concurrency in
    Swing”.
  2. John O’Conner, “Improve Application Performance With SwingWorker in
    Java SE 6” @
    http://java.sun.com/developer/technicalArticles/javase/swingworker/.

翻译自:
https://www3.ntu.edu.sg/home/ehchua/programming/java/j5e_multithreading.html

Java编程之多线程&并发编程(上)
Java编程之多线程&并发编程(中)

它封装了异步事件指派机制,该机制从队列中提取事件,然后通过对此
EventQueue 调用
dispatchEvent(AWTEvent))
方法来指派这些事件(事件作为参数被指派)。该机制的特殊行为是与实现有关的。指派实际排入到该队列中的事件(注意,正在发送到
EventQueue 中的事件可以被合并)的惟一要求是:

按顺序。

也就是说,不允许同时从该队列中指派多个事件。

指派顺序与它们排队的顺序相同。

也就是说,如果 AWTEvent A 比 AWTEvent B
先排入到 EventQueue 中,那么事件 B 不能在事件 A 之前被指派。

一些浏览器将不同基本代码中的 applet
分成独立的上下文,并在这些上下文之间建立一道道墙。在这样的场景中,每个上下文将会有一个
EventQueue。其他浏览器将所有的 applet 放入到同一个上下文中,这意味着所有
applet 只有一个全局
EventQueue。该行为是与实现有关的。有关更多信息,请参照浏览器的文档。

所有Swing/AWT事件的处理方法,都被放到唯一的“事件派发线程”中执行。

一般,我们使用EventQueue类的2个方法,将事件处理方法放到“事件派发线程”中执行。

invokeLater

public static void invokeLater(Runnable runnable)

导致 runnablerun 方法在 EventQueue
的指派线程上被调用。在所有挂起事件被处理后才发生。

参数:

runnableRunnable,其 run 方法应该在 EventQueue 上同步执行

从以下版本开始:

1.2

另请参见:

invokeAndWait(java.lang.Runnable))


invokeAndWait

public static void invokeAndWait(Runnable runnable)

                          throws InterruptedException,

                                 InvocationTargetException

导致 runnablerun 方法在 EventQueue
的指派线程上被调用。在所有挂起事件被处理后才发生。在这发生之前调用被阻塞。如果从事件指派线程进行调用,则该方法将抛出
Error。

参数:

runnableRunnable,其 run 方法应该在 EventQueue 上同步执行

抛出:

InterruptedException – 如果另一个线程已经中断了该线程

InvocationTargetException – 如果运行 runnable 时抛出一个 throwable

从以下版本开始:

1.2

另请参见:

invokeLater(java.lang.Runnable))


设计Swing的UI组件的执行,一般都需要运行在“事件派发线程”上。

 

 

Swing单线程开发引起的问题

Java是一种多线程编程语言。多线程给程序带来了并发的好处。Swing单线程开发的一个问题是,如果在“事件派发线程”上执行的运算太多,那么GUI界面就会停住,系统响应和运算就会非常缓慢。

既然,“事件派发线程”是为了处理GUI事件而设的,那么,我们只应该把GUI事件处理相关的代码,放在“事件派发线程”中执行。其他与界面无关的代码,应该放在Java其他的线程中执行。

这样,我们在Swing的事件处理中,仍然使用Swing的单线程编程模型,而其他业务操作均使用多线程编程模型,这就可以大大提高Swing程序的响应和运行速度,充分运用Java多线程编程的优势。

 

Swing程序的线程

Swing应用程序的线程,分为两种,一种是“事件派发线程”,实际上只有唯一的一条线程;另一种是一般的Java线程,可以有无数条线程。

与系统事件处理相关的代码,需要运行在“事件派发线程”中。一般就是Swing的UI组件。Swing组件,由于包含了SwingUI组件,所以常常也需要运行在“事件派发线程”中。

与业务相关的代码,特别是大计算量,或者涉及到IO,网络,等待资源等耗时的操作,需要放置到一个独立的Java线程中,实现并行运算,提高性能。

 

Swing程序线程应用示例

下面,我以一个一般的Swing程序为例,具体说明Swing多线程编程应该怎样进行。

1,JFrame子类:

public class DiagramDesignerJFrame extends javax.swing.JFrame {…}

这是一个JFrame的子类,是一个顶级窗口。顶级窗口,就是一个从操作系统中拿到的窗口。Java可以在这个窗口中使用从操作系统得到的画笔,绘制出Swing需要的GUI。

 

2,main方法:

 

    /**

     *@paramargs

     *            thecommandlinearguments

     */

    publicstaticvoid main(String args[]) {

        /**

         *在一般线程中,执行SPring容器的初始化

         */

        try {

            SpringUtil.getCtx();

        } catch (BeansException e) {

            /*

            *

            */

            e.printStackTrace();

        } catch (DocumentException e) {

            /*

            *

            */

            e.printStackTrace();

        }

       
java.awt.EventQueue.invokeLater(new Runnable() {

            publicvoid run() {

                new DiagramDesignerJFrame().setVisible(true);

            }

        });

    }

 

首先,我们在一般Java线程中,执行Spring容器的初始化。这是非常大量的计算,而且与操作系统事件根本没有关系。

然后,我们在“事件派发线程”中异步执行对JFrame的子类的实例化和可视化。

new DiagramDesignerJFrame()这个实例化操作,是在“事件派发线程”中执行的;

setVisible(true)也是在“事件派发线程”中执行的。

 

 

控制器中多线程的运用

Swing是MVC模式设计的典范。其控制器,就是事件监听器,一般实现为内部类。Swing各个组件都可以注册非常多的事件监听器,也就是“控制器”。

Swing的UI组件,是其界面View。各个Swing组件的Model是其模型。

一般,用户在Swing的UI组件上执行操作,激发事件,然后由控制器进行处理,修改Swing组件的Model。Model又激发Java事件(而非操作系统事件),使UI根据新的Model值重绘。

我们在“控制器”中会调用业务代码,这些代码可能会很耗时。调用结束以后,常常又会调用Swing组件的方法,更新Swing应用程序的外观。

因此,我们应该在Swing的控制器中,把调用业务方法的代码,交给一个新建的一般Java线程去执行。执行完毕之后,再在“事件派发线程”中执行Swing组件更新代码。

   
下面是执行“另存为”功能的控制器。它首先保存Swing组件中的数据,然后打开文件选择其对话框,让用户选择要保存到哪个文件中。

/**

     *@returnsaveAsActionListener

     */

    public ActionListener getSaveAsActionListener()
{

        if (this.saveAsActionListener == null) {

            this.saveAsActionListener = new ActionListener()
{

                /**

                 *响应点击另存为按钮的事件的方法

                 */

                publicvoid actionPerformed(ActionEvent e)
{

                    final SwingWorker worker = new SwingWorker() {

 

                        @Override

                        public Object construct() {

                            /*

                           
*

                           
*/

 

                        try {

                           
getJEditorPane1().fireControllerChangeListener();

                             return DiagramDesignerJFrame.serviceFinished;

                        } catch (DocumentException e1) {

                            /*

                             *

                            
*/

                           
e1.printStackTrace();

                           
JOptionPane.showMessageDialog(

                                   
DiagramDesignerJFrame.this, “您的输入不符合xml格式要求!”

                                            +
e1.getMessage());

                        } catch (Exception e1) {

                            /*

                             *

                            
*/

                           
e1.printStackTrace();

                        }

                            returnnull;

                        }

                        /**

                         *执行完构造器后,在GUI上异步执行它。

                         */

                         publicvoid finished() {

                            
saveAction();

                            }

                       

                    };

                   
worker.start();

                   

               

 

                   

 

                }

 

            };

 

        }

 

        returnsaveAsActionListener;

    }

 

SwingWorker这个Swing多线程开发的助手类

上面的例子中用到了SwingWorker这个Swing多线程开发的助手类。在JDK6中,这个类已经作为Swing的一部分。这里,我使用的是JDK5,因此,我是使用了之前SUN创建的SwingWorker类。

其中,public Object construct() {…}

这个方法中的代码,运行在一个新建的一般Java线程中。我们把耗时的业务方法放在这个方法中执行。

publicvoid finished() {…}

这个方法中的代码,运行在“事件派发线程”中,是立即返回的。saveAction()显示了一个文件选择其对话框,应该在这里执行!

只有当construct()执行完毕后,才会执行finished()方法。

 

 

结语

   
上面就是关于Swing多线程开发的一些论述。注意到Swing单线程事件队列开发模型这个事实,你就能够合理的把代码分配到一般Java线程和Swing事件派发线程中,提高Swing应用的性能。

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注