zoukankan      html  css  js  c++  java
  • 线程(六)之Lock和Synchronized

    在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReentrantLock、ReadWriteLock(实现类ReentrantReadWriteLock),其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类,实现思路都大同小异,因此我们以ReentrantLock作为讲解切入点。

    ReentrantLock的调用过程

    ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,该类继承了AbstractQueuedSynchronizer:

    abstract static class Sync extends AbstractQueuedSynchronizer

    Sync又有两个子类:

    final static class NonfairSync extends Sync 
    final static class FairSync extends Sync
    

    显然是为了支持公平锁和非公平锁而定义,默认情况下为非公平锁。 

    先理一下Reentrant.lock()方法的调用过程(默认非公平锁): 

    锁实现(加锁)

    简单说来,AbstractQueuedSynchronizer会把所有的请求线程构成一个CLH队列,当一个线程执行完毕(lock.unlock())时会激活自己的后继节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态,经过调查线程的显式阻塞是通过调用LockSupport.park()完成,而LockSupport.park()则调用sun.misc.Unsafe.park()本地方法,再进一步,HotSpot在Linux中中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。  

    CLH锁即Craig, Landin, and Hagersten (CLH) locks,CLH锁是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。

    CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。

     

    与synchronized相同的是,这也是一个虚拟队列,不存在队列实例,仅存在节点之间的前后关系。令人疑惑的是为什么采用CLH队列呢?原生的CLH队列是用于自旋锁,但Doug Lea把其改造为阻塞锁。 
    当有线程竞争锁时,该线程会首先尝试获得锁,这对于那些已经在队列中排队的线程来说显得不公平,这也是非公平锁的由来,与synchronized实现类似,这样会极大提高吞吐量。 
    如果已经存在Running线程,则新的竞争线程会被追加到队尾,具体是采用基于CAS的Lock-Free算法,因为线程并发对Tail调用CAS可能会导致其他线程CAS失败,解决办法是循环CAS直至成功。

    Sync.nonfairTryAcquire

    final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                    if (compareAndSetState(0, acquires)) {
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires;
                    if (nextc < 0) // overflow
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }

    该方法会首先判断当前状态,如果c==0说明没有线程正在竞争该锁,如果不c !=0 说明有线程正拥有了该锁。 
    如果发现c==0,则通过CAS设置该状态值为acquires,acquires的初始调用值为1,每次线程重入该锁都会+1,每次unlock都会-1,但为0时释放锁。如果CAS设置成功,则可以预计其他任何线程调用CAS都不会再成功,也就认为当前线程得到了该锁,也作为Running线程,很显然这个Running线程并未进入等待队列。 
    如果c !=0 但发现自己已经拥有锁,只是简单地++acquires,并修改status值,但因为没有竞争,所以通过setStatus修改,而非CAS

    公平锁和非公平锁 

    • tryAcquire 是一个抽象方法,是公平与非公平的实现原理所在。
    • addWaiter 是将当前线程结点加入等待队列之中。公平锁在锁释放后会严格按照等到队列去取后续值,而非公平锁在对于新晋线程有很大优势。
    • acquireQueued 在多次循环中尝试获取到锁或者将当前线程阻塞。
    • selfInterrupt 如果线程在阻塞期间发生了中断,调用 Thread.currentThread().interrupt() 中断当前线程。 

    公平锁和非公平锁在说的获取上都使用到了 volatile 关键字修饰的state字段, 这是保证多线程环境下锁的获取与否的核心。 

    但是当并发情况下多个线程都读取到 state == 0时,则必须用到CAS技术,一门CPU的原子锁技术,可通过CPU对共享变量加锁的形式,实现数据变更的原子操作。 
    volatile 和 CAS的结合是并发抢占的关键。 

    非公平锁:非公平锁在实现的时候多次强调随机抢占,与公平锁的区别在于新晋获取锁的进程会有多次机会去抢占锁。如果被加入了等待队列后则跟公平锁没有区别。 

        static final class NonfairSync extends Sync {
            private static final long serialVersionUID = 7316153563782823691L;
    
            /**
             * Performs lock.  Try immediate barge, backing up to normal
             * acquire on failure.
             */
            final void lock() {
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                    acquire(1);
            }
          //
    • tryAcquire 是一个抽象方法,是公平与非公平的实现原理所在。
            protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
            }
        }

    公平锁:公平锁的实现机理在于每次有线程来抢占锁的时候,都会检查一遍有没有等待队列,如果有, 当前线程会执行如下步骤:

     其中hasQueuedPredecessors是用于检查是否有等待队列的。

    if (!hasQueuedPredecessors() &&
        compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(current);
        return true;
    }
    

      

     static final class FairSync extends Sync {
            private static final long serialVersionUID = -3000897897090466540L;
    
            final void lock() {
                acquire(1);
            }
            protected final boolean tryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
    //其中hasQueuedPredecessors是用于检查是否有等待队列的。 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } }
     

      Lock和Synchronized 区别  

    1)synchronized是Java语言的关键字,因此是内置特性,Lock不是Java语言内置的,Lock是一个接口,通过实现类可以实现同步访问。

    2)synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

    3)在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。

    类别synchronizedLock
    存在层次 Java的关键字,在jvm层面上 是一个类
    锁的释放 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 在finally中必须释放锁,不然容易造成线程死锁
    锁的获取 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
    锁状态 无法判断 可以判断
    锁类型 可重入 不可中断 非公平 可重入 可判断 可公平(两者皆可)
    性能 少量同步 大量同步

    线程的状态

    BLOCKED状态

    线程处于BLOCKED状态的场景。

    • 当前线程在等待一个monitor lock,比如等待执行synchronized代码块或者使用synchronized标记的方法。
    • 在synchronized块中循环调用Object类型的wait方法,如下是样例
      synchronized(this)
      {
      while (flag)
      {
      obj.wait();
      }
      // some other code
      }

    WAITING状态

    线程处于WAITING状态的场景。

    • 调用Object对象的wait方法,但没有指定超时值。
    • 调用Thread对象的join方法,但没有指定超时值。
    • 调用LockSupport对象的park方法。

    提到WAITING状态,顺便提一下TIMED_WAITING状态的场景。

    TIMED_WAITING状态

    线程处于TIMED_WAITING状态的场景。

    • 调用Thread.sleep方法。
    • 调用Object对象的wait方法,指定超时值。
    • 调用Thread对象的join方法,指定超时值。
    • 调用LockSupport对象的parkNanos方法。
    • 调用LockSupport对象的parkUntil方法。

    https://blog.csdn.net/bingjing12345/article/details/17789613

    https://blog.csdn.net/Luxia_24/article/details/52403033

  • 相关阅读:
    2018年春季个人阅读计划
    软件需求与分析读后感
    假期读后感3
    假期读后感2
    假期读后感1
    四则运算2
    软件工程概论第一次作业
    《大道至简》读后感
    HMX-Server C++ 分步式服务器大版本更新了(有源码)
    HMX-Server-分步式服务器框架(开源+源码)
  • 原文地址:https://www.cnblogs.com/fanBlog/p/9634553.html
Copyright © 2011-2022 走看看