• JAVA并发之ReentrantLock源码(一)


      离上一篇AQS概述已经很久惹,期间也看了一点ReentrantLock、CountdownLantch等的源码,不过并没有看的很深入,也没有把我的理解都记录下来。今天简单的看过线程池之后,就准备对ReentrantLock做一个源码分析,来看看这个lock是怎么做到让多个线程同步的。本文主要是ReentrantLock源码层面的叙述,不会深入到AQS中已经构造好的方法。

    1.reentrantlock和sychronized对比:

      reentrantlock可重入,可以实现公平锁、非公平锁,可以生成多个condition, condition.await signal signAll,获取锁时可以设置超时时间tryLock(long timeout, TimeUnit unit)。

      sychronized 可重入,非公平锁,锁变量只能有一个,通过锁变量的wait notify notifyAll()通信。

      对于可重入锁,公平/非公平锁将会在下面的源码分析中做一个总结。在此不单独解释。

    2、Sync成员变量:

      ReentrantLock中就一个成员变量:sync,是继承AQS类实现的,同时有两个实现类,NonfairSync(非公平锁)、FairSync(公平锁)。

     1     private final Sync sync;
     2 
     3     abstract static class Sync extends AbstractQueuedSynchronizer{
     4         ...
     5     }
     6 
     7     static final class NonfairSync extends Sync {
     8       ...
     9     }
    10 
    11     static final class FairSync extends Sync {
    12       ...
    13     }

    3、主要方法:

      3.1 构造方法:

    1     public ReentrantLock() {
    2         sync = new NonfairSync();
    3     }
    4     public ReentrantLock(boolean fair) {
    5         sync = fair ? new FairSync() : new NonfairSync();
    6     }

      很简单的两个构造函数,默认的构造函数,如果需要创建公平锁可以传入一个boolean值,true代表构造公平锁。 

      3.2 加锁方法实现(lock.lock):

      由于锁有两种,因此lock方法的实现也有两种:

     1     //reentrantlock
     2     public void lock() {
     3         sync.lock();
     4     }
     5     //fairSync
     6     final void lock() {
     7         acquire(1);
     8     }
     9     //NonfairSync
    10     final void lock() {
    11         if (compareAndSetState(0, 1))//非公平锁先尝试获取资源(cas设置state为1)
    12             setExclusiveOwnerThread(Thread.currentThread());
    13         else
    14             acquire(1);
    15     }

      调用ReentrantLock的lock()方法其实是调用NonfairSync或fairSync中的lock方法,其中acquire()方法是AQS底层实现的,主要进行tryAcquire,如果try失败,则会把线程扔到CLH队列中“排队”等待。

      我们再来看看ReentrantLock中是怎么实现AQS需要子类实现的tryAcquire方法的吧:

     1     //fairSync
     2     protected final boolean tryAcquire(int acquires) {
     3         final Thread current = Thread.currentThread();
     4         int c = getState();
     5         if (c == 0) {
     6             if (!hasQueuedPredecessors() &&
     7                 compareAndSetState(0, acquires)) {//如果CLH队列中没有线程在等待并且尝试将资源state设置成1就将占有者设置成当前线程并返回成功
     8                 setExclusiveOwnerThread(current);
     9                 return true;
    10             }
    11         }
    12         else if (current == getExclusiveOwnerThread()) {//可重入判断,如果占有者是当前线程也会尝试成功
    13             int nextc = c + acquires;
    14             if (nextc < 0)
    15                 throw new Error("Maximum lock count exceeded");
    16             setState(nextc);
    17             return true;
    18         }
    19         return false;
    20     }
    21 
    22     //NonFairSync
    23     protected final boolean tryAcquire(int acquires) {
    24         return nonfairTryAcquire(acquires);//这边再套一个非公平尝试获取资源的目的是为了reentrantlock中的tryLock方法直接可以调用
    25     }
    26     final boolean nonfairTryAcquire(int acquires) {//大致和公平锁的实现一样
    27         final Thread current = Thread.currentThread();
    28         int c = getState();
    29         if (c == 0) {
    30             if (compareAndSetState(0, acquires)) {//这里是唯一不同的,非公平锁不会管是否有线程在排队中,直接尝试获取资源
    31                 setExclusiveOwnerThread(current);
    32                 return true;
    33             }
    34         }
    35         else if (current == getExclusiveOwnerThread()) {
    36             int nextc = c + acquires;
    37             if (nextc < 0) // overflow
    38                 throw new Error("Maximum lock count exceeded");
    39             setState(nextc);
    40             return true;
    41         }
    42         return false;
    43     }

      同样实现了两套tryAcquire,看完这两个方法,关于锁公平与非公平的如何实现的也基本清楚了:

      公平锁lock的实现就是直接获取资源(acquire),上一节中我们知道了acquire是AQS的一个重要方法,用于获取资源,如果获取不到就会进入CLH队列中“排队”。(注:对于公平锁获取资源时,如果有等待队列,当前线程会直接进入队列中“排队”)
      非公平锁呢,则是很“不客气”的先用当前线程企图去获取资源,打个不恰当的比方就像排队看病时,一个人大摇大摆的以为自己是全世界的中心(就好像cpu把时间片分配给某个线程),即时门外有一群人在排队,他也走到房间里面,如果医生刚好没在看病(资源可以被获取到),那么他就直接插队成功啦!如果医生再给别的病人看病呢,他就乖乖的到门外去排队等啦。非公平锁可以减少线程状态的切换,因为刚好时间片分给某个线程时,这个线程就不需要再排队阻塞了,可以直接运行。
      我们再看一个ReentrantLock与lock有关的方法:
    1     public boolean tryLock() {
    2         return sync.nonfairTryAcquire(1);
    3     }
    4 
    5     public boolean tryLock(long timeout, TimeUnit unit)
    6             throws InterruptedException {
    7         return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    8     }

      当使用者调用tryLock方法时,并不在意CLH队列是否有节点在等待,用户只关心能否获得资源,占有这把锁,因此不论公平还是非公平锁都是调用上文中的nonfairTryAcquire()方法,提及下面一个带超时时间的方法只是为了进一步说明文章开头ReentrantLock与sychronized的不同。

      3.3 释放锁实现(lock.unlock):

      unlock()方法其实比lock更简单,因为不论公平还是非公平,释放锁只需要把占领锁的证明给“清除”掉就好啦,具体可以看下面的代码:

     1     public void unlock() {
     2         sync.release(1);
     3     }
     4     //release低层会先调用tryRelease方法
     5     protected final boolean tryRelease(int releases) {
     6         int c = getState() - releases;
     7         if (Thread.currentThread() != getExclusiveOwnerThread())//你都不是拥有我的线程,凭什么释放我?我要报警啦!
     8             throw new IllegalMonitorStateException();
     9         boolean free = false;
    10         if (c == 0) {//判断是否资源都减完了,到0才说明重入的次数和释放的次数是一样的
    11             free = true;
    12             setExclusiveOwnerThread(null);
    13         }
    14         setState(c);
    15         return free;
    16     }

      在释放锁的时候不使用CAS修改state的原因大概是锁是独占的,不会有其他线程同时将state的状态改变掉。

      3.4 其余方法:

      其余的一些方法基本都是获取AQS底层的一些状态,比如获取锁的占有线程、获取CLH队列的长度等等,在此不再展开,因为底层都封装好了,在同步器中只需调用底层方法就好。

    4、小结:

      原以为很高深的ReentrantLock就这么简单的实现啦,主要还是AQS的功劳,减少了开发这样简便易用的同步工具的代码量。
      ReentrantLock的其余一些方法基本就是判断是否公平、判断当前队列是否有等待的线程等简单的方法,看方法名就很容易理解,代码也很简洁。就不在展开了。
      emmm,上一次开了一个JUC包学习的坑,叙述了AQS的一些设计,但是对于源码还没有详细的叙述,下一章应该会配合ReentrantLock用到的AQS方法,对AQS源码进行部分解析,希望不会太监吧QAQ。
  • 相关阅读:
    关于微服务的协议概述
    Eclipse 安装阿里巴巴代码规范插件
    Eclipse安装LomBok插件
    关于JAVA架构师
    关于Object类的一些方法
    [SoapUI] SoapUI官方文档
    [Jmeter] 用xsltproc生成html格式的报告
    [RF] Robot Framework新手干货(转载)
    【SoapUI】比较Json response
    怎样查看一个端口有无开启
  • 原文地址:https://www.cnblogs.com/zzzdp/p/9311483.html
Copyright © 2020-2023  润新知