java并发-ReentrantLock源码分析

来源:互联网 发布:java heap size 编辑:程序博客网 时间:2024/05/29 19:49
1关于可重入锁

       ReentrantLock是基于AQS(AbstratcQueuedSynchronizer)实现的可重入的同步工具类,它提供了两种同步器的实现即公平锁FairSync和非公平锁NonfairSync。它提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有的加锁和解锁都是显式的。tryLock()tryLock(long ,TimeUnit)分别提供了可轮询的、可定时的锁获取方式。 Lock()提供了无条件地轮询获取锁的方式。lockInterruptibly()提供了可中断的锁获取方式。 ReentrantLock它提供了两种锁机制,默认创建的是非公平锁。AQS的类结构图如下:

                                       

      在公平锁上,线程是按照他们发出请求的顺序,如果锁被某个线程占用或者AQS队列中有等待线程,则该请求线程将进入队列排队直到获取锁。源码:

final void lock() {     acquire(1);}

     非公平锁在请求获取锁时,直接尝试CAS操作获取锁,如果尝试失败才进行排队。在竞争激烈的情况下,非公平锁的性能高于公平锁的一个原因是:活动线程直接尝试获取锁的操作时间可能要比恢复一个阻塞线,并把锁分配给它的时间短的多(线程的唤醒操作需要额外的时间,从唤起到线程真正运行之间存在着严重的时延)。

 final void lock() {            if (compareAndSetState(0, 1))              setExclusiveOwnerThread(Thread.currentThread());            else              acquire(1);}
2 NonfairSync锁

     非公平锁的获取流程,允许插队,直接尝试CAS操作,尝试失败时才执行入队。竞争激烈的环境中,非公平锁直接尝试锁定操作可以提高系统吞吐量。如果当前线程请求锁时,正好锁刚刚被释放,其他等待线程还没有被唤醒,在这过程中该线程完成了相关操作并释放锁,此时其他线程刚好被唤醒。利用线程唤醒的延时,其他线程能够完成操作,且不影响该阻塞线程唤醒后获取锁。

3 acquire(1)流程

     acquire的获取流程是:先尝试获取锁,如果失败,则将当前节点从队尾添加进入AQS队列等待。操作返回值为空。如果线程在等待过程时中断标识为真,则中断当前线程。

4 tryAcquire(1)流程

     tryAcquire(1)操作返回布尔类型,标识是否成功获取锁。如果空闲时CAS失败返回false,如果是当前线程持有锁,则说明是线程重入操作,将锁的重入次数累加,返回true.否则获取操作失败,返回false.

5 addWaiter入队流程

      addWaiter将当前线程封装成一个队列节点Node,以for循环的方式重复尝试将节点插入AQS等待队列,直到操作成功返回该NodeAQS维持了一个链表,headtail两个属性,初始时均为空。在添加第一个节点时会先创建一个虚拟的头节点(即new Node()没有任何信息的节点),并将tail指向head。新节点从队尾以CAS原子操作插入,插入操作在for循环中,以保证线程无法获取锁的时候一定会被添加到等待队列中。

6 acquireQueued排队线程获取锁流程

      获取失败的锁将会不断重试,直到线程被阻塞或者获取到锁为止。操作返回值是线程中断标识,如果在阻塞过程中被中断,则返回trueacquire操作,由其中断自己。为什么会有这种判断呢?我的理解:是为了保证线程阻塞过程中,中断信号不被淹没。如果在该线程被park后,其他线程向其发送了中断信号,则正常来说它是不能响应该请求的。这就是parkAndCheckInterrupt操作的必要,它在线程唤起时检查中断标志并通知该线程,由其去中断自己。(即acquireselefIntrupt()操作)。

7 shouldParkAfterFailedAcquire

        某个节点尝试获取锁操作,失败后,根据前驱节点的状态判断是否需要挂起该线程,该方法返回布尔类型值,标识当前节点是否需要被挂起。具体流程如下:

        至此,锁获取操作流程结束。ReentrantLocklock操作的结果是,要么线程被挂起,要么循环轮询获取锁直到成功设置状态为1(占用状态),然后被移除排队队列。某个等待线程只有在其前驱节点的等待状态为SIGNAL(即:前驱也等待某个信号)时才会被阻塞,其他情况下都处于循环重试的过程中。我认为这样根据前驱状态阻塞线程或者自旋重试,而不是直接挂起线程的处理很精妙,可以避免线程调度的资源消耗,毕竟,线程的挂起和唤醒是需要付出代价的。

8 unlock操作分析

      锁释放操作只有当前线程是锁的持有者时才能进行,否则会抛出IllegalMonitorStateException。尝试释放锁,如果是成功且等待队列非空,则唤醒它的后继节点。唤醒后继节点的过程中,只有当其后继节点非空且非取消时,调用LockSupportunpark唤醒,否则一直循环查找直到找到一个可唤起的后继节点为止。

0 0
原创粉丝点击