在看第三条路线 acquireQueued
主要会有两个分支判断,首先会进行无限循环中,循环中每次都会判断给定当前节点的先驱节点,如果没有先驱节点会直接抛出空指针异常,直到返回 true。
然后判断给定节点的先驱节点是不是头节点,并且当前节点能否获取独占式锁,如果是头节点并且成功获取独占锁后,队列头指针用指向当前节点,然后释放前驱节点。如果没有获取到独占锁,就会进入 shouldParkAfterFailedAcquire
和 parkAndCheckInterrupt
方法中,我们贴出这两个方法的源码
shouldParkAfterFailedAcquire
方法主要逻辑是使用compareAndSetWaitStatus(pred, ws, Node.SIGNAL)
使用CAS将节点状态由 INITIAL 设置成 SIGNAL,表示当前线程阻塞。当 compareAndSetWaitStatus 设置失败则说明 shouldParkAfterFailedAcquire 方法返回 false,然后会在 acquireQueued 方法中死循环中会继续重试,直至compareAndSetWaitStatus 设置节点状态位为 SIGNAL 时 shouldParkAfterFailedAcquire 返回 true 时才会执行方法 parkAndCheckInterrupt 方法。(这块在后面研究 AQS 会细讲)
parkAndCheckInterrupt
该方法的关键是会调用 LookSupport.park 方法(关于LookSupport会在以后的文章进行讨论),该方法是用来阻塞当前线程。
所以 acquireQueued 主要做了两件事情:如果当前节点的前驱节点是头节点,并且能够获取独占锁,那么当前线程能够获得锁该方法执行结束退出
如果获取锁失败的话,先将节点状态设置成 SIGNAL,然后调用 LookSupport.park
方法使得当前线程阻塞。
如果 !tryAcquire
和 acquireQueued
都为 true 的话,则打断当前线程。
那么它们的主要流程如下(注:只是加锁流程,并不是 lock 所有流程)
非公平锁的加锁(lock)流程详解
非公平锁的加锁步骤和公平锁的步骤只有两处不同,一处是非公平锁在加锁前会直接使用 CAS 操作设置同步状态,如果设置成功,就会把当前线程设置为偏向锁的线程;一处是 CAS 操作失败执行 tryAcquire
方法,读取线程同步状态,如果未加锁会使用 CAS 再次进行加锁,不会等待 hasQueuedPredecessors
方法的执行,达到只要线程释放锁就会加锁的目的。下面通过源码和流程图来详细理解
这是非公平锁和公平锁不同的两处地方,下面是非公平锁的加锁流程图
lockInterruptibly 以可中断的方式获取锁
以下是 JavaDoc 官方解释:
lockInterruptibly 的中文意思为如果没有被打断,则获取锁。如果没有其他线程持有该锁,则获取该锁并立即返回,将锁保持计数设置为1。如果当前线程已经持有锁,那么此方法会立刻返回并且持有锁的数量会 + 1。如果锁是由另一个线程持有的,则出于线程调度目的,当前线程将被禁用,并处于休眠状态,直到发生以下两种情况之一
- 锁被当前线程持有
- 一些其他线程打断了当前线程
如果当前线程获取了锁,则锁保持计数将设置为1。
如果当前线程发生了如下情况:
- 在进入此方法时设置了其中断状态
- 当获取锁的时候发生了中断(Thread.interrupt)
那么当前线程就会抛出InterruptedException
并且当前线程的中断状态会清除。
下面看一下它的源码是怎么写的
首先会调用 acquireInterruptibly
这个方法,判断当前线程是否被中断,如果中断抛出异常,没有中断则判断公平锁/非公平锁
是否已经获取锁,如果没有获取锁(tryAcquire 返回 false)则调用 doAcquireInterruptibly
方法,这个方法和 acquireQueued 方法没什么区别,就是线程在等待状态的过程中,如果线程被中断,线程会抛出异常。
下面是它的流程图
tryLock 尝试加锁
仅仅当其他线程没有获取这把锁的时候获取这把锁,tryLock 的源代码和非公平锁的加锁流程基本一致,它的源代码如下
tryLock 超时获取锁
ReentrantLock
除了能以中断的方式去获取锁,还可以以超时等待的方式去获取锁,所谓超时等待就是线程如果在超时时间内没有获取到锁,那么就会返回false
,而不是一直死循环获取。可以使用 tryLock 和 tryLock(timeout, unit)) 结合起来实现公平锁,像这样
if (lock.tryLock() || lock.tryLock(timeout, unit)) {...}
如果超过了指定时间,则返回值为 false。如果时间小于或者等于零,则该方法根本不会等待。
它的源码如下
首先需要了解一下 TimeUnit
工具类,TimeUnit 表示给定粒度单位的持续时间,并且提供了一些用于时分秒跨单位转换的方法,通过使用这些方法进行定时和延迟操作。
toNanos
用于把 long 型表示的时间转换成为纳秒,然后判断线程是否被打断,如果没有打断,则以公平锁/非公平锁
的方式获取锁,如果能够获取返回true,获取失败则调用doAcquireNanos
方法使用超时等待的方式获取锁。在超时等待获取锁的过程中,如果等待时间大于应等待时间,或者应等待时间设置不合理的话,返回 false。
这里面以超时的方式获取锁也可以画一张流程图如下