和风细雨

世上本无难事,心以为难,斯乃真难。苟不存一难之见于心,则运用之术自出。

线程锁的使用

本文内容

何时该使用线程锁.
线程锁的写法.
以线程锁的例子来理解线程的调度。

使用线程锁的场合

程序中经常采用多线程处理,这可以充分利用系统资源,缩短程序响应时间,改善用户体验;如果程序中只使用单线程,那么程序的速度和响应无疑会大打折扣。
但是,程序采用了多线程后,你就必须认真考虑线程调度的问题,如果调度不当,要么造成程序出错,要么造成荒谬的结果。

一个讽刺僵化体制的笑话

前苏联某官员去视察植树造林的情况,现场他看到一个人在远处挖坑,其后不远另一个人在把刚挖出的坑逐个填上,官员很费解于是询问陪同人员,当地管理人员说“负责种树的人今天病了”。
上面这个笑话如果发生在程序中就是线程调度的问题,种树这个任务有三个线程:挖坑线程,种树线程和填坑线程,后面的线程必须等前一个线程完成才能进行,而不是按时间顺序来进行,否则一旦一个线程出错就会出现上面荒谬的结果。

用线程锁来处理两个线程先后执行的情况

在程序中,和种树一样,很多任务也必须以确定的先后秩序执行,对于两个线程必须以先后秩序执行的情况,我们可以用线程锁来处理。
线程锁的大致思想是:如果线程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类中就不会这样方便了。

总结

线程锁用于必须以固定顺序执行的多个线程的调度。
线程锁的思想是先锁定后序线程,然后让线序线程完成任务再接触对后序线程的锁定。
线程锁的写法和使用一定要理解记忆下来。

 

posted on 2008-02-22 14:20 和风细雨 阅读(4538) 评论(0)  编辑  收藏 所属分类: 线程


只有注册用户登录后才能发表评论。


网站导航: