Lock详解(涉及到AQS队列)

Lock思想:
a朋友想进门,b朋友也想进门,还有c,d,e等一堆堆朋友,这个时候就要去锁管理处。获取锁,一个锁同时只能被一个朋友拿到。这个锁管理处,还有一个队列,一旦获取不到锁,就让等待锁的人排队,等别人归还了锁,就从队列中踢出新的小朋友去拿锁。

类比说明:
各位小朋友:多个线程a,b,c,d...
锁管理处:ReentrantLock+sync
锁管理处的队列:AbstractQueuedSynchronizer
锁:ReentrantLock的一个私有变量state的值。
拿锁:通过cas原子方法,来设置state的值。

Lock方法主要流程:

 final void lock() {
            if (compareAndSetState(0, 1))//直接去放锁的地方尝试拿锁(通过cas设置state为1),这就是为啥叫做不公平锁,上来先不排队,先直接去抢锁。
                setExclusiveOwnerThread(Thread.currentThread());//成功则设置当前线程
            else
                acquire(1);//cas失败,说明别人(也有可能是自己)已经获取锁,进入正常获取锁流程。
 }

acquire(1)方法:

public final void acquire(int arg) {//
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//tryAcquire尝试直接获取锁,acquireQueued排队等锁。两者任一成功,则拿锁成功,返回acquire方法。
            selfInterrupt();//都失败的话,就将当前线程中断。
    }

tryAcquire(1)调用以下方法

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {//若state为0,则说明锁已经空闲
                if (compareAndSetState(0, acquires)) {//试图直接获取一下。
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//虽然state不为0,但是,之前获取锁的就是自己线程本身。
                int nextc = c + acquires; //锁的状态+1
                if (nextc < 0) // overflow,如果小于1,说明自己获取太多了,获取的次数已经溢出了整数范围
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false; //为false,说明目前是别的线程在获取着锁。上段代码的tryAcquire(arg)就会为false。
        }

addWaiter(Node.EXCLUSIVE), arg):

/**
*@param mode 要插入队列的节点
*/
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);//Node这个Class表示等待队列中的节点。
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {//队列不为空的情况下,
            node.prev = pred;//直接在queue的尾部加入当前这个需要等待的节点。
            if (compareAndSetTail(pred, node)) {//并设置tail为这个节点。
                pred.next = node;
                return node;//成功加入queue尾部后,返回这个新节点。
            }//若失败。则说明队列已经发生了改变。则去enq(node)。这个 if (pred != null)逻辑是为竞争不激烈的情况准备的。
        }
        enq(node);//这个就要为竞争激烈或者队列为空的情况准备。
        return node;
    }
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize,tail为空,说明等待队列中无人,则试图初始化一个head节点,
                if (compareAndSetHead(new Node()))
                    tail = head;//初始化tail和head均指向该node。
            } else {//若tail不为空,则说明queue不为空,则正常将node加入queue。
                node.prev = t;
                if (compareAndSetTail(t, node)) {//试图设置tail为新的node。
                    t.next = node;
                    return t;//成功的话则将该节点放入queue,失败的话,说明queue已经改变,进行下一波循环,直到设置成功(竞争激烈的情况,就会多次循环,不激烈的时候就一次循环搞定,此处是通过cas保证原子性)
                }
            }
        }
    }

缕一下奥:
获取锁的步骤(unfairLock):
1.cas直接试图设置state为1。成功转8
2.查看之前拿锁的人是自己,还是别人。是别人转3,是自己转7。
3.将当前线程包装为一个nodeA,查看queue是空还是非空。空转4,非空转5
4.初始化queue的head为一个初始化nodeHead,tail为当前nodeA,加入queue成功,返回nodeA。
5.直接加入队列queue。并返回nodeA。
6.走到这步,说明等待队列queue已经加入成功了,接下来,开始排队等锁,也就是acquireQueued(Node )。
7.千辛万苦等待到了锁,返回acquire(1)。
8.拿锁成功。

接下来是acquireQueued(Node )

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);//将head指针前移,
                    p.next = null; // help GC,并断掉之前的head。
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())//第一个方法就是判断状态是否为signal,是的话,就可以在第二个方法将这个线程park(就是阻塞),不需要不断循环判断,只需要阻塞,静静的等待被唤醒(具体怎样被唤醒,参看unlock()方法),不是signal的话,就看是否为别的状态,是0的话,说明是初始化,将状态设置为signal,是cancell的话,遍历前面的节点看有木有cancell,有的话从链中将那些节点全部去掉。第二个方法,就是将线程park,等待唤醒。
                    interrupted = true;//走到这一步,说明,节点在等待期间,该线程已经中断了。
            }
        } finally {
            if (failed)//该方法一旦有什么异常,保证要放弃获取锁。
                cancelAcquire(node);
        }
    }

unlock思想:
获取锁时,只可能有一个线程持有锁,但是一个线程可能重入锁。unlock时,如果不再持有锁,就让下一个等待者去拿锁,如果还是持有状态,则单纯将state-1就可以。

unLock方法较简单,主要流程:

  public final boolean release(int arg) {
        if (tryRelease(arg)) {//state-1后,若为0,说明,当前线程不再持有锁,进入if
            Node h = head;
            if (h != null && h.waitStatus != 0)//且queue不为空,有等着锁的线程呢
                unparkSuccessor(h);//将之前queue中被park的node,唤醒,让他去尝试拿锁。
            return true;
        }
        return false;//state-1后,若不为0,则单纯返回false
 }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值