AbstractQueuedSynchronizer- 独占锁实
2021-02-28 本文已影响0人
凯玲之恋
1-ReentrantLock
ReentrantLock是独占锁,而且内部可以是公平锁,非公平锁;
公平锁:
公平锁:加锁钱需要检查是否还有在排队(等待)的线程,优先排队的
final void lock() {
acquire(1);
}
非公平锁:
加锁时无需考虑之前是否有线程等待,直接尝试获取锁,获取失败会自动追加到同步队列队尾
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
2 上锁流程
2.1 acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected boolean tryAcquire(int var1) {
throw new UnsupportedOperationException();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
涉及简单方法分析:
- tryAcquire方法直接抛出异常,也即是自定义独占锁必须实现这个方法;
- selfInterrupt 获取锁的线程进行中断操作,这个并不一定导致线程停止
2.2 addWaiter方法
整体来说,就是加入一个节点到队列尾部;如果未初始化队列,则进行初始化(延时策略)
private Node addWaiter(Node mode) {
Node node = new Node(mode);
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
U.putObject(node, Node.PREV, oldTail);
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
initializeSyncQueue();
}
}
}
- 首先生成一个Node节点,这个节点nextWaiter为空(Node.EXCLUSIVE为空对象);独占锁的nextWaiter为空
- for循环自旋
- 如果队列未进行初始化,则initializeSyncQueue进行初始化,如果不成功,继续此步骤直至成功
- 加入队列尾部
2.3 acquireQueued方法
final boolean acquireQueued(final Node node, int arg) {
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
- for循环自旋;如果其是头节点下的第一个节点,如果尝试获取资源成功,则进行设置为队列头,释放之前头节点,并返回false;返回false,则意味线程跳出自旋,可以继续执行
- 如果不是锁等待队列的第二个,则执行shouldParkAfterFailedAcquire方法,如果为true,继续执行parkAndCheckInterrupt方法
- shouldParkAfterFailedAcquire方法执行后,返回false会去掉取消的节点,之后如果未有状态变化(比如外部取消线程,打断等操作),则会返回true,可以详细看下面方法源码
2.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 {
pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
}
return false;
}
- 如果node 前一个节点pred节点已经是等待唤醒状态,则返回true,表示当线线程应该被暂停
- 如果node前一个节点状态大于0,暂时好像只有取消状态的,则找到一个状态小于等于0的,并是node为其后继节点,则寻找过程中的节点都会被移除队列,返回false
- 如果node前一个节点已经是小于等于0了,这时把前一个几点的状态置为等待唤醒-1,返回false
2.6 parkAndCheckInterrupt方法
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- 暂停正在执行的线程,并返回打断状态
3 释放锁流程
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需要自定义实现,否则直接抛出异常
- 如果头节点不是正在运行状态,则解锁头节点线程,释放锁成功
- 否则不需要释放锁,或者释放失败(按照正常,在锁等待队列中,独占锁/条件锁,都会为signal状态,为共享锁为signal或者PROPAGATE状态)
3.1 unparkSuccessor方法
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
node.compareAndSetWaitStatus(ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
}
if (s != null)
LockSupport.unpark(s.thread);
}
- CAS操作,释放头节点状态设置为0
- 从头结点后寻找一个节点不为空,且节点为0的节点,释放此节点的线程(上述获取资源时,进行自旋,去除头结点后的取消的节点后的第一个节点才可以获取资源)
4 独占锁原理小结
- nextWaiter为空
- 排队等锁的队列,头优先获取资源(对于非公平锁,新获取未排队的线程也会获取锁);尝试获取资源的线程排队到队尾
- 获取资源失败的线程,被挂起;持有线程执行完毕,则头节点的下一个节点恢复执行,尝试获取资源(非公平锁,会和新获取锁未排队进来的线程争夺锁)成功后继续执行其任务,失败线程挂起,并置为等待唤醒状态
- 可重入锁,即当前线程再次获取资源,状态+1,释放资源状态-1,如果是0,则是当前线程完全释放了资源,其它排队线程可以获取资源了;代码如下:非公平锁的代码
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}