線程鎖

本文內容

何時該使用線程鎖.
線程鎖的寫法.
以線程鎖的例子來理解線程的調度。

使用線程鎖的場合

程序中經常採用多線程處理,這可以充分利用系統資源,縮短程序響應時間,改善用戶體驗;如果程序中只使用單線程,那麼程序的速度和響應無疑會大打折扣。
但是,程序採用了多線程後,你就必須認真考慮線程調度的問題,如果調度不當,要麼造成程序出錯,要麼造成荒謬的結果。

一個諷刺僵化體制的笑話

前蘇聯某官員去視察植樹造林的情況,現場他看到一個人在遠處挖坑,其後不遠另一個人在把剛挖出的坑逐個填上,官員很費解於是詢問陪同人員,當地管理人員說“負責種樹的人今天病了”。
上面這個笑話如果發生在程序中就是線程調度的問題,種樹這個任務有三個線程:挖坑線程,種樹線程和填坑線程,後面的線程必須等前一個線程完成才能進行,而不是按時間順序來進行,否則一旦一個線程出錯就會出現上面荒謬的結果。

用線程鎖來處理兩個線程先後執行的情況

在程序中,和種樹一樣,很多任務也必須以確定的先後秩序執行,對於兩個線程必須以先後秩序執行的情況,我們可以用線程鎖來處理。
線程鎖的大致思想是:如果線程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類中就不會這樣方便了。

總結

線程鎖用於必須以固定順序執行的多個線程的調度。
線程鎖的思想是先鎖定後序線程,然後讓線序線程完成任務再接觸對後序線程的鎖定。
線程鎖的寫法和使用一定要理解記憶下來。


轉載自:http://hi.baidu.com/linrw/blog/item/52e8c1da2d1a21d0b7fd486d.html


發佈了16 篇原創文章 · 獲贊 7 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章