AQS之独占锁(结合ReentrantLock进行源码分析)

AQS结构

  • AQS中有一个队列(FIFO,用双向链表实现),每个请求共享资源的线程都会被封装成一个队列中的节点来实现锁的获取。
volatile int state; // 表示共享资源/同步状态,通过CAS操作来修改state值
  • 队列中的节点Node的一些属性

    属性或者一些值
    SHARED表示线程以共享的模式等待锁
    EXCLUSIVE表示线程正在以独占的方式等待锁
    CANCELLED = 1线程获取锁的请求已经取消(由于超时或者中断),具有cancelled node的线程不会再次阻塞(感觉这句话没看懂意思)-- > 被取消的节点的thread会置为null。
    SIGNAL = -1表示该节点的后继节点线程会处于阻塞状态,待当前节点线程释放锁资源或者被取消时,会unpark后继节点的线程。然后后继的线程重新尝试获取锁,获取失败时阻塞。 --> 注意该状态无法看出该节点是否拥有锁,只是用来指示后继节点的状态。因为一个线程只有在他的前一个节点为SIGNAL状态时,他才可以safety park。因为他的前一个节点的线程释放锁资源或者被取消时会唤醒他。
    CONDITION = -2当前节点在条件队列中,在被唤醒之前,不会用作同步队列的节点。(不过当其他的线程对Condition调用了signal()方法后,该节点就会从等待队列转移到同步队列末尾(移入到同步队列时节点的waitStatus为0),然后等待获取锁)
    PROPAGATE = -3
    waitStatus节点的状态:取值有0(初始状态),CANCELLED,SIGNAL,CONDITION,PROPAGATE
    nextWaiter指向下一个处于CONDITION状态的节点
    thread封装在该节点中的线程

锁的获取(独占模式)

非公平锁的获取lock(),tryAcquire() // 独占模式

lock():若锁未被其他线程持有,获取锁;若锁被其他线程持有,禁用当前线程,直到获取到锁为止。

// ReentrantLock.NonfairSync
final void lock() { 
    if (compareAndSetState(0, 1)) // 先通过CAS更新state,更新成功则把该线程设置为拥有锁的线程-->获取锁成功,否则通过acquire()获取锁
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1); 
}

tryLock():立即返回获得锁的结果

	// ReentrantLock 默认为非公平锁
	public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
	// ReentrantLock.NonfairSync
	protected final boolean tryAcquire(int acquires) {
    	return nonfairTryAcquire(acquires);
    }
// ReentrantLock.Syn
		final boolean nonfairTryAcquire(int acquires) {
			// 非公平体现在不是按照队列中的顺序去获取锁
			// 而是在锁空闲的时候直接通过CAS修改state的值
            final Thread current = Thread.currentThread(); 
            int c = getState();
            if (c == 0) { // 锁空闲
                if (compareAndSetState(0, acquires)) { 
                // 先通过CAS更新state,更新成功则把该线程设置为拥有锁的线程-->获取锁成功
                    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;
        }

公平锁的获取lock(),tryAcquire() // 独占模式

公平锁没有tryLock()方法,公平锁的tryAcquire()时用在acquire()中的

lock():若锁未被其他线程持有,获取锁;若锁被其他线程持有,禁用当前线程,直到获取到锁为止。

// ReentrantLock.FairSync
final void lock() {
    acquire(1);
}

tryAcquire():立即返回获得锁的结果

		// ReentrantLock.FairSync		 
		protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) { // 锁空闲
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    // if中用于判断当前线程是否可以去获取锁若是,再判断是否通过CAS设置state成功 --> 是,则获取锁成功
                    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;
        }

hasQueuedPredecessors():判断当前线程是否有获取锁的资格

// AbstractQueuedSynchronizer (AQS)
public final boolean hasQueuedPredecessors() {
	Node t = tail; 
	Node h = head;
	Node s;
	return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
// h == t说明这时候队列中还没有有效节点,应该就是h = t = null的时候 --> 无排在当前线程前面的线程,当前线程将会是第一个有效线程,当前线程有资格获取锁
// h != t时; (入队的时候 第一步:先把node的pre指向t(旧的tail),第二部然后再将node设置为新的tail,再将旧的tail的next设置为新的tail即node)若此时s = h.next == null, 说明此时有一个线程正在进行初始化(也就是进行了第一步,第二步进行到将node设置为新的tail,但还没让旧的tail的next设置为新的tail即node) --> 当前线程没有资格去获取锁,已经有线程排在他前面了
或者 
s = h.next != Thread.currentThread() 也就是该线程不是队列中的第二个节点(因为队列中的第一个节点要么是虚节点[第一个线程节点入队之后的情况,这时候第一个节点是一个虚节点,第二个节点才是真正入队的这个线程节点],要么是正在持有锁的线程节点),只有第二个节点才有可能去成功拿到锁

公平锁和非公平锁的公共部分(都调用acquire())

acquire(): 获取锁,Acquires in exclusive mode, ignoring interrupts.

	// AbstractQueuedSynchronizer
	public final void acquire(int arg) {
		// 如果获取成功即tryAcquire(arg)返回True则不需要执行后面的acquiredQueued()
		// 否则通过acquiredQueued()方法不断尝试去拿锁,一直到拿到锁之后,再返回
		// 从代码中可以看到,在acquiredQueued()方法不断尝试去拿锁期间,他会忽略中断
		// 但是acquiredQueued()返回的是在方法期间是否发生中断(也就是该线程的中断标志位)
		// 若最后返回的是true,则会进入if中执行selfInterrupt(),这个方法只会将线程的中断标识设置为true,但是该线程仍会继续运行。
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt(); //调用Thread.currentThread().interrupt()方法
    }

补充一下interrupt():

  • 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。

  • 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。

    –> 可以看出interrupt()并无中断线程的作用

补充一下interrupted():

–>isInterrupted(): ClearInterrupted = false; -->isInterrupted(ClearInterrupted)

  • 若中断标志位为true/false, 那么调用了该方法后会清楚中断标志位,也就是重置中断标志位为false。

问题:

  • acquire()方法中,(若if中为true)为何获取到了锁还会被中断?

addWaiter():节点入队

刚放入队列中的节点的waitStatus都是0,什么时候变成-1的???????????

	// AbstractQueuedSynchronizer
	// 将节点加入队列末尾
	private Node addWaiter(Node mode) { 
		// 把这个线程包装成一个节点
        Node node = new Node(Thread.currentThread(), mode);  // 刚放入队列中的节点的waitStatus都是0,什么时候变成-1的???????????
        Node pred = tail;
        if (pred != null) {
        	// 队列已被初始化,先尝试将该节点入队
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node; // 入队成功则返回该节点
            }
        }
        enq(node); //入队失败,进入enq()的入队操作,会循环直到入队成功
        return node;
    }

enq(): addWaiter()前面将节点加入队列末尾失败就调用进入enq()中,enq()一直循环直到入队成功,然后返回该结点

	// AbstractQueuedSynchronizer	
	private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { 
            	// tail为null,也就是此时队列为空,进行初始化,new一个虚节点
            	// 并通过CAS将该节点设为头节点,注意此时还未将真正要入队的节点node入队,
            	// 所以此时将new出来的虚节点设为头节点后并未return            	
                if (compareAndSetHead(new Node())) // 第一处设置head
                    tail = head;
            } else {
            	// 真正将node入队的部分
                node.prev = t; // 第一步:先将让node的pre指向tail
                if (compareAndSetTail(t, node)) {
                	// 第二步:通过CAS操作将node设置为tail后
                	// 让原先的tail即t的next指向新的tail即node
                    t.next = node;
                    return t;
                }
                // 从这里可以看出会短暂出现head != tail 且 head.next =null的情况: 
                // 这是因为只进行到第二步的通过CAS操作将node设置为tail,
                // 但还未执行让原先的tail即t的next指向新的tail即node
            }
        }
    }

acquireQueued():lock()中先尝试获取锁失败就进入此方法,这个方法会一直执行,直到获得锁为止。最后返回的是线程的中断标志位。

	// AbstractQueuedSynchronizer
	final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor(); // 获取当前节点的前驱节点
                // // 若该节点是队列中的第二个节点且获得锁成功(state = 0)
                if (p == head && tryAcquire(arg)) { 
                // tryAcquire中会setExclusiveOwnerThread()             	
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    // shouldParkAfterFailedAcquire()判断获取锁失败后是否应该阻塞
                    // 若否,则不执行parkAndCheckInterrupt(),也就是不会阻塞该线程
                    // 是则执行parkAndCheckInterrupt(),即阻塞该线程并且检查中断标志位
                    // 因为执行了interrupted()方法会将标志会重置为false
                    // 若该方法为true会将标志位从ture->false,
                    // 所以要执行interrupted = true
                    interrupted = true;
            }
        } finally { 
            if (failed) // 获取锁失败,则将该节点设置为CANCELLED状态
                cancelAcquire(node);
        }
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jkw7Zk8D-1596609222461)(C:\Users\86134\AppData\Roaming\Typora\typora-user-images\image-20200729002651290.png)]

—> 能否获取到锁是看state

—> 线程的状态(阻塞or被唤醒之类的)看waitStatus

几个问题:

  • 一个节点入队的时候,他的waitStatus是0,什么时候变成-1的??

    答:见shouldParkAfterFailedAcquire()方法,

  • 释放锁后唤醒后继线程?

    答:见锁的释放release()方法,会唤醒head后面的第一个有效的线程

  • 被取消的时候处理唤醒后继线程

    答:见cancelAcquire()方法

  • 两个节点变化情况:一个节点要变为CANCELLED状态时指针的变化情况以及判断一个节点的线程是否要被阻塞时,若他的前置节点为CANCELLED状态时,指针又该怎么变化。

    答:cancelAcquire() 处理cancelled节点node的prev和next,以及pred(node前面的第一个有效节点)的next,这里不处理node后面的第一个有效节点的prev

    –>在shouldParkAfterFailedAcquire()中处理

shouldParkAfterFailedAcquire():判断获取锁失败后该线程是否应该被阻塞
	// AbstractQueuedSynchronizer
	private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL) 
        // 若node的前置节点的waitStatus是SIGNAL状态,那么node应该被park,因为当他的前置节点取消或者释放锁的时候,会通知该节点的线程
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
        // 若node的前置节点的waitStatus是CANCELLED状态
        // 让node的prev指针指向他的前面第一个waitStatus部位CANCELLED(=1)的节点,记作pred
        // 并且让pred的next指向node
        // 也就是跳过/删除node前面被取消的节点
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0); // 这里只处理node的prev指针,next指针的处理在
            pred.next = node;
        } else { 
        // 若node的前置节点的waitStatus是0 或者 PROPAGATE(-3)[没有CONDITION=-2状态是因为等于-2时还在条件队列中,不会作为同步队列中的节点,条件队列的现在还没看,不太清楚],把node的前置节点的waitStatus置为SIGNAL状态,但是该线不会被park,到下一次循环时就可以阻塞该线程了       	
            /*
             * 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;
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lyYy3R6c-1596609222462)(C:\Users\86134\AppData\Roaming\Typora\typora-user-images\image-20200729002739178.png)]

parkAndCheckInterrupt():阻塞线程并且检查中断标志位

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

cancelAcquire(): [Cancels an ongoing attempt to acquire.]将该节点设置为CANCELLED状态,并且处理他的后继节点的唤醒问题,把唤醒任务交给前面的节点或者自己立即把他唤醒

	// AbstractQueuedSynchronizer
	private void cancelAcquire(Node node) {
        if (node == null)
            return;

        node.thread = null;

        Node pred = node.prev;
       // 从node向前找,找到第一个waitStatus<=0的节点,记为pred,让node的prev指针指向pred
        while (pred.waitStatus > 0) 
            node.prev = pred = pred.prev;

        // 得到从node往前找到的第一个waitStatus<=0的节点的后继节点
        Node predNext = pred.next;

        // 把当前节点waitStatus状态设置为CANCELLED
        node.waitStatus = Node.CANCELLED;

        // 如果node是tail,则把pred设置为新的tail,若成功则,把tail的后继节点设置为null
        // 这种情况没有后继节点,不需要处理后继节点的唤醒问题,所以直接设置tail就行
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        }else{
        // 若node不是tail,
        //或者node是tail但CAS把pred设置为新的tail失败(多个线程在竞争尾部??)->node不是tail了?
            int ws;
            // 若node不是head的后继节点(也就是pred不是head),判断1:pred的waitStatus是否为SIGNAL 或者判断2:pred的状态是否<=0且通过CAS设置pred的waitStatus为SIGNAL是否成功。若1和2中只要有有一个为true,再判断pred节点中的thread是否为null
            // 若都满足 就设置pred的next指针指向node的后继节点,后继节点的prev指针的处理会在shouldParkAfterFailedAcquire()中完成
            // 其实就相当于,node的后继节点的唤醒本来应该是node负责的,但是因为node节点现在要被取消了,但是node的节点前面还有可以交付唤醒node后继节点任务的节点(也就是pred,因为他的waitStatus=-1),就让node前面的节点指向node后面的节点,也就是把后继节点的任务给了pred
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) 						&&pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
            // 若node是head的后继节点(也就是pred是head),那么也应当立即唤醒真正有效的第二个节点,也就是node后继的节点,唤醒后他才可以检查是否可以获取锁[存在这种情况,某一个时刻,head线程拥有锁,但是他的waitStatus不为-1,也就是他用完锁之后,不会负责去唤醒后继线程,所以需要node自己去处理唤醒他的后继节点]
            // 不满足上面两个判断(也就是这时候pred的节点的waitStauts为0或者-2)的情况,pred不会唤醒后继节点,所以唤醒后继节点的任务就需要node自己完成
            // 立即唤醒ndoe的后继节点
                unparkSuccessor(node);
            }		
            node.next = node; // help GC
        }
    }

unparkSuccessor(node):唤醒node后面的第一个有效的节点的线程(也就是waitStatus <= 0的节点),从tail开始往前找

	// AbstractQueuedSynchronizer
	private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
            // 从tail往前,找到node后面的第一个waitStauts <= 0的节点
            // 因为node的后面可能还有被取消的节点,被取消的节点的next的指针会指向自己,导致无法通过他的next执政找到后续节点,但是他的prev指针却不会断,所以可以通过prev指针往前找
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread); // 唤醒从tail往前找到的node后面的第一个waitStauts <= 0的节点
    }
    // 这里面只要负责唤醒,不用处理指针的变化,这是因为被唤醒的线程会进入acquireQueued()方法中先尝试获取锁,然后会进入shouldParkAfterFailedAcquire()方法中,这个方法中会处理前置节点waitStatus=1的情况时的指针变化,也就是会跳过/删除他前面的被取消的节点,这里就不用重复处理了

锁的释放:公平锁和非公平锁的释放流程一样,都是调用release()方法

unlock(): 释放锁

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

release():

	// AbstractQueuedSynchronizer
	public final boolean release(int arg) {
        if (tryRelease(arg)) { // 若锁空闲,处理后继是否要唤醒head的后继节点问题
            Node h = head;
            if (h != null && h.waitStatus != 0)
            // head == null 说明第一个节点还没对head进行初始化
            // head != null.若头节点的waitStatus 等于0,说明他的后继线程没有被阻塞,不需要去唤醒[从shouldParkAfterFailedAcquire()中可以看到,只有当这个节点的前一个节点的waitStatus为-1时,这个节点才会被阻塞,否则就是活动状态];
            // 若head的waitStatus为-1,说明后继节点被阻塞,需要他去唤醒后继节点;为-2,-3的情况暂不管了。
                unparkSuccessor(h); // 唤醒从tail往前找到的head后面的第一个有效的节点
            return true;
        }
        return false;
    }

tryRelease(): 检查是否锁是否空闲

		// ReentrantLock.Sync
		protected final boolean tryRelease(int releases) {
            int c = getState() - releases; // 减少重入次数
            if (Thread.currentThread() != getExclusiveOwnerThread())
            // 不是拥有锁的线程进入此方法会抛出异常
                throw new IllegalMonitorStateException();
            boolean free = false; // 记录锁的空闲状态,被占有-->false,空闲-->true
            if (c == 0) {
            	// 若此时state已经为零了,说明要释放锁
            	// 把free置为true,代表锁空闲,并把拥有锁的线程置为null
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c); // 设置更新后的state
            return free;
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值