今天公司项目中需要使用Lock锁,所以顺便也看了下源码,同时发表一下自己的理解。
Lock:接口,用于定义锁操作的方法
ReentrangtLock:Lock接口的具体实现类
AbstractQueuedSynchronizer:同步队列
FairSync:公平锁
NonfairSync:非公平锁
锁的结构:
State:大于等于0,等于0则表示没有加锁,大于0表示已加锁
Queue:双向链表,用于保存竞争锁的Thread
OwnerThread:表示加锁成功的线程
Node:节点
Node head:头结点
Node tail:尾节点
以非公平锁为例:有三个线程进行加锁,分别为线程A,线程B,线程C;
lock()方法:
此时线程A最先执行获得锁通过CAS将state的值设置为1,并将OwnerThread设置为线程A。
那么线程B,线程C怎么办?此时调用acquire(1)方法将线程封装为一个Node对象,组成一个双向链表。tryAcquire(arg)方法此时会尝试获取锁,当然肯定是失败的。所以会首先执行addWaiter()方法
将线程加入到双向链表中,将线程B,线程C封装为Node节点,第一次tail肯定是null,所以走enq(node)方法.
enq()方法主要是将Node加入到双向链表中
图示:
线程B先执行enq方法,并返回当前线程
如果线程B已经添加到双向链表中后,返回Node调用acquireQueued方法
进入死循环,如果当前线程是B。
拿到线程B的上一个节点,判断是否等于头节点,并且会重新尝试加锁,这两个条件都为true,才会执行if里面的代码。由于线程A的所以并没有释放,所以这里不会进入
所以进入下一个if
shouldParkAfterFailedAcquire(Node pred, Node node)主要是设置线程
Node节点的上一个节点的waitStatus状态,如果等于-1表示是可唤醒竞争锁的线程
大于0则是异常,其他等于0表示初始值则修改waitStatus等于-1
parkAndCheckInterrupt()阻塞当前线程,也就是说当线程运行到这里的时候阻塞了
unlock()方法:
tryRelease():就是对state值减一,如果state等于0就设置owenrThread
为null,否则就是一个重入锁,只需设置state值就行
unparkSuccessor():拿到头节点,判断waitStatus如果小于0则设置头节点的
waitStatus为0。并拿到头结点的下一个节点,如果下一个节点为null或者节点的waitStatus大于0,则会从链表尾部将这些Node给删除。否则就会唤醒这个节点。
唤醒节点后:又会调用这个acquireQueued()方法,走第一个if进行尝试获取锁,
如果获取锁成功,则将这个节点设置为头节点,原来头节点的next设置为null.
至此,加锁以及释放锁都完了,其实也不是很难。
公平锁与非公平锁的区别:公平锁:lock时期,就必须将其加入到队列中
非公平锁:lock时期,会首先尝试通过CAS更改state的值进行获取锁
领取专属 10元无门槛券
私享最新 技术干货