分享

大数据面试之1:多线程锁有哪几种

desehawk 发表于 2018-4-25 16:23:00 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 2 16650
本帖最后由 desehawk 于 2018-4-25 16:27 编辑

问题导读

1.什么是线程锁?
2.如何使用线程锁?
3.多线程锁有哪几种?




多线程,可能一些人还是比较陌生的,锁也是比较陌生。所以这里补充了关于线程锁的知识,在看线程锁种类就明白多了。如果对线程比较熟悉可以越过补充知识


补充知识:

1.什么是线程锁
首先我们明白什么是线程锁:
  线程锁:主要用来给方法、代码块加锁。当某个方法或者代码块使用锁时,那么在同一时刻至多仅有有一个线程在执行该段代码。当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段。但是,其余线程是可以访问该对象中的非加锁代码块的。


2.线程锁的作用和使用

线程锁的写法.
以线程锁的例子来理解线程的调度。使用线程锁的场合程序中经常采用多线程处理,这可以充分利用系统资源,缩短程序响应时间,改善用户体验;如果程序中只使用单线程,那么程序的速度和响应无疑会大打折扣。

但是,程序采用了多线程后,你就必须认真考虑线程调度的问题,如果调度不当,要么造成程序出错,要么造成荒谬的结果。一个讽刺僵化体制的笑话前苏联某官员去视察植树造林的情况,现场他看到一个人在远处挖坑,其后不远另一个人在把刚挖出的坑逐个填上,官员很费解于是询问陪同人员,当地管理人员说“负责种树的人今天病了”。

上面这个笑话如果发生在程序中就是线程调度的问题,种树这个任务有三个线程:挖坑线程,种树线程和填坑线程,后面的线程必须等前一个线程完成才能进行,而不是按时间顺序来进行,否则一旦一个线程出错就会出现上面荒谬的结果。用线程锁来处理两个线程先后执行的情况在程序中,和种树一样,很多任务也必须以确定的先后秩序执行,对于两个线程必须以先后秩序执行的情况,我们可以用线程锁来处理。

线程锁的大致思想是:如果线程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类中就不会这样方便了。总结线程锁用于必须以固定顺序执行的多个线程的调度。

线程锁的思想是先锁定后序线程,然后让线序线程完成任务再解除对后序线程的锁定。


补充完毕.

###################################
下面我们开始正题

多线程种类:

  • NSLock
    • NSLock是Cocoa提供给我们最基本的锁对象,这也是我们经常使用的,除lock和unlock外,NSLock还提供了tryLock和lockBeforeDate:两个方法,前一个方法会尝试加锁,如果锁不可用(已经被锁住),并不会阻塞线程,直接返回NO。后一个方法则会在指定的Date之前尝试加锁,如果在指定的时间内都不能加锁,则返回NO
  • synchronized(互斥锁)
    • synchronized会创建一个异常捕获handler和一些内部的锁,所以使用@synchronized替换普通锁的代价是要付出更多的时间消耗
    • 创建给给@synchronized指令的对象是一个用来区别保护块的唯一标识符。如果你在两个不同的线程里面执行上述方法,每次在一个线程传递了一个不同的对象给anObj参数,那么每次都将会拥有它的锁,并持续处理,中间不会被其他线程阻塞。然而如果你传递的是同一个对象,那么多个线程中的一个线程会首先获得该锁,而其他线程将会被阻塞直到第一个线程完成它的临界区
    • 作为一个预防措施。@synchronized块隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁,这就意味着为了使用@synchronized指令,你必须在你的代码中启用异常处理。如果你不想让隐式的异常处理例程带来额外的开销,那么可以使用其他的锁
  • atomic
    • atomic只是给成员变量的set和get方法加了一个锁,防止多线程一直去读写这个成员变量。但这也仅仅是对读写的锁定,并不是线程安全。而且使用atomic比nonatomic慢了将近20倍
  • OSSpinlock
    • 自旋锁
    • 耗时最少
    • 自旋锁几乎不进入内核,仅仅是重新加载自旋锁
    • 如果自旋锁被占用时间在一百纳秒以内,性能还是比较高的,因为减少了代价较高的系统调用和一系列的上下文切换
    • 但是该锁不是万能的,如果该锁占用的时间比较多的时候,使用该锁会导致占用的cpu较多
  • pthread_mutex
    • 是底层的API,在各种加锁方式中属于性能比较高的
    • 如果自旋锁占用的时间比较多,那么使用pthread是一个不错的选择
  • NSConditionLock(条件锁)
    • 条件锁与特定的与用户定义的条件有关,它可以确保一个线程可以获取满足一定条件的锁
    • 内部涉及到信号量机制,一旦一个线程获取锁以后,它可以放弃锁并设置相关条件,这时候其他锁竞争该锁
    • 线程之间的竞争激烈,涉及到条件锁检测、线程间通信、系统调用,上下文切换比较频繁
  • NSRecursiveLock递归锁
    • NSRecursiveLock实际上定义的是一个递归锁,这个锁可以被同一线程多次请求,而不会引起死锁。这主要是用在循环或者递归操作中
  • 总结:
    • 如果只是粗略的使用锁,不考虑性能可以使用synchronized
    • 如果对效率有较高的要求,采用OSSpinLock
    • 因为pthread的锁也是使用OSSpinLock实现的,而且在OSSpinLock的实现过程中,并没有进入系统kernel,使用OSSpinLock可以节省系统调用和上下文切换
    • NSLock/NSConditionLock/NSRecursive耗时接近。220ms左右
    • dispatch_barrier_async的性能并没有我们想象中的纳闷好,这与线程同步调度开销有关


来自:
https://blog.csdn.net/qq_35247219/article/details/51930849


以上问题来自about云铁粉微信群分享,加微信w3aboutyun,附上帖子名称,可以进入铁粉微信群。后续不断更新

已有(2)人评论

跳转到指定楼层
奋斗靠自己 发表于 2018-11-4 23:07:53
mmmmmmmmmmm
回复

使用道具 举报

奋斗靠自己 发表于 2018-11-4 23:09:30
不错值得分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条