ReentrantLock使用
首先是一个demo方法

执行结果

只有获得锁的线程释放锁以后,其他线程才能获取锁。
从lock.lock();这一行代码点进去进行debug,发现底层是非公平锁。

继续往下跟,非公平锁继承了Sync类,而Sync类又继承了AbstractQueuedSynchronizer类。


继续看上面非公平锁的lock方法,首先使用CAS将state值设为1,期望值是0,底层使用的unsafe类的compareAndSwapInt方法,是原子性的,这里的state继承了AQS类的变量,至于state变量具体含义,在下一节分析AQS的时候再具体说明,如果设置成功,说明抢占锁成功,执行 setExclusiveOwnerThread(Thread.currentThread()); 设置独占锁的线程为当前线程;如果设置state失败,说明抢占锁失败,下面具体分析AQS。
AQS源码分析
AQS是juc包下很多并发工具类的底层依赖类,是一个双向队列,每个节点是一个Node。
SHARED:表示共享锁,CountDownLatch、Semaphore、ReentrantReadWriteLock等使用的都是共享锁;
EXCLUSIVE:表示独占锁,ReentrantLock、ThreadPoolExecutor等使用的是独占锁;
接下来是节点的waitStatus值,表示节点代表的线程等待状态
CANCELLED:1,表示线程取消等待
SIGNAL:-1,表示前置节点需要被唤醒
CONDITION:-2,表示等待队列
PROPAGATE:-3,共享式状态

其他的一些属性:
volatile int waitStatus;//节点代表的线程等待状态,也就是上图中的几种
volatile Node prev;//前置节点
volatile Node next;//后置节点
volatile Thread thread;//节点代表的线程
Node nextWaiter;//等待队列节点
private transient volatile Node head;//同步队列头结点,表示获取锁的线程节点
private transient volatile Node tail;//同步队列尾结点
private volatile int state;//锁的重入次数
然后接着上面第一节ReentrantLock的lock方法,如果state等于0,表示当前没有线程获取锁,使用CAS设置state值为1,如果此时正好有其他线程也来抢占锁,只能有一个线程抢占成功,抢占成功的线程,将exclusiveOwnerThread设置为自己。
抢占锁失败的线程,会进入else,执行acquire(1);

线程1抢占锁
首先线程1过来抢占锁,进入tryAcquire(arg),这里arg参数为1,获取当前锁的状态state,如果等于0,表示锁没有被占用,接着就可以使用CAS将state设置为1,设置成功后,将独占锁的线程设置为自己,返回true。然后!tryAcquire(arg)就是false,&& 后面的不再执行。

线程2进来
此时线程1还没有释放锁,线程2进来了,同样先进入ReentrantLock的lock方法,CAS设置state为1失败,进入else执行acquire(1);最终进入上图的nonfairTryAcquire方法,判断state不等于0,进入else if判断当前获取锁的线程是否等于自己,结果也不是自己,就返回false,接着就会执行 && 后面的 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 方法,首先是内层的addWaiter(Node.EXCLUSIVE)方法。
创建一个Node,独占模式,代表当前线程,注意,此时同步队列是空的,当前获取锁的线程是线程1,获取同步队列尾结点,当然也是null,进入enq(node);

进来是一个死循环,第一次循环由于tail为null,CAS设置头节点为一个新创建的Node,这个Node是个空节点,不代表任何线程,等待状态也都是初始值0,然后将tail指向head。

第一次循环以后

接着第二次循环,tail不为null,进入else,将线程2代表的节点Node,假如到同步队列末尾,tail指向线程2节点Node,两个节点双向链表关联,然后就跳出循环,返回tail尾节点,如下图:

回到addWaiter方法,最后返回线程2节点Node,接下来看外层方法acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
进去又是一个死循环,第一次循环,获取尾节点的前置节点,如果前置节点是头节点,这里线程2节点是尾节点,前置节点就是头节点,所以满足if条件,接着执行tryAcquire(arg),这里调用的是ReentrantLock类的nonfairTryAcquire方法,这里再发一次,不用再往前翻

判断state不等于0,进入else if,当前获取锁的线程是线程1,所以返回false。

接着就到了if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())
首先先执行shouldParkAfterFailedAcquire(p, node),第一个参数是线程2节点的前置节点,即头节点,第二个参数是线程2节点,前置节点的waitStatus为默认值0,会进入else代码块,CAS将前置节点的waitStatus设置为-1,然后返回false,&& 后面的代码不再执行。这里需要注意一点,如果前置节点的waitStatus>0,即取消排队了,将进入do while循环,从后往前遍历同步队列,如果节点取消等待了,就将节点删除,prev链接到waitStatus<=0的节点,后面这些取消等待的节点,将被GC回收掉。

