ReentrantLock lock = new ReentrantLock(true/false)
如果创建的时候是设置的true就是创建的公平锁,如果是false默认那么就是非公平锁。
1.公平锁加锁
首先先获取加锁执行lock()方法,会进行acquire操作,acquire里有一个tryAcquire,先判断它是不是成功,如果是成功说明加锁成功就不会进行后续操作,如果是加锁失败,那么取反是true,就会进行下次的操作acquireQueued这个里面会先进性addWaiter方法将线程Node加入阻塞队列,这也是AQS的一个主要体现,如果加成功了会执行外面的acquireQueued操作。
tryAcquire里会先获取state状态如果是0就是证明这个锁还是空闲状态,如果是1证明这把锁已经被人用了,如果是1证明是被人用了,还会进行一个判断,看看这个把锁是不是自己当前线程的,如果是自己当前线程的那么那么会将state+1,也就是说这个state不止有0或1这个状态,还有可能有123456,这也就是可重入锁的体现。如果此时判断state是0,证明锁没被人用,正常来说此时应该进行CAS将锁的状态由0改成1证明锁被人使用了然后设置锁的使用者为当前线程,但是因为这是公平锁,所以会先判断如果没人在队列中排队才会进行CAS加锁,如果加锁失败了或者队列中有人等待那么会将该线程加入阻塞队列中。判断有没有人在阻塞队列就是先判断头结点和尾节点是不是相等,如果是相等证明是null,说明阻塞队列中没有元素,如果判断头节点的下一个节点是是自己当前线程,如果是证明是一个可重入锁,那么会返回false,否则返回true,或者判断头节点的下一个节点是不是null,如果是null此时证明有可能是多线程情况下,加入队列的两个节点,第一个节点可能还没有将头节点的next想连,此时第二个节点也加入队列,判断head.next是不是null,如果是null证明此时阻塞队列有元素,考虑到并发勤快了。如果判断有人在等待或者加锁失败,则会退出这个tryAcquire方法。进行acquireQueued里的addwaiter方法,将当前线程加到AQS里的双向队列节点后面,如果此时有5个线程同时加队列,然后都发现队列有元素证明有人在等待,那么就会进行争抢看谁先入队成功,剩下4没成功的就会执行enq方法,这里面是一个while操作,直到自己的线程加入到队列才能结束,保证此时线程都能加入队列。加入队列这个操作有个注意的就是如果加入队列的时候这里面是空的,证明该线程是第一个加入节点的,那么此时不会把该线程当成第一个节点,而是创造一个空节点,然后将该线程设置节点加入上一个空节点的下一个节点上,目的就是为了,如果是第一个线程解锁成功了,然后队列中有一个节点,那么第一个线程解锁成功会唤醒队列中head.next这个节点,那么这个节点就可以争取锁了。然后进入acquireQueued方法里会进入一个循环,然后里面会判断当前加入的节点的前一个节点是不是刚才创建的那个空的头节点,如果是的话说明这个节点是阻塞队列里面唯一的线程节点,那么允许他再进行获取一次锁,如果此时获取锁成功了那么就将当前节点置空,然后设置为新的头节点以供下次线程加锁失败加入该队列时候使用。如果获取锁失败执行下面的判断,如果获取当前节点的上一个节点的waitstatus是不是-1如果不是-1会将它改成-1然后返回false,会进行再一轮循环,如果此时还是加锁失败,那么继续进行刚才的if判断,此时判断上一个节点的waitstatus是-1了,那么就将这个线程阻塞等待park()。如果被唤醒,则继续循环,直到阻塞队列只剩下头节点以及线程的节点了,那么此时线程获取锁成功后会将头结点清理断开循环GC垃圾回收器回收掉。
2.非公平锁加锁
调用lock()方法上来先进行加锁,加到锁将持有线程改成当前线程。如果一上来没有加到锁,那么会进入acquire方法,调用tryAcquire方法这个是非公平锁的视线,他这里就直接判断state是不是等于0.如果等于0那么直接就加锁,不许要判断之前公平锁里那样看阻塞队列里有没有线程存在。如果不等于0判断是不是当前线程如果是就是可重入锁,state+1,如果加锁失败了,就返回false。之后退出这个方法接下来就像公平锁一样,会将该任务加入到队列中接下来和公平锁一样。
3.解锁
不论公平锁还是非公平锁,他们的解锁都是一致的,并且他们解锁其实都是公平解锁。执行unlock()方法会进入release方法,此时公平锁和非公平锁的逻辑是统一的。实际上就是该state,不断的-1目的是防止可重入锁,直到为0证明这个锁结束然后回到release方法进行unpark解除阻塞。当然还有细节方面的比如中断的时候节点的waitstatus会是1,那么此时解锁的时候就会跳过这些waitstatus1的节点。
归纳:
1. 公平锁加锁流程
- 执行加锁操作:调用
lock()
方法时会执行acquire
操作,其中的tryAcquire
方法首先判断锁是否已被占用。若获取成功,则加锁完成,若失败则进行acquireQueued
操作。 - 检查锁状态:
tryAcquire
中会检查state
值,state=0
表示锁空闲,state=1
表示锁已被占用。如果锁被当前线程占用(即可重入情况),则state+1
。 - 公平性检查:在公平锁中,若锁空闲,CAS 加锁之前会检查阻塞队列是否有其他线程等待;若有等待线程或 CAS 失败,当前线程会加入阻塞队列。
- 队列管理:若线程无法获取锁,会通过
acquireQueued
的addWaiter
方法加入 AQS 双向阻塞队列。若多个线程竞争入队,会通过enq
循环操作确保线程最终进入队列。
2. 非公平锁加锁流程
- 直接加锁:非公平锁在调用
lock()
时会直接尝试加锁,不检查阻塞队列。tryAcquire
中,若state=0
,则直接加锁;若锁已被当前线程持有,state+1
。 - 进入等待队列:若加锁失败,进入
acquire
流程,调用tryAcquire
并返回false
后进入队列,之后流程与公平锁一致。
3. 解锁流程
- 释放锁:公平锁和非公平锁的解锁操作相同,通过
unlock()
进入release
方法,不断将state-1
直至为 0,表示锁完全释放。接着,unpark
唤醒队列中的下一个节点。 - 中断处理:若节点
waitStatus
为 1 表示节点中断,解锁时会跳过这些节点。