一個swing程序有三個類型的線程,
> 應該是這三個:初始化線程,edt,toolkit線程,用戶線程不一定有
* 初始化線程:就是main函數,用來啟動GUI
* 用戶事件調度線程EDT:負責對GUI組件的渲染和刷新,它只有一個,一定要注意這個問題,它處理的就是事件隊列里的事情,他通過調用事件處理器來響應用戶交互。所有的事件處理都是在EDT上進行的。
* 任務線程:響應時具體的數據處理。
Swing框架負責管理組件繪制、更新以及EDT上的線程處理。可以想象,該線程的事件隊列很繁忙,幾乎每一次GUI交互和事件都是通過它完成。事件隊列的上任務必須非常快,否則就會阻塞其他任務的執行,使隊列里阻塞了很多等待執行的事件,造成界面響應不靈活,讓用戶感覺到界面響應速度很慢,使他們失去興趣。理想情況下,任何需時超過30到100毫秒的任務不應放在EDT上執行,否則用戶就會覺察到輸入和界面響應之間的延遲。
在這里一定注意:
1. 其他線程上訪問UI組件和事件處理器都是不安全的,都有可能導致界面的更新和繪制錯誤。
1. 在EDT上執行耗時性任務會發生阻塞隊列,會讓你覺得界面很卡,所以,要盡可能的不要在EDT上編寫耗時性的代碼。
1. 應當使用獨立的任務線程來執行耗時性的任務。
**所以到現在一句話,所有耗時性的任務(即任何干擾或延遲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?()
1. 計算一個結果,如果不能這樣做,就會拋出一個異常。
1. 首先,這個方法一定要重寫,因為這里是處理一個swingworker實例的最后計算結果,當這個函數被執行完后,說明這個線程的所有順序執行的程序已經執行完了。
1. 其次,想要獲取doinbackground方法的返回值要使用get方法,**get方法是同步的,所以一般使用時在done方法中調用或者在判斷isdone后執行**。
1. 最后,因為,當doinbackground執行完后,在EDT中會自動執行done方法。
### protected void done?() --**在edt中被執行,可用于修改GUI**
doInBackground方法完成后,在EDT中被執行。
### void execute?()
計劃這個 SwingWorker在 工作線程上執行。用它來開啟這個進程,就是說一旦執行這個方法,swingworker就進入了工作線程。
### T get?()
等待doInBackground?計算完成,返回doInBackground?的返回值。
### boolean isDone?()
判斷是否完成整個計算過程,如果此任務完成,則返回 true 。
### protected void process?(List<V> chunks)**在edt中被執行,可用于修改GUI**
在 事件調度線程上異步接收來自 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中執行,即重寫。
```
@Override
protected String doInBackground() throws Exception {
String dateLines = null;
Pattern pattern = Pattern.compile(
"(?mx)^.*flid=([^0]|[0-9]{2,}),.*flno=(.*),.*mvin=D.*"+
"("+
"(Rout.*rtno=) (1)"+
"(,\\sapcd=) (\\w{3})"+
"(,\\s(\\w|=|,|\\s)*scdt=) ([\\d|\\w]{0,11})"+
"(\\],\\s)"+
")"+
"("+
"(Rout.*rtno=) (2)"+
"(,\\sapcd=) (\\w{3})"+
"(,\\s(\\w|=|,|\\s)*scdt=) ([\\d|\\w]{0,11})"+
"(\\],\\s)"+
")"+
"(("+
"(Rout.*rtno=) (3)"+
"(,\\sapcd=) (\\w{3})"+
"(,\\s(\\w|=|,|\\s)*scdt=) ([\\d|\\w]{0,11})"+
"(\\],\\s)"+
")|)(.*)"
);
try (Socket socket = new Socket("10.5.25.193", 9999)) {
Scanner scanner = new Scanner(socket.getInputStream());
String totalLines = scanner.nextLine();
System.out.println(totalLines);
JOptionPane.showMessageDialog(null, "連接服務器成功\r\n" + totalLines );
while (scanner.hasNextLine()) {
dateLines = scanner.nextLine();
Matcher matcher = pattern.matcher(dateLines);
if (matcher.find()) {
String [] strings =new String[]{
matcher.group(1+1),
getAirportName(matcher.group(6+1)),
getAirportName(matcher.group(15 + 1)),
getAirportName(matcher.group(25 + 1)),
getTime(matcher.group(9 + 1)),
};
publish(strings);
}
}
}
return "dates have been processed!";
return "dates have been processed!"
}
```
由于此方法是異步執行所以會直接返回值,在后續過程中繼續執行。由于我們希望每處理完一條信息,就顯示在航顯系統中,所以我們需要的是過程中的處理結果,將過程中的處理結果publish到process維護的數據塊中。每當publish方法在任務線程中執行一次,process方法就會在EDT中被執行。但是process從publish獲得的數據是以數據列表形式被存儲的,此列表中包含了所有publish的數據。現在我們就可以重寫process方法,實現工作線程與EDT的交互。每次我們將chunks中的最后一條數據添加到table中。
```
protected void process(List<String[]> chunks) {
tableModel.addRow(chunks.get(chunks.size()-1));
}
```
當希望所有的數據處理完后,即doInBackground方法執行完,即工作線程結束后,需要收尾工作,那么我們就可以重寫done方法。
```
protected void done() {
String message;
try {
message = get();//得到doinbackground方法的返回值
JOptionPane.showMessageDialog(null, message);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
```
通過SwingWorker很好了實現了任務線程與EDT之間線程安全的異步的數據交互。
### 實例二:通過SwingWorker實現JProgressBar
```
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class SwingWorkerWithProgressBar extends JFrame {
private JPanel contentPane;
private JProgressBar progressBar;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
SwingWorkerWithProgressBar frame = new SwingWorkerWithProgressBar();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public SwingWorkerWithProgressBar() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
progressBar = new JProgressBar(0, 100);
contentPane.add(progressBar, BorderLayout.NORTH);
JButton btnBegin = new JButton("Begin");
btnBegin.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
new ProgressBarRealized().execute();
}
});
contentPane.add(btnBegin, BorderLayout.SOUTH);
}
class ProgressBarRealized extends SwingWorker<Void, Integer> {
@Override
//后臺任務在此方法中實現
protected Void doInBackground() throws Exception {
// 模擬有一百項任務,每次睡1s
for (int i = 0; i < 100; i++) {
Thread.sleep(1000);
publish(i);//將當前進度信息加入chunks中
}
return null;
}
@Override
//每次更新進度條的信息
protected void process(List<Integer> chunks) {
progressBar.setValue(chunks.get(chunks.size() - 1));
}
@Override
//任務完成后返回一個信息
protected void done() {
JOptionPane.showMessageDialog(null, "任務完成!");
}
}
}
```
## 關于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組件的數據。
- 前言
- CSS
- VUE
- Vue.js 安裝
- Vue.js 目錄結構
- Vue.js 起步
- Vue.js 模板語法
- Vue.js 條件與循環
- Vue.js 循環語句
- Vue.js 計算屬性
- Vue.js 監聽屬性
- Vue.js 樣式綁定
- Vue.js 事件處理器
- Vue.js 表單
- Vue.js 組件
- Vue.js 自定義指令
- Vue.js 路由
- React
- 安裝
- React JSX
- React 組件
- 問題1
- React state
- React Props
- React 組件 API
- React 組件生命周期
- React AJAX
- React 表單與事件
- React Refs
- Babel
- Ant Design
- 安裝
- 快速上手
- webpack
- 安裝
- JavaScript
- 知識點
- 字符轉數字
- js中字符串全部替換
- 函數
- reduce() 方法
- UI控件
- DataTable
- 語言配置 選項
- 增加行
- 列渲染-自定義列
- 創建行回調-操作行
- 自定義數據長度
- 默認設置
- 樣式
- 集成Bootstrap 3
- 分頁相關
- 數據
- NodeJs
- Electron
- 打包
- 介紹
- 知識點
- 使用 jquery
- CommonJS規范
- Bower
- 簡介
- 安裝
- Swing
- Swing界面組件
- JComboBox
- JDesktopPane和JInternalFrame
- JFrame
- JTabbedPane
- JTable
- JProgressBar
- JToolBar
- 知識點
- 截取log4j日志并輸出到GUI組件
- JFrame 居中顯示
- Swing中三種最大化初始窗口的方法
- Layout布局
- BorderLayout
- GridBagLayout
- GridLayout
- BoxLayout
- JxBrowser
- 瀏覽器引擎-Browser Engine
- 創建瀏覽器-Creating Browser
- 創建隱身瀏覽器-Creating Incognito Browser
- 存儲用戶數據-Storing User Data
- 處理瀏覽器-Disposing Browser
- 瀏覽器偏好-Browser Preferences
- 恢復瀏覽器-Restoring Browser
- 渲染流程事件-Render Process Events
- 渲染進程ID-Render Process ID
- 獲取幀ID-Getting Frame IDs
- 獲取產品版本-Getting Product Version
- 尋找文本-Finding Text
- 清除緩存-Clearing Cache
- 轉發鍵盤事件-Forwarding Key Events
- 轉發鼠標事件-Forwarding Mouse Events
- 加載內容-Loading Content
- 加載網址-Loading URL
- 使用POST加載URL-Loading URL with POST
- 加載HTML-Loading HTML
- 從JAR加載HTML-Loading HTML from JAR
- 獲取HTML-Getting HTML
- 獲取選定的HTML-Getting Selected HTML
- 加載事件-Loading Events
- 正在加載和等待-Loading & Waiting
- 顯示PDF-Displaying PDF
- 網絡活動-Network Events
- 處理資源加載-Handling Resources Loading
- 啟用/禁用退格導航-Enabling/Disabling Backspace Navigation
- 處理SSL證書錯誤-Handling SSL Certificate Errors
- SSL證書驗證程序-SSL Certificate Verifier
- 導航歷史-Navigation History
- User-Agent
- WebSockets
- 處理加載-Handling Loading
- 修改POST / PUT / PATCH上傳數據-Modifying POST/PUT/PATCH Upload Data
- HTML5本地和會話存儲-HTML5 Local & Session storages
- 訪問HTTP響應數據-Accessing HTTP response data
- HTTP服務器白名單-HTTP Server Whitelist
- 自定義協議處理程序-Custom Protocol Handler
- ActiveX
- 瀏覽器視圖-Browser View
- 輕量級或重量級-Lightweight or Heavyweight
- 在Swing中使用JxBrowser-Using JxBrowser in Swing
- 在JavaFX中使用JxBrowser-Using JxBrowser in JavaFX
- 在SWT中使用JxBrowser-Using JxBrowser in SWT
- 自定義CSS光標-Custom CSS Cursors
- 標題事件-Title Events
- 狀態事件-Status Events
- 鍵盤和鼠標事件-Keyboard & Mouse Events
- 處理鍵盤事件-Handling Keyboard Events
- 處理鼠標事件-Handling Mouse Events
- 編輯器命令-Editor Commands
- 拖放-Drag & Drop
- 內容縮放-Content scaling
- 上下文菜單-Context Menu
- JMenuBar
- JInternalFrame
- JTabbedPane
- JPanel
- 加速輕量級渲染-Accelerated Lightweight Rendering
- 透明背景-Transparent Background
- DOM
- 使用文檔-Working with Document
- 注入css-Injecting CSS
- 尋找元素-Finding Elements
- 元素屬性-Element Attributes
- 創建元素和文本節點-Creating Element & Text Node
- 設置節點值-Setting Node Value
- Select & Option Elements
- 選擇CheckBox-Selecting CheckBox
- Getting Selected Text
- 模擬點擊-Simulating Click
- DOM事件
- XPath
- 查詢選擇器-Query Selector
- 使用表單-Working with Form
- 滾動文檔-Scrolling Document
- 在Point處查找節點-Finding Node at Point
- 獲得元素界限-Getting Element Bounds
- 監聽內容變化-Listening to the Сontent Сhanges
- 模擬DOM事件-Simulating DOM Events
- Audio & Video
- MP3/MP4/H.264
- 網絡攝像頭和麥克風-Web Camera & Microphone
- 全屏視頻-Full Screen Video
- 靜音音頻-Muting Audio
- HTML5 Video
- Pop-ups
- 關于彈出窗口-About Pop-ups
- 在swing中處理彈出窗口-Handling Pop-ups Swing
- 在JavaFX中處理彈出窗口-Handling Pop-ups JavaFX
- Dialogs
- JavaScript對話框-JavaScript Dialogs
- 文件下載-File Download
- 上傳文件-File Upload
- 選擇SSL證書-Select SSL Certificate
- 選擇自定義SSL證書-Select Custom SSL Certificate
- 卸載前-Before Unload
- 顏色選擇器-Color Chooser
- Proxy
- 使用代理-Working with Proxy
- 系統代理設置-System Proxy Settings
- Authentication
- 處理代理驗證-Handling Proxy Authentication
- 處理基本,摘要和NTLM身份驗證-Handling Basic, Digest and NTLM Authentication
- JavaScript Java Bridge
- 從Java調用JavaScript-Calling JavaScript from Java
- 從JavaScript調用Java-Calling Java from JavaScript
- 控制臺消息-Console Messages
- 使用JSON-Working with JSON
- 使用jQuery-Working with jQuery
- 使用ScriptContext-Working with ScriptContext
- 將表單數據發送到Java-Sending Form Data to Java
- 使用數組-Working with Arrays
- @JSAccessible
- Plugins
- Printing
- Cookies
- Saving Web Page
- Zoom
- Integration
- Deploying
- Chromium
- Spell Checker
- Debugging
- Why JxBrowser
- Tips & Tricks
- 基礎知識
- AbstractAction
- Void
- SwingWorker應用詳解
- JAVA實現國際化
- UIManager
- AppJS
- heX
- bootstrap
- 知識點
- 空行
- Eclipse RCP
- Eclipse e4 概覽