离上一篇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。