Java自學-圖形介面 Swing中的執行緒

Swing中的執行緒

步驟 1 : 三種執行緒

在Swing程式的開發中,需要建立3種執行緒的概念

  1. 初始化執行緒
    初始化執行緒用於創建各種容器,組件並顯示他們,一旦創建並顯示,初始化執行緒的任務就結束了。

  2. 事件調度執行緒
    通過事件監聽的學習,我們了解到Swing是一個事件驅動的模型,所有和事件相關的操作都放是放在事件調度執行緒 (Event Dispatch)中進行的。比如點擊一個按鈕,對應的ActionListener.actionPerformed 方法中的程式碼,就是在事件調度執行緒 Event Dispatch Thread中執行的。

  3. 長耗時任務執行緒
    有時候需要進行一些長時間的操作,比如訪問資料庫,文件複製,連接網路,統計文件總數等等。 這些操作就不適合放在事件調度執行緒中進行,因為佔用時間久了,會讓使用者感覺介面響應很卡頓。 為了保持介面響應的流暢性,所有長耗時任務都應該放在專門的 長耗時任務執行緒中進行

步驟 2 : 事件調度執行緒是單執行緒的

在開始講解這3種執行緒之前, 要建立一個概念: 事件調度執行緒是單執行緒的。
為什麼呢?
這是因為 Swing裡面的各種組件類,比如JTextField,JButton 都不是執行緒安全的,這就意味著,如果有多個執行緒,那麼同一個JTextField的setText方法,可能會被多個執行緒同時調用,這會導致同步問題以及錯誤數據的發生。

如果把組件類設計成為執行緒安全的,由於Swing事件調度的複雜性,就很有可能導致死鎖的發生。

為了規避同步問題,以及降低整個Swing設計的複雜度,提高Swing的相應速度,Swing中的 事件調度執行緒被設計成為了單執行緒模式,即只有一個執行緒在負責事件的響應工作。

步驟 3 : 初始化執行緒

如程式碼所示,同時我們在初始化一個圖形介面的時候,都會直接在主方法的主執行緒里,直接調用如下程式碼來進行初始化

new TestFrame().setVisible(true);

如果是小程式這沒有什麼問題,如果是複雜的程式就有可能產生問題了。因為這裡有兩個執行緒在同時訪問組件:1. 主執行緒 2. 事件調度執行緒。 如果是複雜的圖形介面程式,就有可能出現這兩個執行緒同時操作的情況,導致同步問題的產生。

為了規避這個問題的產生,創建和顯示介面的工作,最好也交給事件調度執行緒,這樣就保證了只有一個執行緒在訪問這些組件

SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        new TestFrame().setVisible(true);
    }
});

像這樣,new TestFrame().setVisible(true); 這段程式碼就是在事件調度執行緒中執行了。

還可以使用SwingUtilities.isEventDispatchThread()來判斷當前執行緒是否是事件調度執行緒

package gui;
 
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
 
public class TestGUI {
    public static void main(String[] args) {
        new TestFrame().setVisible(true);
         
//      SwingUtilities.invokeLater(new Runnable() {
//          public void run() {
//              new TestFrame().setVisible(true);
//          }
//      });
    }
 
    static class TestFrame extends JFrame {
        public TestFrame() {
            setTitle("LoL");
 
            setSize(400, 300);
 
            setLocation(200, 200);
 
            setLayout(null);
 
            JButton b = new JButton("一鍵秒對方基地掛");
 
            b.setBounds(50, 50, 280, 30);
 
            add(b);
 
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
            setVisible(true);
             
            System.out.println("當前執行緒是否是 事件調度執行緒: " + SwingUtilities.isEventDispatchThread());
 
        }
    }
}

步驟 4 : 事件調度執行緒

以 按鈕監聽 中的程式碼為例,ActionListener.actionPerformed 中的程式碼,就是事件調度執行緒執行的。

可以藉助SwingUtilities.isEventDispatchThread() 確認,是事件調度執行緒在執行相應的程式碼

事件調度執行緒

package gui;
   
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
   
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
   
public class TestGUI {
    public static void main(String[] args) {
   
        JFrame f = new JFrame("LoL");
        f.setSize(400, 300);
        f.setLocation(580, 200);
        f.setLayout(null);
   
        final JLabel l = new JLabel();
   
        ImageIcon i = new ImageIcon("e:/project/j2se/shana.png");
        l.setIcon(i);
        l.setBounds(50, 50, i.getIconWidth(), i.getIconHeight());
   
        JButton b = new JButton("隱藏圖片");
        b.setBounds(150, 200, 100, 30);
   
        b.addActionListener(new ActionListener() {
   
            public void actionPerformed(ActionEvent e) {
                l.setVisible(false);
                 
                System.out.println("當前使用的是事件調度執行緒:" + SwingUtilities.isEventDispatchThread());
            }
        });
   
        f.add(l);
        f.add(b);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   
        f.setVisible(true);
    }
}

步驟 5 : 長耗時任務執行緒

有時候需要執行長耗時任務,比如資料庫查詢,文件複製,訪問網路等等。

而這些操作一般都會在事件響應後發起,就會自動進入事件調度執行緒。 而事件調度執行緒又是單執行緒模式,其結果就會是在執行這些長耗時任務的時候,介面就無響應了。

如圖所示,當點擊第一個按鈕的時候,會在其中進行一個5秒鐘的任務,這個期間,第一個按鈕會保持按下狀態,其他按鈕也無法點擊,出現了無響應了狀態。

為了解決這個問題,Swing提供了一個SwingWorker類來解決。 SwingWorker是一個抽象類,為了使用,必須實現方法 doInBackground,在doInBackground中,就可以編寫我們的任務,然後執行SwingWorker的execute方法,放在專門的工作執行緒中去運行。

SwingWorker worker = new SwingWorker() {
    protected Object doInBackground() throws Exception {
        //長耗時任務
        return null;
    }
};
worker.execute();

SwingWorker又是如何工作的呢?
當SwingWorker執行execute的時候,調用默認有10根執行緒的執行緒池,執行doInBackground中的程式碼,通過如下程式碼,可以獲知執行當前SwingWorder的執行緒名稱

System.out.println("執行這個SwingWorder的執行緒是:" + Thread.currentThread().getName());

長耗時任務執行緒

package gui;
 
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingWorker;
 
public class TestGUI {
    public static void main(String[] args) {
 
        JFrame f = new JFrame("LoL");
        f.setSize(300, 300);
        f.setLocation(200, 200);
        f.setLayout(new FlowLayout());
 
        JButton b1 = new JButton("在事件調度執行緒中執行長耗時任務");
        JButton b2 = new JButton("使用SwingWorker執行長耗時任務");
        JLabel l = new JLabel("任務執行結果");
        f.add(b1);
        f.add(b2);
        f.add(l);
 
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
        b1.addActionListener(new ActionListener() {
 
            @Override
            public void actionPerformed(ActionEvent e) {
                l.setText("開始執行完畢");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
                l.setText("任務執行完畢");
            }
        });
        b2.addActionListener(new ActionListener() {
 
            @Override
            public void actionPerformed(ActionEvent e) {
                SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
 
                    @Override
                    protected Void doInBackground() throws Exception {
                        System.out.println("執行這個SwingWorder的執行緒是:" + Thread.currentThread().getName());
                        l.setText("開始執行完畢");
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e1) {
                            // TODO Auto-generated catch block
                            e1.printStackTrace();
                        }
                        l.setText("任務執行完畢");
                        return null;
                    }
                };
                worker.execute();
 
            }
        });
 
        f.setVisible(true);
    }
}

練習查找文件內容

(查找文件內容本身是一個比較耗時的任務,採用長耗時任務執行緒的手段,開發這個功能)
在這裡插入圖片描述