1、个人总结和看法:
(1)、AQS和ReentrantLock的关系?
ReentrantLock是基于AQS的实现的,昨天我们说了AQS的tryAcquire()是默认抛出异常的需要子类去重写逻辑,ReentrantLock就重写了tryAcquire()。这样就解释了之前的疑问,因为这本来就是留给子类自己去完成的逻辑。
(2)、ReentrantLock的锁模式?
默认是非公平获取锁,不过可以在构造是设置公平锁模式获取。
(3)、为什么ReentrantLock的锁模式默认为非公平锁?
我的理解是当在竞争锁时,如果为非公平锁模式,那么当有新开启的线程在和队列中的线程竞争时,新开启的线程会更容易获取锁,因为队列唤醒需要更多操作,这样的避免了入队和队列唤醒的操作开销,节约了很多的性能,并发原理分析始终要从安全性和性能考虑。
2、内部结构解析:
(1)、ReentrantLock有一个内部抽象类 Sync,有一个lock抽象方法,它继承了AQS也就解释了ReentrantLock和AQS的关系。还有两个抽象类FairSync和NonfairSync 他们都继承了Sync,重写了lock方法,分别为公平和非公平获取锁的实际处理逻辑。
(2)、构造方法
1 public ReentrantLock() {2 //默认为非公平获取锁3 sync = new NonfairSync();4 }
1 public ReentrantLock(boolean fair) {2 //也可以传入参数为true就表示公平锁3 sync = fair ? new FairSync() : new NonfairSync();4 }
3、重点方法源码分析:
(1)、非公平锁获取
1 static final class NonfairSync extends Sync { 2 private static final long serialVersionUID = 7316153563782823691L; 3 4 final void lock() { 5 //首先设置状态位 6 if (compareAndSetState(0, 1)) 7 //如果设置状态位成功 就设置独占线程 8 setExclusiveOwnerThread(Thread.currentThread()); 9 else10 //失败后竞争锁11 acquire(1);12 }13 14 protected final boolean tryAcquire(int acquires) {15 //非公平锁获取锁的实际逻辑16 return nonfairTryAcquire(acquires);17 }18 }
总结:所以你能看到其实ReentrantLock当设置独占线程失败后是直接调用的AQS中的方法,进行入队和其他操作。这里应该将ReentrantLock和AQS的关系体现的比较明显了,重点是看ReentrantLock重写的tryAcquire方法。
acquire() 源码分析:
1 public final void acquire(int arg) {2 //这里我们昨天分析过了 就是AQS的方法 3 if (!tryAcquire(arg) &&4 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))5 selfInterrupt();6 }
这里其实就是AQS的方法。
nofairTryAcquire()方法源码分析:
1 final boolean nonfairTryAcquire(int acquires) { 2 //获取当前线程 3 final Thread current = Thread.currentThread(); 4 //获取状态位 5 int c = getState(); 6 if (c == 0) { 7 //如果没有被设置 就设置 并设置独占线程为自己 8 if (compareAndSetState(0, acquires)) { 9 setExclusiveOwnerThread(current);10 return true;11 }12 }13 //如果设置了 就检查独占线程是不是自己 是自己的话那就再次获取 这就是可重入锁14 else if (current == getExclusiveOwnerThread()) {15 int nextc = c + acquires;16 if (nextc < 0) // overflow17 throw new Error("Maximum lock count exceeded");18 setState(nextc);19 return true;20 }21 //否则返回false22 return false;23 }
这里我们的非公平方式获取锁的逻辑就明白了 所以调用流程其实是(假设存在竞争,并且失败,因为这个流程比较复杂,其他相对简单)lock()、acquire()、tryAcquire()、nofairTryAcquire()、addWaiter()(其他是AQS参看AQS个人分析)
所以ReentrantLock其实是在AQS的基础上完成的,自己也就是设置独占线程这种简单操作,当遇到竞争时操作都是AQS的逻辑完成。
(2)、非公平锁的释放
释放相对于简单一些,昨天我们并没有分析AQS的释放 所以我们现在分析一下,直接上源码
我还是喜欢根据逻辑来分析 这样也方便大家分析
unLock()源码分析:
1 public void unlock() {2 //实际上是Sync的释放 再向下转型到非公平锁3 sync.release(1);4 }
release()源码分析:
1 //这是AQS中的方法了 2 public final boolean release(int arg) { 3 4 //尝试释放 我们应该能猜到tryRelease是子类实现的 5 if (tryRelease(arg)) { 6 //获取头节点 7 Node h = head; 8 //如果不为空且不为等待状态 9 if (h != null && h.waitStatus != 0)10 //释放头节点11 unparkSuccessor(h);12 释放成功返回true13 return true;14 }15 return false;16 }
tryRelease()分析:
1 protected final boolean tryRelease(int releases) { 2 //获取状态位 3 int c = getState() - releases; 4 //如果当前线程不是持有锁的线程就抛出异常 5 if (Thread.currentThread() != getExclusiveOwnerThread()) 6 throw new IllegalMonitorStateException(); 7 boolean free = false; 8 //如果状态位为0 则表示已经成功的释放了锁 9 if (c == 0) {10 free = true;11 //设置独占线程为null12 setExclusiveOwnerThread(null);13 }14 //否则返回当前状态位 15 setState(c);16 return free;17 }
其实ReentrantLock是通过一个volatile的state来表示当前所的状态,其实就是内部规定的机制而已,这就是抽象意义的锁。就是所有权。
(3)、公平锁获取:
因为和非公平锁的性质类似 我们主要分析不同的地方,直接上源码吧。
1 static final class FairSync extends Sync { 2 private static final long serialVersionUID = -3000897897090466540L; 3 4 final void lock() { 5 //重写了lock方法 6 acquire(1); 7 } 8 9 10 protected final boolean tryAcquire(int acquires) {11 //获取当前线程12 final Thread current = Thread.currentThread();13 int c = getState();14 if (c == 0) {15 //这里和非公平不同的是,它就算知道锁没有被占有,还是会去检查自己前面还有没有等待的线程 公平锁嘛 必须要前面没有线程等待了 才会去获取锁16 if (!hasQueuedPredecessors() &&17 compareAndSetState(0, acquires)) {18 setExclusiveOwnerThread(current);19 return true;20 }21 }22 else if (current == getExclusiveOwnerThread()) {23 int nextc = c + acquires;24 if (nextc < 0)25 throw new Error("Maximum lock count exceeded");26 setState(nextc);27 return true;28 }29 return false;30 }31 }
请注意我在代码注释里面说的和非公平锁的不同点 这是重点
我们看看hasQueueedPredecessors()的源代码吧:
1 public final boolean hasQueuedPredecessors() {2 //从代码里面很容易看到逻辑是3 //头节点初始化了 并且当前节点是第一节点4 Node t = tail; // Read fields in reverse initialization order5 Node h = head;6 Node s;7 return h != t &&8 ((s = h.next) == null || s.thread != Thread.currentThread());9 }
释放节点就不分析了 差不多