前續知識,一個swing程序有三個類型的線程,
- 初始化線程:就是main函數,用來啟動GUI
- 用戶事件調度線程EDT:負責對GUI組件的渲染和刷新,它只有一個,一定要注意這個問題,它處理的就是事件隊列里的事情,他通過調用事件處理器來響應用戶交互。所有的事件處理都是在EDT上進行的。
- 任務線程:響應時具體的數據處理。
Swing框架負責管理組件繪制、更新以及EDT上的線程處理。可以想象,該線程的事件隊列很繁忙,幾乎每一次GUI交互和事件都是通過它完成。事件隊列的上任務必須非常快,否則就會阻塞其他任務的執行,使隊列里阻塞了很多等待執行的事件,造成界面響應不靈活,讓用戶感覺到界面響應速度很慢,使他們失去興趣。理想情況下,任何需時超過30到100毫秒的任務不應放在EDT上執行,否則用戶就會覺察到輸入和界面響應之間的延遲。
在這里一定注意:
- 其他線程上訪問UI組件和事件處理器都是不安全的,都有可能導致界面的更新和繪制錯誤。
- 在EDT上執行耗時性任務會發生阻塞隊列,會讓你覺得界面很卡,所以,要盡可能的不要在EDT上編寫耗時性的代碼。
- 應當使用獨立的任務線程來執行耗時性的任務。
所以到現在一句話,所有耗時性的任務(即任何干擾或延遲UI的任務)都應該放在任務線程中去解決。在初始化線程和任務線程中與UI組件或其缺省數據模型進行的交互都是非線程安全的。
既然是非線程安全怎么辦呢?這時候一個非常強大的類就出現了,神一般的SwingWorker工具類,它幫助任務線程與EDT進行交互,雖然不能實現完全的線程安全,但是解決了大部分的非線程安全的問題。通過它使任務線程和EDT各負其責,EDT實現繪制和刷新,任務線程實現與界面無關的耗時性的操作。將EDT線程僅用於GUI任務。
在這里加個知識點:
同步與異步:
- 同步是程序在發起請求后開始處理事件並等待處理的結果或等待請求執行完畢,在此之前程序被block住直到請求完成。
- 異步是當前程序發起請求后立即返回,當前程序不會立即處理該事件並等待處理的結果,請求是在稍后的某一時間才被處理。
串行與並行:
- 串行是指多個要處理請求順序執行,處理完一個再處理下一個;容易阻塞。
- 並行可以理解為並發,是同時處理多個請求(實際上我們只能理解為是這樣,特別是CPU數目少於線程數的機器而言,真正意義的並發是不存在的,各個線程只是斷斷續續地交替地執行)。
SwingWorker類介紹
Class SwingWorker<T,V>
參數類型
T - 該 SwingWorker's doInBackground和 get方法返回的結果類型,就是最終結果的類型
V - 用於執行中間結果的類型 SwingWorker's publish和 process方法的處理的數據類型 ,就是中間結果的類型
All Implemented Interfaces:Runnable, Future<T>, RunnableFuture<T>
最重要的幾個方法:
protected abstract T doInBackground()
- 計算一個結果,如果不能這樣做,就會拋出一個異常。
- 首先,這個方法一定要重寫,因為這里是處理一個swingworker實例的最后計算結果,當這個函數被執行完后,說明這個線程的所有順序執行的程序已經執行完了。
- 其次,想要獲取doinbackground方法的返回值要使用get方法,get方法是同步的,所以一般使用時在done方法中調用或者在判斷isdone后執行。
- 最后,因為,當doinbackground執行完后,在EDT中會自動執行done方法。
protected void done()
doInBackground方法完成后,在EDT中被執行。
void execute()
計划這個 SwingWorker在 工作線程上執行。用它來開啟這個進程,就是說一旦執行這個方法,swingworker就進入了工作線程。
T get()
等待doInBackground計算完成,返回doInBackground的返回值。
boolean isDone()
判斷是否完成整個計算過程,如果此任務完成,則返回 true 。
protected void process(List<V> chunks)
在 事件調度線程上異步接收來自 publish方法的數據塊。即中間結果。中間結果是任務線程在產生最后結果之前就能產生的數據。當任務線程執行時,它可以發布類型為V的中間結果,通過覆蓋process方法來處理中間結果。任務對象的父類會在EDT線程上激活process方法,因此在process方法中程序可以安全的更新UI組件。這個方法線程安全的實現了DET與任務線程之間的交互。過程是這樣的,當從任務線程調用publish方法時,在DET上自動執行process方法。
protected void publish(V chunks)
發送數據chunks到 process(java.util.List<V>)方法中的list列表中。這個方法是自動在DET上被調用的。是由任務對象的父類將其激活的。
SwingWorker具體過程講解:
首先我們要知道一個SwingWorker的生命周期涉及三個狀態,三個線程:
- PENDING:初始狀態,被創建時的狀態。
- STARTED:調用 doInBackground后的狀態。
- DONE:doInBackground方法完成后的狀態。
- 當前線程:在這個線程上調用了execute()方法。它在工作線程上執行SwingWorker,並立即返回,因為是異步的。
- 工作線程:在這個線程上調用了doInBackground()方法。 這是所有背景活動都應該發生的地方。
- 事件調度線程EDT:所有Swing相關活動發生在此線程上。
知道以上狀態和涉及的線程后,我們看整個SwingWorker的運行過程:
一般當前線程是EDT,當在當前線程中執行excute方法后,在工作線程上執行swingworker(注意為什么不是直接進入工作線程呢,是因為是並發執行的,具體什么時候看CPU),即調用doInBackground方法,這時候,狀態由PENDING進入STARTED。在此方法中,如果希望獲得中間結果,可以用publish(V chunk)方法,chunk是中間結果,一旦publish,就會將chunk送入process(List<V> chunks)方法中的chunks列表中,並且這個process方法在EDT中自動執行,這個方法非常漂亮的實現了任務線程與EDT之間的交互。當doInBackground方法執行完后,會使狀態值STARTED變為DONE狀態,並且自動執行在EDT中自動執行done方法,注意done是在EDT中執行的,所以這里也實現了交互,在done方法里調用get方法獲取doInBackground的返回值。到此,整個swingworker就被執行完了,而且它只能夠執行一次。
這里有一個問題:為什么直接通過調用doInBackground或許返回值呢,答案是當然不行呀,其實這個方法也是自動被調用的,不是自己調用的呀,這個方法是由swingworker內部調用的,精髓就在這里呀,就是因為這個方法是swingworker自己為了實現異步而自己去調用的,我們只是去重寫它去實現相應的計算。而且,這個方法只能運行一次,wingworker在設計上只能被運行一次就是因為這個原因。所以想要獲取這個方法的返回值,我們只能用同步的get方法。既然是同步的所以會發生阻塞現象,提前要判斷isdone了沒。
總結一下,doInBackground、publish是任務線程中調用的,done、process是在EDT中調用的的。doInBackground與done,publish與process,這兩對方法,優美的完成了大部分EDT與任務線程之間的線程安全的異步交互。記得,EDT上執行的代碼都盡可能是非耗時性的。
運行實例一:實現工作線程與EDT線程安全的並發執行
在航顯系統中,創建一個JTable列表,每當我們從服務器獲取一個事件信息后,我們需要處理這條數據信息,並把它添加到列表中,如果此時不進行線程安全的並發操作,那么UI產生的其它事件都阻塞在EDT的事件觸發隊列里,什么操作都做不了,知道數據全部處理完。現在我們用SwingWorker來實現就不會出現這種問題。
首先將所有后台處理數據放在doInBackground中執行,即重寫。
1 @Override 2 protected String doInBackground() throws Exception { 3
4 String dateLines = null;
5 Pattern pattern = Pattern.compile( 6 "(?mx)^.*flid=([^0]|[0-9]{2,}),.*flno=(.*),.*mvin=D.*"+
7 "("+
8 "(Rout.*rtno=) (1)"+
9 "(,\\sapcd=) (\\w{3})"+
10 "(,\\s(\\w|=|,|\\s)*scdt=) ([\\d|\\w]{0,11})"+
11 "(\\],\\s)"+
12 ")"+
13 "("+
14 "(Rout.*rtno=) (2)"+
15 "(,\\sapcd=) (\\w{3})"+
16 "(,\\s(\\w|=|,|\\s)*scdt=) ([\\d|\\w]{0,11})"+
17 "(\\],\\s)"+
18 ")"+
19 "(("+
20 "(Rout.*rtno=) (3)"+
21 "(,\\sapcd=) (\\w{3})"+
22 "(,\\s(\\w|=|,|\\s)*scdt=) ([\\d|\\w]{0,11})"+
23 "(\\],\\s)"+
24 ")|)(.*)"
25 ); 26 try (Socket socket = new Socket("10.5.25.193", 9999)) { 27
28 Scanner scanner = new Scanner(socket.getInputStream()); 29 String totalLines = scanner.nextLine(); 30 System.out.println(totalLines); 31 JOptionPane.showMessageDialog(null, "連接服務器成功\r\n" + totalLines ); 32 while (scanner.hasNextLine()) { 33 dateLines = scanner.nextLine(); 34 Matcher matcher = pattern.matcher(dateLines); 35 if (matcher.find()) { 36 String [] strings =new String[]{ 37 matcher.group(1+1), 38 getAirportName(matcher.group(6+1)), 39 getAirportName(matcher.group(15 + 1)), 40 getAirportName(matcher.group(25 + 1)), 41 getTime(matcher.group(9 + 1)), 42 }; 43 publish(strings); 44 } 45 } 46 } 47 return "dates have been processed!";
1 return "dates have been processed!"
48 }
由於此方法是異步執行所以會直接返回值,在后續過程中繼續執行。由於我們希望沒處理完一條信息,就顯示在航顯系統中,所以我們需要的是過程中的處理結果,將過程中的處理結果publish到process維護的數據塊中。每當publish方法在任務線程中執行一次,process方法就會在EDT中被執行。但是process從publish獲得的數據是以數據列表形式被存儲的,此列表中包含了所有publish的數據。現在我們就可以重寫process方法,實現工作線程與EDT的交互。每次我們將chunks中的最后一條數據添加到table中。
1 protected void process(List<String[]> chunks) { 2 tableModel.addRow(chunks.get(chunks.size()-1)); 3 }
當希望所有的數據處理完后,即doInBackground方法執行完,即工作線程結束后,需要收尾工作,那么我們就可以重寫done方法。
1 protected void done() { 2 String message; 3 try { 4 message = get();//得到doinbackground方法的返回值
5 JOptionPane.showMessageDialog(null, message); 6 } catch (ExecutionException | InterruptedException e) { 7 e.printStackTrace(); 8 } 9 }
通過SwingWorker很好了實現了任務線程與EDT之間線程安全的異步的數據交互。
實例二:通過SwingWorker實現JProgressBar
1 import java.awt.BorderLayout; 2 import java.awt.EventQueue; 3 import java.util.List; 4 import javax.swing.JFrame; 5 import javax.swing.JOptionPane; 6 import javax.swing.JPanel; 7 import javax.swing.border.EmptyBorder; 8 import javax.swing.JProgressBar; 9 import javax.swing.SwingWorker; 10 import javax.swing.JButton; 11 import java.awt.event.ActionListener; 12 import java.awt.event.ActionEvent; 13
14 public class SwingWorkerWithProgressBar extends JFrame { 15
16 private JPanel contentPane; 17 private JProgressBar progressBar; 18
19 /**
20 * Launch the application. 21 */
22 public static void main(String[] args) { 23 EventQueue.invokeLater(new Runnable() { 24 public void run() { 25 try { 26 SwingWorkerWithProgressBar frame = new SwingWorkerWithProgressBar(); 27 frame.setVisible(true); 28 } catch (Exception e) { 29 e.printStackTrace(); 30 } 31 } 32 }); 33 } 34
35 /**
36 * Create the frame. 37 */
38 public SwingWorkerWithProgressBar() { 39 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 40 setBounds(100, 100, 450, 300); 41 contentPane = new JPanel(); 42 contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); 43 contentPane.setLayout(new BorderLayout(0, 0)); 44 setContentPane(contentPane); 45
46 progressBar = new JProgressBar(0, 100); 47 contentPane.add(progressBar, BorderLayout.NORTH); 48
49 JButton btnBegin = new JButton("Begin"); 50 btnBegin.addActionListener(new ActionListener() { 51 public void actionPerformed(ActionEvent e) { 52 new ProgressBarRealized().execute(); 53 } 54 }); 55 contentPane.add(btnBegin, BorderLayout.SOUTH); 56 } 57
58 class ProgressBarRealized extends SwingWorker<Void, Integer> { 59
60 @Override 61 //后台任務在此方法中實現
62 protected Void doInBackground() throws Exception { 63 // 模擬有一百項任務,每次睡1s
64 for (int i = 0; i < 100; i++) { 65 Thread.sleep(1000); 66 publish(i);//將當前進度信息加入chunks中
67 } 68 return null; 69 } 70 @Override 71 //每次更新進度條的信息
72 protected void process(List<Integer> chunks) { 73 progressBar.setValue(chunks.get(chunks.size() - 1)); 74 } 75 @Override 76 //任務完成后返回一個信息
77 protected void done() { 78 JOptionPane.showMessageDialog(null, "任務完成!"); 79 } 80 } 81 }
關於Swing的線程安全
SwingWorker
適用於需要在后台線程中運行長時間運行的任務並在完成或處理時向UI提供更新的情況。 SwingWorker
子類必須實現doInBackground()方法來執行后台計算。大多數Swing API是非線程安全的,也就是說不能在任意地方調用,它應該只在EDT中調用。Swing的線程安全靠事件隊列和EDT來保障。對非EDT的並發調用需通過 invokeLater(runnable)和invokeAndWait(runnable)使請求插入到隊列中等待EDT去執行。 invokeLater(runnable)方法是異步的,它會立即返回,具體何時執行請求並不確定,所以命名invokeLater是稍后調用。invokeAndWait(runnable)方法是同步的,它被調用結束會立即block當前線程(調用invokeAndWait的那個線程)直到EDT處理完那個請求。invokeAndWait一般的應用是取得Swing組件的數據。
參考博文:http://blog.sina.com.cn/s/blog_4b6047bc010007th.html