第一次循环后,如下图:

接着第二次循环,再次判断线程2节点的前置节点是头节点,tryAcquire失败,进入if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()),首先执行shouldParkAfterFailedAcquire方法,判断前置节点的waitStatus等于-1,直接返回true,接着就会执行 && 后面的parkAndCheckInterrupt(),将线程2挂起,设置中断状态为true。


线程3进来
至此,线程1抢占到了锁,线程2在同步队列中,被阻塞。此时线程3进来了,与线程2一样,判断state是否为1,是否重入,然后排到同步队列末尾,并将前置节点,即线程2节点的waitStatus设置为-1,最后阻塞挂起。

线程1释放锁
执行ReentrantLock的unlock方法,进入AQS的release方法

然后进入ReentrantLock的tryRelease方法
由于释放锁的时候,当前线程肯定已经获取到了锁,所以不存在竞争,不需要使用CAS,直接对state-1,如果获取锁的线程不是当前线程,抛异常,然后判断state-1后是否等于0,等于0,表示完全释放,因为一个线程可以多次获取锁,state表示重入次数,然后设置独占锁线程为null,重新设置state值,本例中,线程1只加了一次锁,所以完全释放了,返回true。

线程1释放锁执行完tryRelease方法后,如下图:

接着往下执行AQS类的release方法,获取同步队列头节点,如果头节点不为null,且头节点的waitStatus不等于0,就唤醒头节点的后继节点,这里将唤醒线程2。
看一下AQS类的unparkSuccessor方法,传进来的参数是同步队列的头节点,获取头节点的waitStatus等于-1,小于0,就CAS将头节点的waitStatus置为0,然后获取头节点的后继节点,即线程2节点,判断线程2节点不等于null,就唤醒线程2。
unparkSuccessor方法中需要注意一点,如果头节点的后继节点,其waitStatus大于0,文章开头讲过,该值大于0,也就是1,CANCELLED,表示线程取消等待,然后倒序循环,获取到离头节点最近的第一个waitStatus<=0的节点,为什么是倒序呢?需要看AQS类的addWaiter方法,即没有抢占到锁的线程进入同步队列时,先将传入的节点的前置节点指向原尾结点,然后再CAS设置尾结点为当前传入的节点,最后设置原尾结点的后继节点指向传入的节点,这里要考虑到多线程并发下,如果CAS失败的话,新节点就只有prev与原尾结点关联上了,next还没有关联上,所以如果正序循环的话,可能导致漏掉某些节点,没有全部遍历。

线程1释放锁后,如下图:

接下来线程2被唤醒,就能去抢占锁了,会接着被park的地方继续执行,也就是acquireQueued方法,进入for循环,判断线程2的前置节点是头节点,则尝试抢占锁,抢占成功后,设置head为线程2节点,线程2节点的thread和prev都置为null,头节点的next置为null,之后同步队列中原头节点就能被GC回收了。

线程1释放锁,线程2获取锁后的AQS,如下图:

公平锁和非公平锁
上面分析的是ReentrantLock类的默认非公平锁,也就是线程进来以后,先判断state,如果等于0,直接尝试抢占锁,而不管同步队列中是否有已经正在排队的线程节点,抢占失败后,添加到同步队列尾部,然后再次判断前置节点如果是头节点,再次抢占锁,如果还是失败,则被挂起。
公平锁是进来以后,先判断state,如果等于0,再判断同步队列中是否有已经有正在排队的线程节点,如果没有,才去抢占锁,后面的流程就与非公平锁一样了,也就是抢占锁失败后,添加到同步队列尾部,然后再次判断前置节点如果是头节点,再次抢占锁,如果还是失败,则被挂起。
如果要使用ReentrantLock的公平锁,直接使用new ReentrantLock(true);非公平锁相比公平锁性能更高,但是有可能造成某些线程一直被阻塞,抢占不到锁。
总结
ReentrantLock底层使用了AQS,默认使用的是非公平锁,抢占锁失败的线程会进入到同步队列末尾,同步队列以双向链表关联,头节点代表获取锁的线程,释放锁后,唤醒头节点的后继节点,获取锁成功后,对应的线程节点将从同步队列删除。
