互斥模式获取
在互斥(独占)模式下获取,忽略中断。 通过至少调用一次tryAcquire ,并在成功后返回。 否则,将线程排队,可能反复阻塞和解除阻塞,直到调用tryAcquire成功为止。 此方法可用于实现Lock.lock方法。
成功修改state
的值,意味着获取成功。
获取逻辑
// 在独占模式下获取,并忽略中断
public final void acquire(int arg) {
// tryAcquire(arg):由具体的子类实现
// 当获取成功时,则跳出该方法,避免阻塞
if (!tryAcquire(arg) &&
// 当获取失败时,将当前线程封装成Node节点,加入到等待队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 如果 acquireQueued() 方法返回true,则中断当前线程
// 内部是一行很简单的代码,Thread.currentThread().interrupt();
// 将当前线程置为中断状态,响应不响应该中断 由开发者决定
selfInterrupt();
}
复制代码
响应还是不响应中断,由开发者决定。
public class LockTest {
public staic void main(String[] args) {
Lock lock = new ReentrantLock();
lock.lock();
try {
if (Thread.currentThread().isInterrupted()) {
System.out.println("线程被中断了,跳出业务逻辑");
} else {
System.out.println("执行业务逻辑");
}
} finally {
lock.unlock();
}
}
}
复制代码
入队逻辑
// 创建节点,并加入到队列尾部
private Node addWaiter(Node mode) {
// mode:模式,表示当前节点是由于什么模式而加入到队列
// 将当前线程和模式封装成Node节点(this.nextWaiter = mode)
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
// 尾节点不为null,代表等待队列已初始化
if (pred != null) {
// 将 新节点 指向(?) 尾节点(此时就已经入队成功,剩下两个指针未调整)
node.prev = pred;
// (指针一)通过CAS 修改尾节点,将其指向新节点
if (compareAndSetTail(pred, node)) {
// (指针二)将旧的尾节点指向(?)新节点(当前节点已经成为新的尾节点)
pred.next = node;
return node;
}
}
// CAS修改失败或者等待队列未初始化,进入该逻辑
enq(node);
return node;
}
复制代码
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 尾节点为null,代表等待队列还未初始化
if (t == null) { // Must initialize,初始化逻辑
// CAS原子修改,将头节点指向一个空的Node节点(Thread为null)
if (compareAndSetHead(new Node()))
// 此时尾节点和头节点指向同一个空的Node节点
tail = head;
} else {
// 将新节点指向(?)尾节点
node.prev = t;
// 通过CAS修改尾节点,将其指向新节点
if (compareAndSetTail(t, node)) {
// 将旧的尾节点指向(?)当前节点(当前节点已经成为新的尾节点)
t.next = node;
return t;
}
}
}
}
复制代码
重试和阻塞逻辑
在线程被阻塞之前,还存在获取的机会,这也就是所说的自旋锁。如果线程被阻塞,那么会加大操作系统切换线程的成本。
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);
// 将前置节点的引用清除,帮助GC
p.next = null; // help GC
failed = false;
return interrupted;
}
// shouldParkAfterFailedAcquire() 方法返回false时,
// 再一次进入for循环,表示当前节点还有一次机会尝试获取
// shouldParkAfterFailedAcquire() 方法返回true时,
// 进入 parkAndCheckInterrupt() 方法,阻塞当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 当线程在等待时间内被中断,在被唤醒时
// 会修改中断状态为 true,但还是要进入for循环直到获取锁成功
interrupted = true;
}
} finally {
// 该方法下,应该是没有情况进入if代码块
if (failed)
cancelAcquire(node);
}
}
复制代码
// 获取失败时,应该阻塞吗
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取前置节点的等待状态
int ws = pred.waitStatus;
// 如果状态为 -1 ,则直接返回true
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
// 前驱节点已经被置为-1状态,当前节点可以安心阻塞
return true;
// 如果状态大于 0,代表前置节点被取消
if (ws > 0) {
状态大于0,代表被取消,需要跳过这些节点
do {
// 向上一直寻找节点,直到不为取消状态,并将当前节点(?)指向它
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// 未取消的节点指向(?)当前节点,形成双向链表
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
// 状态必须为0或者-3,
// 将前置节点的等待状态置为 -1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
复制代码
private final boolean parkAndCheckInterrupt() {
// 阻塞当前线程
LockSupport.park(this);
// 当被唤醒时,返回其中断状态(注意:interrupted()方法会清空中断状态)
return Thread.interrupted();
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END