AQS(AbstractQueuedSynchronizer)总结

AQS是一个同步队列,它是一个抽象类,主要用途是处理多线程竞争,将未获取到锁的线程进行排队,是JUC包中重要的基石,其中除了维护一个队列之外,还有两个值比较关键,分别是:
  1.   state   标记位
  2.   exclusiveOwnerThread  当前所属线程
维护队列的两个参数分别是:
  1.  Node head  同步队列头节点
  2.  Node tail  同步队列未节点
维护的队列是一个双向链表,指向来上一个和下一个节点。
AQS是JUC的基石,类似于CountDownLatch、ReentrantLock等都是通过AQS实现的,其中state在不同的实现中作用也是不相同的,这里只拿ReentrantLock来做阐述。

ReentrantLock 锁

  1. ReentrantLock锁是有两个构造函数的,分别是无参构造和传入布尔值的构造函数,默认无参构造是创建一个非公平锁,布尔值构造函数,传入true则创建公平锁,传入false则创建的是非公平锁。这两种锁的大概区别就是,当有新线程获取锁时候,公平锁会将当前线程直接放入到队列当中,等待前面所有线程任务完成后才获取锁,而非公平锁在获取锁时,不管队列中是否有等待线程,它都会先尝试获取当前锁,如果获取失败,会判断是否是重入,还会去尝试获取当前锁,在加入队列前还会去尝试获取锁。
  2. ReentrantLock是属于可重入锁,可重入锁的意思就是如果当前线程已经拥有了锁,当当前线程再次去获取锁的时候,会判断获取锁的线程是否就是当前锁拥有的线程,如果相等则直接获取锁,可重入锁的好处就是可以避免死锁问题。前面说的AQS中有两个重要参数,exclusiveOwnerThread是用来保存当前锁所属线程,而state则是保存重入锁次数,如果一个线程获取锁一次,state的值则会加1,释放一次锁state的值会减1,所以在使用的时候,获取来几次锁则要相应的释放几次锁。当state为0时,表示当前锁还没有被线程占用。
  3. 在操作重要参数时都是使用的CAS方式设置值的,为的是解决多线程竞争修改同一个值的问题,这里的CAS是属于乐观锁,它主要的思想是<比较-替换> ,操作方法是使用的java的native方法,直接操作内存。<比较-替换>的意思就是有两个值,一个是旧的值,一个是我将要设置的新值,当JVM内存中的值与传入的旧值相等时,才将新值设置。如果就是不相等,则设置失败。CAS无法解决ABA的问题。
  4. 当创建的是非公平锁时,调用lock()方法
    final void lock() {
        if (compareAndSetState(0, 1))   //判断是否当前锁是否被占用,如果未被占用则直接获取锁
            setExclusiveOwnerThread(Thread.currentThread()); // 设置锁的所属线程
        else
            acquire(1);  //再次尝试获取锁
    }
    public final void acquire(int arg) {
            if (!tryAcquire(arg) &&   // 再次尝试获取锁,判读是否是重入
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  // 如果再次尝试获取锁失败则准备加入等待队列
                selfInterrupt();  // 通知线程中断
        }
  5. tryAcquire(arg)  再次尝试获取锁(判断是否是重入),经过两次跳转后进入到nonfairTryAcquire方法
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {   // 判断是否属于无锁状态,如果是则直接获取当前锁
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {  // 判断是重入,如果是则将AQS中的state重新设置值
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
  6. 如果再次获取锁失败,则准备加入队列   acquireQueued(final Node node, int arg),这里的Node指将当前的线程组装为一个Node节点,也是通过CAS+死循环的方式将节点加入到等待队列中,使用死循环是为了多线程竞争导致加入队列失败,再次重新加入。
    final boolean acquireQueued(final Node node, int arg) {  // node是指当前线程组成的节点,已经加入到了队列
        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   //将已经释放了锁的节点从队列中清除,顺便解除GC Root,帮助GC回收
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  7. p == head && tryAcquire(arg)  ,判断是否可以获取锁,p == head表示当前节点的前继节点是否是头结点,如果第一个条件成功,则再次尝试获取锁,在获取锁的方法中,会判断state是否为0(无锁状态),如果为0,则表示当前节点的前继节点(头节点)已经使用完毕释放了锁,当前节点获取锁成功返回true,当两个条件都为true时,表示当前节点的前继节点执行完毕,需要剔除队列,当前节点应该设置为头节点。
  8. 释放锁比较简单,调用unlock()方法后进入下面的方法
    public final boolean release(int arg) {
        if (tryRelease(arg)) { //
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
     

    第二行代码:CAS将state值减少 ,如果不是所属线程去释放锁会抛异常,如果state减为0,则将所属线程设置为空

    第五行代码:将唤醒后继节点获取锁。

猜你喜欢

转载自blog.csdn.net/weixin_33613947/article/details/113778155
今日推荐