Lock公平锁与非公平锁的源码分析(以ReentrantLock为例)

本文详细解读了ReentrantLock接口的内部结构,特别是公平锁与非公平锁的实现机制,涉及同步状态、公平与非公平获取/释放锁的过程,以及它们的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、类的结构简介

Lock是一个接口,ReentrantLock则是这个接口的一个实现类,在ReentrantLock中有一个抽象内部类Sync,ReentrantLock中所有方法的实现均在这个抽象内部类中。

而这个抽象内部类Sync又可以分为两个子类,分别为NonfairSync和FairSync。

具体的结构图如下:

        在ReentrantLock的构造方法中,需要传入一个boolean型参数fair,内部类Sync将根据这个参数的值来创建对应的子类。

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

        也就是说,Sync类中的方法在不同的子类中会有不同的实现,这也就对应着公平锁和非公平锁方法中对应的实现。

        而Sync类又是AbstractQueuedSynchronizer的子类,在AbstractQueuedSynchronizer中定义有一个变量state,该变量用于记录当前锁的持有计数(可以理解为线程每成功获取一次该锁,锁的持有计数+1,当持有计数为0时,表示该锁未被任何线程所持有)。(重要!)

private volatile int state;

        在这个类的父类AbstractOwnableSynchronizer中定义了一个变量 exclusiveThread,用于记录当前锁的持有线程。(重要!)

private transient Thread exclusiveOwnerThread;

二、公平锁的实现

2-1 Lock解析

         先来看一下公平锁获取锁的方法

final void lock() {
            acquire(1);
        }

public final void acquire(int arg) {
//在当前结点获取锁失败,且收到过中断信号的时候,则阻塞当前线程
//下面的注释中分别对if中的两个判断条件进行分析
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

       2-1-1 tryAcquire()

//当前线程尝试获取锁
protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //获取锁的持有计数
            int c = getState();
            //1.锁的持有计数为0
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //2.锁的持有计数不为0,但当前线程是锁的占有线程
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        在tryAcquire()方法中,线程在两种情况下满足获取锁:

        1、锁的持有计数为0,代表当前锁未被任何线程获取。

        2、锁的持有计数不为0,但是当前锁的占有线程是当前线程,代表当前线程重入其持有的锁。

        3、当前线程必须是队列的队首结点对应的线程。(下面会讲)

        对于满足条件的线程,其将尝试获取锁,若成功获取锁,则需要修改state的值和exclusiveThread的值,并返回true,若其获取锁失败,则返回false。

        2-1-2 acquireQueued方法解析

        2-1-2-1 addWaiter()

       再来看另一个条件:

//在看这段代码之前,请先看上面我画的队列的结构,会好理解一些
//将mode结点加入到队列的队尾
private Node addWaiter(Node mode) {
        //根据当前线程生成结点
        Node node = new Node(Thread.currentThread(), mode);
        //根据我上面画的图,结点每次入队时需要插在队列队尾,这部分的逻辑是将新创建的结点和队列的队尾相连。
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //结点若入队失败,则在队尾生成一个空节点,然后不断自旋,直到将队尾设置为对应的结点即可。
        enq(node);
        return node;
    }

//判断队尾结点也就是当前线程,在入队到其获取锁的过程中是否周到过中断信号
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            //判断当前线程是否收到过中断信号
            boolean interrupted = false;
            for (;;) {
                //获取当前线程对应结点的上一个结点
                final Node p = node.predecessor();
                //1.若p为头结点,则当前线程为队首线程(根据上面的结构图,head为null,并不存储线程信息),若其成功获取了锁,那么这个过程中,它没有被打断,因此interruped = false,failed = false
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //2.判断当前线程在获取锁是否需要被阻塞,以及它能否阻塞自身,若为true,代表自身成功被阻塞
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
                //3.若线程在没有获取到锁的时候退出,则删除这个结点,并将结点的waitStatus值设置为CANCELLED
            if (failed)
                cancelAcquire(node);
        }
    }

        addWaiter()方法比较好理解,就是将当前线程作为结点加入到队列的队尾。

2-1-2-2 acquireQueue()

        acquireQueue()方法较难,主要用于判断这个结点从入队到获取锁的过程中是否收到过中断信号,因为在lock中,线程获取锁失败,是需要被阻塞的,而阻塞状态下的线程无法响应中断信号,这个时候,在线程被唤醒之后,需要阻塞当前线程,处理掉前面阻塞期间收到的中断信号后,再进行其他操作。

        那么如何判断当前线程在阻塞期间是否收到过中断信号的依据就在第二个if判断条件中,这两个判断条件分别对应:

        1.当前线程在获取锁失败后被阻塞过(对应shouldParkAfterFailAcquire)

        2.检测当前线程的中断位是否被设置为中断,若为中断,代表着其收到过中断信号(对应着parkAndInterrupt)

2-1-2-3 shouldParkAfterFailAcquire

下面根据代码来进行分析,为了更好的理解两个if中的代码逻辑,先来看一下node.waitStatus属性

        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        volatile int waitStatus;

         注释里面已经写的非常清楚了,这里我们只关注具体值的含义,通过源码,不难得知

        1、当waitStatus>0的时候,其只能代表一种情况,那就是CANCELLED,也就是线程取消了获取锁的请求,对应着acquireQueue中finally中的代码,以下代码截取自cancelAcquire()方法。

node.waitStatus = Node.CANCELLED;

         2、SIGNAL状态代表着线程已被阻塞,需要唤醒,也就是说,找到pred.waitStatus = Node.SIGNAL状态的结点,返回true,代表着当前结点的上一个结点已经被阻塞,当前结点在获取锁失败后,同样需要被阻塞。

        了解了这些之后再来看代码:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        //找到pred.waitStatus为SIGNAL的结点,返回true
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        //前一个结点取消获取锁,需要一直循环,直到找到waitStaus<0的结点,并将此结点与当前结点相连
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        //其他 <=0 的状态,将这些状态设置为SIGNAL
        } 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.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

2-1-2-4 parkAndCheckInterrupt

        对于第二个if循环以及else,并不确定pred结点的waitStatus一定为SIGNAL,或一定被原子操作写成SIGNAL,故本次方法返回false,但这个方法调用在for(;;)这个死循环中,因此,下一次循环会再次检验pred.waitStatus是否为SIGNAL,所以不用担心会误判。

        在找到waitStatus为SIGNAL的结点后,阻塞当前线程,并检查线程是否被中断。

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

 至此,acquire中的所有判断条件均已分析完毕,若线程获取锁失败,且获取锁失败期间有收到过中断信号,则线程会被挂起。

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

2-2 unLock

public void unlock() {
        sync.release(1);
    }

 2-2-1 release

public final boolean release(int arg) {
        //成功释放锁
        if (tryRelease(arg)) {
            Node h = head;
            //则唤醒队列中的队首线程
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

 2-2-1-1 tryRelease()

protected final boolean tryRelease(int releases) {
            //更新当前的state值,每执行依次state-1
            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;
        }

        tryRelease的逻辑是,每次将state-1,后进行判断:

        1、若state = 0,代表当前锁空闲,其他线程可以获取这把锁,则返回true

        2、若state > 0,代表当前锁依然被某个线程持有,那么其他线程无法获取该锁,返回false

  2-2-1-2 unparkSuccessor

private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        //找到头结点的下个结点,也就是队首线程对应的结点
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            //更新队首线程之后下一个需要被唤醒的线程,防止出现队首线程结点waitStatus为CANCELLED的情况
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            //唤醒队首结点对应的线程
            LockSupport.unpark(s.thread);
    }

         这个方法做的事就是判断队首线程的waitStatus是否 < 0,若是,则其需要被唤醒,那么更新这个结点的下一个结点,并唤醒这个结点所代表的线程。

        也就是说release方法的逻辑就是当state=0,也就是锁可以被其他线程占用时,唤醒队首线程,队首线程在被唤醒后会获取锁,因为队列是按先进先出的原则来排列的,因此,队列中的线程一定可以按照入队的顺序依次获取到锁(在没有取消获取锁的前提下),因此它是公平的。

三、非公平锁的实现

3-1 lock

final void lock() {
            //通过原子操作将state的值从0改为1,若成功表示当前线程成功获取了锁,将exclusive属性设置为当前线程,表示当前锁的占有线程是当前线程。
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        可以看到,相比于公平锁,非公平锁的lock方法中多了一个if代码块,其含义是当前线程通过原子操作将state的值从0改为1,若成功,则将当前线程设置为锁的占有线程。

        else代码块中方法的逻辑与公平锁大体相同,但是tryAcquire方法不相同

3-1-1 tryAcquire()

protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //state=0代表当前锁可以被抢占
            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;
        }

       二者的唯一区别就是在c =0的代码块中if的判断条件非公平锁比公平锁少了一个!hasQueuedPredecessors()的判断。

        下面来看看这个代码的含义是什么:

public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        //队首结点对应的线程是当前线程时返回FALSE
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

        通过分析不难得出,当队首结点对应的线程是当前线程的时候,方法返回false,那么取反就是true,也就是说对于公平锁,state=0时,抢锁的线程必须是队首线程,而非公平锁取消了这一条。

        通过上面的分析不难看出,非公平锁之所以不公平,体现在:

        1、新来的线程可以直接通过原子操作将state设置为1的方法,先于队首结点对应的抢到锁。

        2、即使原子操作失败,在acquire()方法中,新来的线程依然有机会先于队首结点对应的抢到锁,因为没有!hasQueuedPredecessors()这个判断。

        这是有这些原因,所以它才叫非公平锁。

3-2 unlock

        非公平锁的unlock()方法与公平锁相同,此处不再展开介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值