Java自學-圖形介面 Swing中的執行緒
Swing中的執行緒
步驟 1 : 三種執行緒
在Swing程式的開發中,需要建立3種執行緒的概念
-
初始化執行緒
初始化執行緒用於創建各種容器,組件並顯示他們,一旦創建並顯示,初始化執行緒的任務就結束了。 -
事件調度執行緒
通過事件監聽的學習,我們了解到Swing是一個事件驅動的模型,所有和事件相關的操作都放是放在事件調度執行緒 (Event Dispatch)中進行的。比如點擊一個按鈕,對應的ActionListener.actionPerformed 方法中的程式碼,就是在事件調度執行緒 Event Dispatch Thread中執行的。 -
長耗時任務執行緒
有時候需要進行一些長時間的操作,比如訪問資料庫,文件複製,連接網路,統計文件總數等等。 這些操作就不適合放在事件調度執行緒中進行,因為佔用時間久了,會讓使用者感覺介面響應很卡頓。 為了保持介面響應的流暢性,所有長耗時任務都應該放在專門的 長耗時任務執行緒中進行
步驟 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);
}
}
練習: 查找文件內容
(查找文件內容本身是一個比較耗時的任務,採用長耗時任務執行緒的手段,開發這個功能)