本文內容
何時該使用線程鎖.
線程鎖的寫法.
以線程鎖的例子來理解線程的調度。
使用線程鎖的場合
程序中經常采用多線程處理,這可以充分利用系統資源,縮短程序響應時間,改善用戶體驗;如果程序中只使用單線程,那么程序的速度和響應無疑會大打折扣。
但是,程序采用了多線程后,你就必須認真考慮線程調度的問題,如果調度不當,要么造成程序出錯,要么造成荒謬的結果。
一個諷刺僵化體制的笑話
前蘇聯某官員去視察植樹造林的情況,現場他看到一個人在遠處挖坑,其后不遠另一個人在把剛挖出的坑逐個填上,官員很費解于是詢問陪同人員,當地管理人員說“負責種樹的人今天病了”。
上面這個笑話如果發生在程序中就是線程調度的問題,種樹這個任務有三個線程:挖坑線程,種樹線程和填坑線程,后面的線程必須等前一個線程完成才能進行,而不是按時間順序來進行,否則一旦一個線程出錯就會出現上面荒謬的結果。
用線程鎖來處理兩個線程先后執行的情況
在程序中,和種樹一樣,很多任務也必須以確定的先后秩序執行,對于兩個線程必須以先后秩序執行的情況,我們可以用線程鎖來處理。
線程鎖的大致思想是:如果線程A和線程B會執行實例的兩個函數a和b,如果A必須在B之前運行,那么可以在B進入b函數時讓B進入wait set,直到A執行完a函數再把B從wait set中激活。這樣就保證了B必定在A之后運行,無論在之前它們的時間先后順序是怎樣的。
線程鎖的代碼
如右,SwingComponentLock的實例就是一個線程鎖,lock函數用于鎖定線程,當完成狀態isCompleted為false時進入的線程會進入SwingComponentLock的實例的wait set,已完成則不會;要激活SwingComponentLock的實例的wait set中等待的線程需要執行unlock函數。
public class SwingComponentLock {
// 是否初始化完畢
boolean isCompleted = false;
/**
* 鎖定線程
*/
public synchronized void lock() {
while (!isCompleted) {
try {
wait();
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage());
}
}
}
/**
* 解鎖線程
*
*/
public synchronized void unlock() {
isCompleted = true;
notifyAll();
}
}
線程鎖的使用
public class TreeViewPanel extends BasePanel {
// 表空間和表樹
private JTree tree;
// 這個是防樹還未初始化好就被刷新用的
private SwingComponentLock treeLock;
protected void setupComponents() {
// 初始化鎖
treeLock = new SwingComponentLock();
// 創建根節點
DefaultMutableTreeNode root = new DefaultMutableTreeNode("DB");
tree = new JTree(root);
// 設置布局并裝入樹
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
add(new JScrollPane(tree));
// 設置樹節點的圖標
setupTreeNodeIcons();
// 解除對樹的鎖定
treeLock.unlock();
}
/**
* 刷新樹視圖
*
* @param schemas
*/
public synchronized void refreshTree(List<SchemaTable> schemas) {
treeLock.lock();
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
root.removeAllChildren();
for (SchemaTable schemaTable : schemas) {
DefaultMutableTreeNode schemaNode = new DefaultMutableTreeNode(
schemaTable.getSchema());
for (String table : schemaTable.getTables()) {
schemaNode.add(new DefaultMutableTreeNode(table));
}
root.add(schemaNode);
}
model.reload();
}
講解
上頁中,setupComponents函數是Swing主線程執行的,而refreshTree函數是另外的線程執行(初始化時程序開辟一個線程執行,其后執行由用戶操作決定)。 refreshTree函數必須要等setupComponents函數把tree初始化完畢后才能執行,而tree初始化的時間較長,可能在初始化的過程中執行refreshTree的線程就進入了,這就會造成問題。
程序使用了一個SwingComponentLock來解決這個問題,setupComponents一開始就創建SwingComponentLock的實例treeLock,然后執行refreshTree的線程以來就會進入treeLock的wait set,變成等待狀態,不會往下執行,這是不管tree是否初始化完畢都不會出錯;而setupComponents執行到底部會激活treeLock的wait set中等待的線程,這時再執行refreshTree剩下的代碼就不會有任何問題,因為setupComponents執行完畢tree已經初始化好了。
讓線程等待和激活線程的代碼都在SwingComponentLock類中,這樣的封裝對復用很有好處,如果其它復雜組件如table也要依此辦理直接創建SwingComponentLock類的實例就可以了。如果把wait和notifyAll寫在TreeViewPanel類中就不會這樣方便了。
總結
線程鎖用于必須以固定順序執行的多個線程的調度。
線程鎖的思想是先鎖定后序線程,然后讓線序線程完成任務再接觸對后序線程的鎖定。
線程鎖的寫法和使用一定要理解記憶下來。