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。
posted @ 2018-07-15 00:42  zhangdapao  阅读(164)  评论(0编辑  收藏  举报