4

ReentrantLock 公平锁源码 第2篇 - Jame!

 2 years ago
source link: https://www.cnblogs.com/sunankang/p/16461261.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Reentrant 2

前两篇写完了后我自己研究了下,还有有很多疑惑和问题,这篇就继续以自问自答的方式写

如果没看过第1篇的可以先看看那个https://www.cnblogs.com/sunankang/p/16458795.html

java
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

进入acquireQueued方法

java
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);
    }
}

第一个问题

interrupted这个变量的作用

java
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

parkAndCheckInterrupt方法中最后return的是这个线程是否被打断,它的作用是啥?

先来回顾interrupt()interrupted()isInterrupted()三者区别,长得很像,注意区分

interrupt()的作用是中断线程,如果被中断的线程处于阻塞状态下,例如调用wait(),join() sleep(),则抛出异常,否则只是设置一个中断标记为true,注意:仅仅是设置中断状态为true,并不会去 "中断" 线程

interrupted() 获取线程的中断状态并且清空中断状态(将中断状态设置为false)

isInterrupted() 获取线程的中断状态并不会清除中断状态

调用 interrupt 会使park方法立即结束,可以理解为唤醒

继续代码,看这个变量最后到了哪里

情况1 没有被打断过

假设线程没有被中断过,那么parkAndCheckInterrupt返回就是false

java
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);
    }
}

那么不进入 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())这个if,获取到锁后返回false,回到acquire方法

java
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

因为false,所以不进入selfInterrupt(),方法结束

情况2 park或准备park,被唤醒后直接获取到了锁

先证明一下打断是会唤醒park中的线程的

1687169-20220709164058542-1169195413.png

我就再重复粘一下代码了,方便看

java
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

那么返回的就是true,回到上级acquireQueued方法

java
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);
    }
}

因为返回true,所以进入if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) 将interrupted返回true

假设循环获取到锁,那么再返回上一级acquire()

java
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

那么进入selfInterrupt()

java
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

是不是有点疑惑?我如果没有调用过interrupt() 那ReentrantLock就不做任何操作,我如果调用了,那它再给我调用一次 ???? 还有情况3

情况3 park或准备park,被唤醒后没有获取到锁

java
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;
            }
            //假设在调用shouldParkAfterFailedAcquire成功后,马上就要调用parkAndCheckInterrupt 时间片用完了
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

那么这个时候interrupted属性就有用了

首先要知道一点,一个被中断的线程是无法park的,除非清除了中断状态,即设置为将中断状态设置为false, 口说无凭,直接上图

1687169-20220709164106714-1778619476.png

1687169-20220709164108615-1509272926.png

第二张图还是在park状态,证明了被打断的线程是无法park的,除非将它中断状态设置为false

那么回到代码中就能知道这个的作用

java
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);
    }
}

如果线程被打断唤醒,还是在for(;;)中,还是去获取锁,假设没有获取到呢?那么就一直在for循环中嘎嘎跑,因为线程的状态是被中断的,无法再次park了

java
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

那么现在懂了最后的Thread.interrupted()作用了吗,就是将中断状态设置回false,好让线程没有获取到锁继续park

那这时候可能就问了:那你ReentrantLock把中断状态给我清空了,我自己如果有需要根据中断状态来判断的代码咋办啊?

好,咱们从park先被打断来捋一下

java
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

因为被打断,线程醒来,执行Thread.interrupted()并清空中断状态,返回true

java
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);
    }
}

因为返回的是true,所以进入if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())的代码块,将interrupted属性设置为true

那么for(;;)循环再来一次,如果没有获取到锁.继续park,直到被唤醒,走tryAcquire()获取到为止,那么此时interrupted变量就为true了

java
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

那么退出acquireQueued()方法回到acquire()中,因为acquireQueued()返回的是true,所以进入selfInterrupt()

java
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

所以懂了吗?

__EOF__


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK