(十)JDK源码分析之ReentrantLock流程总结

  • 建议
    希望读者打开源码对照看,或者有AQS源码基础存在一些疑惑的同学看,一定能有所收获

  • lock
    1.调用AQS的acquire方法,先tryAcquire方法尝试获取锁根据status的判断。如果获取锁成功,那么直接返回结束;如果尝试获取失败,那么需要将当前线程封装为Node对象,加入到同步队列中。

    2.如果尝试获取锁失败,那么在acquire继续调用addWaiter方法将当前线程封装为Node对象,加入到同步队列中,如果同步队列头结点没有初始化,那么先cas初始化头结点,然后再cas将当前创建的Node加入到尾节点后面。

    3.将上面未获取到的Node添加到阻塞队列后,在acquire方法中,会继续调用acquireQueued方法进行轮训获取锁,开始自旋(死循环)。1.自旋尝试获取锁是再次调用tryAcquire方法,但是尝试获取锁是有条件的,必须当前Node的前面的Node是头结点,才会去tryAcquire,否则是在浪费,因为你前面Node都没获取到锁,你怎么可能获取的到呢。2.自旋最多一次执行两次,如果第二次自旋还是没获取到锁,那么当前线程被挂起(LockSupport.park),一直阻塞,直到被唤醒。

    4.被唤醒时,其实老头结点还在(注意,注意,注意)。被唤醒的节点,在获取到了锁之后,才会真正把老头结点释放掉,然后将自己设置为新的头结点。

  • unlock
    1.调用release开始释放锁,调用tryRelease修改status值。比如将status值减一,将exclusiveOwnerThread也就是当前AQS中持有锁的线程置为null.
    2.在release方法中会调用unparkSuccessor方法进行唤醒(LockSupport.unpark)他的下一个节点,让他从挂起的状态中唤醒,再次自旋去获取锁。

  • 总结
    1.大概流程就是一个线程去获取锁,如果获取成功,那么结束,如果获取失败,那么加在同步队列中,然后自旋两次,直到前驱节点释放锁唤醒后继节点,被唤醒后继续去获取锁。

    2 在实现是有公平锁和非公平锁之分,这个在tryAcquire中有体现。公平锁,去尝试获取锁会先调用hasQueuedPredecessors,判断同步队列中是否有等待的Node,如果有,那么直接排队去,不参与锁的竞争;非公平锁,就是即使你是排队在同步队列中第一个,那么新加入的线程依然会和它竞争。也就是说在被前驱节点唤醒的线程,开始再次自旋获取锁时,公平锁时,是一定可以获取到锁的,非公平锁不一定能获取到,可能在竞争激烈情况下,造成锁获取饥饿。

    3由于可重入锁的特点,status的值可能会大于1,同一个线程可以获取多次锁,每次加1,也就是说一个线程,你可以调用多次lock,但是同时几点对应调用多次unlock,因为只要status>0,那么还是那个线程持有锁

    4.头结点唤醒后继节点的一些细节需要注意一下。对于公平锁,在头结点释放锁之后,后继节点可以马上获取到锁,将自己设置为新的头结点;对于非公平锁,在头结点释放锁,虽然唤醒了后继节点,但是有新进来(非同步队列中)的线程可能会抢到锁,导致后继节点获取锁失败,再次被挂起,当新进来的线程释放锁时,会使用头结点再次唤醒它的后继节点(新进来的队列直接就获取到锁了,没在同步队列中,所以不需要去做acquireQueued的这段逻辑,只要在释放锁时,再次唤醒头结点的后继节点即可)。

    5.公平锁和非公平锁的区别只是在入队列前头结点的下一个节点(新头结点)会和非同步队列的线程发生竞争,入队列后是一样的。

    6.纠正一句话,“头结点是持有锁的节点”。公平锁时,这句话没啥问题,非公平锁时,持有锁的节点可能是头结点,也有可能是入队前竞争的那个线程抢到了锁。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章