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 else 10 //失败后竞争锁 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) // overflow 17 throw new Error("Maximum lock count exceeded"); 18 setState(nextc); 19 return true; 20 } 21 //否则返回false 22 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 释放成功返回true 13 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 //设置独占线程为null 12 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 order 5 Node h = head; 6 Node s; 7 return h != t && 8 ((s = h.next) == null || s.thread != Thread.currentThread()); 9 }
释放节点就不分析了 差不多