ReentrantLock加锁解锁过程分析

1,创建ReetrantLock对象

ReentrantLock lock = new ReentrantLock(true);

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

2,加锁

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

为了便于理解,想象一个场景,假如有三个线程分别为A,B,C,同时加锁,在此之前并没有其它线程加锁。

2.1 tryAcquire

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

A,B,C三个线程同时开始执行tryAcquire方法,当三个线程同时调用getState()得到c的值都为0(根据之前的场景设定,没有其它线程加锁)。并且因为此时AQS队列中并没有等待获取锁的线程,所以!hasQueuedPredecessors()为true,因此会继续调用符号&&后面的方法compareAndSetState方法。

2.2 compareAndSetState

这个方法是把锁状态state从0设置为1,假如A先开始执行该方法,因为该方法是线程安全的,所以当A调用没有结束之前,B和C只能等待。

当A调用完之后,成功将state从0设置为1,表示加锁成功,并且把当前线程A设置拥有独占访问权的线程,返回true到上一层逻辑。

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

此时!tryAcquire(arg)=false,因为是&&所以不会执行后面的acquireQueued(addWaiter(Node.EXCLUSIVE), arg),加锁成功,A线程不会被阻塞。

B和C此时开始调用compareAndSetState(0, acquires),因为它们的原始数据是0,但此时state已经为1,所以B和C都不会设置成功,返回false,!tryAcquire(arg)=true,因此会继续执行符号&&后面的方法acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

2.3 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放入队列中排队,因为此时队列为空,所以(pred != null)=false,会执行enq(node)方法。

2.4 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;
                }
            }
        }
    }

首先判断队列尾部是否为空,因为此时队列为空所以(tail==null)=true开始执行compareAndSetHead设置头结点(此方法相当于初始化队列,在队列中放入一个Node,这个Node不包含线程信息,将队列的头和尾都指向这个Node),初始化之后的数据结构如下所示:

因为compareAndSetHead方法是线程安全的,假如B先执行这个方法,C必须等待,B执行完compareAndSetHead之后,C就可以执行,因为B已经初始化完成,C初始化就会失败,失败之后,C继续循环执行。B初始化队列成功后也会再次循环,因为B已经初始化成功,所以此时tail不为空,假如B和C也是同时执行到相同逻辑,假设B对应Node为:NodeB,C对应Node为:NodeC,尾结点为tailNode,此时会同时将NodeB和NodeC的前一个结点同时设置为tailNode,这是不合理的,因为尾结点只能有一个,所以才有compareAndSetTail方法,通过线程安全方法保证只能有一个Node被成功设置为tailNode,如果B先执行compareAndSetTail,C必须等待,当B执行完之后,设置尾结点成功,结束循环。此时的数据结构如下所示:


C调用compareAndSetTail必定失败,因为此时tailNode已经改变,C会继续循环,重新获取最新的tailNode,然后调用compareAndSetTail方法,将NodeC设置为尾结点,此时队列中数据为:

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

addWaiter执行完之后会将线程对应结点放入队列,并且返回结点Node
假如B和C同时执行到这个方法,首先先获取对应结点的前一个结点,B结点对应的前一个结点是headNode,执行符号&&后面的方法tryAcquire,再次尝试加锁,
此时分两种情况:
1,如果A已经释放锁,B会成功加锁,然后将NodeB设置为headNode,移除原headNode,此时的数据结构为:

此时B已经加锁成功,不会被阻塞,走加锁成功逻辑
2,如果A此时还没有释放锁,B加锁失败,接着往后执行shouldParkAfterFailedAcquire,因为NodeB为新节点,NodeB.preNode.waitStatus为0,如下所示:

执行compareAndSetWaitStatus方法后数据结构如下,headNode.waitStatus=-1

 然后返回false,回到上一层逻辑,继续循环再次尝试加锁,
如果加锁成功参考上面说的第一种情况,
如果还是没加锁成功,此时shouldParkAfterFailedAcquire为true(刚刚执行compareAndSetWaitStatus将NodeB.preNode.waitStatus设置为Node.SIGNAL),

执行符号&&后面的parkAndCheckInterrupt方法阻塞当前线程。
如果C和B同时执行该方法,因为此时NodeC.preNode不是headNode,参考以上第二种情况。

如果C比B后执行该方法,这时候分两种情况:

1,A释放锁,B获取锁成功,此时NodeC.preNode=headNode,参考以上第一种情况

2,A没有释放锁,B获取锁失败,此时NodeC.preNode!=headNode,参考以上第二种情况

3,解锁

设定一个场景,假设现在B和C都在队列中排队

lock.unlock();
public void unlock() {
        sync.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;
    }

3.1 tryRelease

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

这里为什么不是直接将state设置为0,这是因为考虑到重入锁的情况,比如A线程可以多次调用lock.lock()加锁,每调用一次,state就会在原来的基础上加1。如果想要释放锁,lock.unlock()方法调用次数必须和lock.lock()调用次数一致,所以这里用getState()-releases,而不是直接将state设置为0,假如A线程只加了一次锁,现在执行完了,要释放锁,getState得到的值为1,int c= (1-1)=0,满足条件(c==0)=true,设置free=true,设置当前独占线程为空,设置state为0,返回free=true,返回到上一层代码:

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为true,继续执行接下来的逻辑

取出当前headNode,再回顾一下现在的数据结构如下:

headNode不为空,并且headNode.waitStatus!=0满足条件,开始执行unparkSuccessor方法

3.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;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

参数node为headNode,headNode.next为NodeB,满足条件s!=null,唤醒线程B,回顾一下阻塞代码:

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

当线程B被唤醒之后,进入下次循环,获取前一个结点为headNode,满足p==head,然后执行tryAcquire方法,准备尝试加锁,因为A已经释放锁,此时B会加锁成功,然后设置头结点为NodeB,把原headNode从队列中移除,此时的数据结构如下:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值