Lock接口的实现类,基本上都是通过【聚合】了一个【队列同步器Sync extend AQS】的子类完成线程的访问控制
ReentrantLock
默认创建非公平锁
内部类Sync继承AQS
公平锁和非公平锁
两者的lock方法唯一区别在于公平锁在获取同步状态时多一个限制条件,hasQueuedPredecessors()
此方法是公平锁加锁时判断等待队列中是否存在有效结点的方法
公平锁:
先来先到,线程获取锁之前先判断等待队列是否有其他线程等待,有就自己进入等待
非公平:
不管是否有等待,如果可以直接获取锁,立刻占有锁对象,存在竞争关系
ReentrantLock解析
static Lock lock = new ReentrantLock();
lock.lock();
lock.unlock();
// 3步到底发生了什么事情 ReentrantLock内部的AQS如何操作的?
1、内部调用
public void lock() {
sync.lock();
}
2、sync是一个内部类继承AQS
abstract static class Sync extends AbstractQueuedSynchronizer
3、在非公平锁继承sync,实现了lock方法
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
4、区别于公平锁,非公平锁多了一个compareAndSetState判断,公平锁只有acquire
5、compareAndSetState方法尝试抢占资源,即把AQS中的state设为1
设为1后调用setExclusiveOwnerThread方法把当前线程设置为正在处理线程
这里没有直接排队而是进来直接尝试抢占,体现了非公平性
6、acquire方法处理没有抢占到资源,线程进入队列等待排队的过程
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
进来之后还是先尝试抢资源tryAcquire,抢不到变true执行后面acquireQueued方法
见缝插针式抢资源,体现非公平性
6.1、addWaiter传入排它结点模式
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
将当前需要排队的线程封装成一个node(结点),判断当前AQS队列有没有排队,没有则执行enq
如果队列不为空,直接插入到队伍后
6.2、enq方法用于初始化排队队列
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
通过自旋的方式,若队伍没有结点则new Node()创建一个傀儡结点
注意第一个结点不是排队1的结点而是傀儡结点
傀儡几点创建完后头尾指针都指向傀儡结点,然后把排队1加入到傀儡结点后面
6.3、添加完队列后执行acquireQueued方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
failed控制失败,interrupted控制中断,失败为true取消排队interrupted=true线程中断
node.predecessor()获取当前节点的前一个结点,当前为排队1,前结点是傀儡结点
p=傀儡,再次尝试争抢
若成功傀儡结点解除引用等待gc回收,排队1结点线程占用,然后1结点线程为空变傀儡结点
6.4、执行shouldParkAfterFailedAcquire方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
一开始p指向傀儡结点,传入p和node1,node1用于cancel逻辑,主要是是把传入的pred结点
即一开始的傀儡结点的waitstate设为-1准备好,false则acquireQueued在度自旋
再次进入shouldParkAfterFailedAcquire,但是傀儡的ws已经是-1,返回true
进入parkAndCheckInterrupt方法
6.5、parkAndCheckInterrupt方法调用LockSupport方法进行线程挂起
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
this为当前的排队1结点,即排队1结点到这里才正式挂起,等到通知
后面的排队的都只是进入队列,连ws都没=-1,只是在排队,等排到了再挂起
7、调用unlock方法
public void unlock() {
sync.release(1);
}
7.1、release传入步长(解锁层级,一次1)
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease尝试释放资源,成功后头结点不为空且ws不为0,此时应该为-1,调用unparkSuccessor
7.2、unparkSuccessor
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
此时头结点为傀儡结点,ws=-1,把-1设成0
s指向排队1结点,如果后面结点为空但是ws大于0,则应该是取消的结点还在队列中
for循环将这些取消结点去掉指向ws=-1的有效结点
因为有的排队排着排着会取消,所以可能排队1234都取消,这时候通过循环直接把5安排上了
通过LockSupport.unpark()唤醒线程,此处唤醒的是排队1的线程
6.6、唤醒后继续回到parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
由于没有中断所以返回false
回到acquireQueued,继续自旋
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
此时p为傀儡结点,当前是排队1,尝试tryAcquire,由于刚刚释放了,所以这里会成功
注意,在多线程环境,其他线程可能在排队1来到这里之前通过tryAcquire获取到,所以非公平
获取到以后,setHead方法把AQS的prev指向排队1,排队1的prev结点置空,把排队1的线程置空
然后傀儡 结点next置空,相当于傀儡结点全部引用解除,等待GC回收
failed=false说明没有失败,不用取消排队,逻辑继续,返回中断false
最上层的acquire方法acquireQueued=fasle,不会执行selfInterrupt中断自身
- AQS初始化CHL队列时,排在第一位的是傀儡结点,且傀儡结点一直存在,通过新的结点线程获取资源后
把就傀儡结点引用置空,队列头变成新的傀儡结点
- AQS中的state是记录资源情况,0=释放,1=占用
- Node结点的waitstate是记录结点的准备情况,初始是0,-1是准备继续,准备抢占
- 可重入锁是通过没进入一层内部逻辑,则AQS的state+1来实现只要不为0则资源未得到释放