lock()方法
1.入口方法:ReentrantLock.lock()方法.
主要逻辑交给了内部类FairSync,和 NonfairSync两个类实现。
2.主要分析了公平锁。FairSync.lock()方法。
3.AbstracktQueuedSynchronizer.acquire()
分为两步:1) 尝试过去锁,如果为false,2) 将不能获取锁的线程,加入队列,并阻塞。
1).FairSync.tryAcquire(1)方法。
a)state=0,锁没有被占用,直接则进入if 判断,hasQueuePreecessors,tail==head 或者,tail!=head && (s=h.next)!=null && s.thead == Thread.currentThread();总是,是当前线程是第一个来获取锁的,没有其他线程比他早,就返回false。然后compareAndSetState将状态设为acquires(默认为1),先入队列,然后再比较,实现先入先出,公平锁。在并发获取锁的情况在,在tryAcquire(1),然后大量的线程返回false。再进addWaiter(Node)方法,这是队列就有了数据,这时hasQueuePredecessors()可以快速返回失败,减少更多直接操作内存的风险。
b)如果是当前线程再次获取锁,就将状态+1,这是在unlock的时候,释放锁-1相互匹配的,直到所有的锁都释放之后,才会让其他线程获得锁。所以,unLock(),必须要放在finally{}中,避免异常时,死锁。
另外,private volatile int state; 其中,volatile是ReentrantLock实现内存语义的关键,利用volatile的读写,在释放锁时,reentrantLock锁内的写操作,对后续获取锁的线程可见;
2)加入FIFO队列,先尝试加入到tail中,CAS将节点插入到末尾,然后将原来的tail节点的next只向当前节点。
否则进入循环,将节点插入到队列中,节点tail节点为null时,说面head和tail都是初始状态。先初始化 tail和head,然后,不断的比对tail节点,尝试将自己设置为tail,直到设置成功以后,才返回出来。
3)循环将队列中的取出来,比对是否是当前队列中最先入队的,否则再次阻塞等待锁的释放。
a)如果当前节点的前节点为head,那么尝试获得锁,将设置当前节点为head,之前的head节点,取消prev和next的引用(设置为null),帮助快速释放GC。
b)获取锁失败进入阻塞状态,shouldParkAfterFailedAcquire(Node pred,Node node),如果前节点状态为SIGNAL,则直接返回,去阻塞(状态分为:CANCELLED =1; SIGNAL= -1;CONDITION = -2;PROPAGATE = -3;默认为0,无状态)。Node节点有两种方式:1,状态控制,2模式控制( Node SHARED =new Node(); Node EXCLUSIVE =null)。公平锁使用模式控制,默认EXCLUSIVE,进入else,将前一个节点的状态设置为SIGNAL,返回false,再下次循环中,如果不能获得锁,则进入阻塞状态。(自旋的一个操作)
c) 让当前线程阻塞。如果线程重新被唤醒,检查线程是否中断,重置中断状态。如果已经被中断,返回true,如果还没被中断,设置为false,返回false。当线程中断时,设置中断标识为true,在selfInterrupt()中,中断此线程。线程不中断,则进入下一次循环,重新竞争锁。
unlock()方法
调用release方法,主要逻辑交给同步类的子类(公平锁和非公平锁),释放资源都由父类AbstractQueuedSynchronizer类来实现。
重点看一下tryRealease
a) 查看当前占用锁的线程是否为当前线程,否,则抛出异常。
是,则比较C 是否等于0,否则更新状态,状态最多为 Integer.MAX_VALUE,获取锁的最多次数为 Integer.MAX_VALUE。
b)状态为0时,head==null 说明有线程获取锁,还在初始状态,waitStatus==0,说明刚初始化,还没线程获得锁。除了这两种情况,去unpark。
唤醒阻塞的线程。
备注:能看懂和能跟别人讲的懂,还差一段距离,能讲的懂,和能复现还差这一段距离。