请先看——————>AQS的简单说明
ReentrantLock底层是基于AQS实现的,在并发编程中可以实现公平锁和非公平锁来对同步资源进行控制,默认是非公平锁,并且是可重入锁。
1.ReentrantLock的体系关联结构
2.构造方法
可以发现ReentrantLock的非公平锁和公平锁实际是NonfairSync()和FairSync()这两个类来实现的。
3.非公平锁和公平锁的实际区别
因为sync中的lock()方法是抽象方法,所以NonfairSync和FairSync类里面会重写lock()方法。
发现在调用lock方法的唯一区别就是,非公平锁会先尝试CAS去修改状态来抢占锁,如果修改成功,则抢占锁成功并返回,如果没有修改成功,则调用acquire(1)方法。而公平锁直接是调用acquire(1)方法。
4.非公平锁的实现
4.1tryAcquire()方法
进入到acquire方法后,先调用tryAcquire(1)方法............
发现此方法内还是要进行尝试抢占锁,如果此时正好state状态为0了,那么CAS进行尝试修改状态,如果成功直接返回。还会判断正在占用锁的线程和当前线程是不是同一个,如果是,则证明是要重入锁,也返回true,如果这两个都没有成功,则返回false。
4.2公平锁和非公平锁在tryAcquire方法中的区别
公平锁的tryAcquire()方法中,就算此刻状态变为0了,不会直接CAS,而是会先判断FIFO队列中是否有等待的线程,如果有,那么就不进行CAS操作了,因为要保证公平!
4.3addwaiter()方法
需要注意的是,在双向链表中,第一个结点为虚结点(也叫做哨兵结点),只是占位用的,所以真正有用的数据结点是从第二个结点开始的。
进入到addwaiter()方法,当双向链表中尾指针为null时,需要进行初始化,创建虚结点并将封装线程的Node结点入队。当尾指针不为null时,将封装线程的Node结点入队,并将尾指针指向Node结点。
执行完毕后返回加入的Node结点。
4.4acquireQueued()方法
我们将Node入队之后,执行acquireQueued方法,先拿到Node的前驱结点,判断是否为虚拟头结点,如果是,那么直接尝试tryAcquire()去获取锁,如果获取到了,将虚拟头结点指向Node,原来的虚拟头结点指向null,等待GC回收即可,但是如果Node的前驱结点不是虚拟头结点或没获取到锁,那么修改其前驱结点的状态直到为SIGNAL,前驱结点的状态修改完后,然后利用LockSuppor.park()将当前Node进行阻塞。
那现在我们的好奇心飙升了,加入的Node将它的前驱结点状态修改了,然后自己又阻塞了,那么Node什么时候唤醒unPark呢?一个线程占用锁后,调用了unlock()方法解锁,那么unlock()方法有什么猫腻呢?
4.5unlock()方法
4.6unlock后发生的一些事情
一个线程释放锁后,会唤醒虚拟头结点后的第一个结点,唤醒之后,还在acquireQueued()方法中,然后进行for循环,因为此时锁此时已经释放了,此时唤醒的Node的前驱就是虚拟头结点,调用tryAcquire()方法去获取锁,获取到后将虚拟头结点指向Node,原来的虚拟头结点指向null,等待GC回收即可。队列中一直后面的线程Node就还处于阻塞的状态。