转载:总结java中使用多线程锁的使用 、实用

来源:互联网 发布:设备安全管理网络 编辑:程序博客网 时间:2024/05/13 17:56
线程锁的使用


本文内容


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


使用线程锁的场合


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


一个讽刺僵化体制的笑话


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


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


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


总结


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