AQS—阻塞队列(待整理)

阻塞队列不包含 head,头结点也不包含线程

Sync 有两个实现,分别为 NonfairSync(非公平锁)和 FairSync(公平锁)。

 

    public static ReentrantLock reenT = new ReentrantLock();//参数默认false,不公平锁
    private static  ReentrantLock lock = new ReentrantLock(true); //公平锁  速度慢与不公平锁


    public static void tryLockTest() {
        lock.lock();
            try {
                System.out.println(Thread.currentThread().getName());
            }
            finally {
                lock.unlock();
            }
        }

 //FairSync获取锁

   static final class FairSync  {

        //挣锁

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

        public final void acquire(int arg) { // 此时 arg == 1
            // 首先调用tryAcquire(1)
            //如果成功不需要进队列排队了,
            if (!tryAcquire(arg) &&
                    // 没有成功,这个时候需要把当前线程挂起,放到阻塞队列中。
                    acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
                selfInterrupt();
            }
        }

     
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {//此时此刻没有线程持有锁
                //查看有没有线程在等待
                if (!hasQueuedPredecessors() &&
                        // 如果没有线程在等待,那就用CAS尝试一下,成功了就获取到锁了,
                        // 不成功的话,就是刚刚几乎同一时刻有个线程抢先了
                        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;
        }

     

        //查看有没有线程在等待
        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;
            return h != t &&
                    ((s = h.next) == null || s.thread != Thread.currentThread());
        }

        //把线程包装成node,插入至队尾,返回在等待队列中的节点
        private Node addWaiter(Node mode){
            //把当前线程封装为node,指定资源访问模式
            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;
                }
            }
            //如果tail为空,说明队列还没有初始化,执行enq()
            //或者 CAS失败(有线程在竞争入队)
            enq(node);
            return node;
        }

        //将节点插入队尾,失败则自旋,直到成功。
        //等待队列为空,或者有线程竞争入队,才进入该方法
        private Node enq(final Node node) {
            for (;;) {
                Node t = tail;
                if (t == null) {
                    if (compareAndSetHead(new Node()))
                        tail = head;
                } else {
                    node.prev = t;
                    if (compareAndSetTail(t, node)) {
                        t.next = node;
                        return t;
                    }
                }
            }
        }

        //参数node,经过addWaiter(Node.EXCLUSIVE),此时已经进入阻塞队列
        final boolean acquireQueued(final Node node, int arg) {
            boolean failed = true;//标记是否成功拿到资源
            try {
                boolean interrupted = false;//标记等待过程中是否被中断过
                //还是自旋尝试获取锁资源
                for (;;) {
                    final Node p = node.predecessor();
                    //如果前驱是head,即该结点已成阻塞队列的第一个,那么便有资格去尝试获取资源。
                    //成功后则返回中断位结束
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);//拿到资源后,将head指向该结点。所以head所指的标杆结点,就是当前获取到资源的那个结点或null。
                        p.next = null; //setHead中node.prev已置为null,此处再将head.next置为null,就是为了方便GC回收以前的head结点。也就意味着之前拿完资源的结点出队了!
                        failed = false;
                        return interrupted;
                    }
                    //shouldParkAfterFailedAcquire(Node, Node)检测当前节点是否应该park()
                    //parkAndCheckInterrupt()用于中断当前节点中的线程
                    if (shouldParkAfterFailedAcquire(p, node) &&
                            parkAndCheckInterrupt())
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }

        //当前线程没有抢到锁,是否需要挂起当前线程
        private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
            int ws = pred.waitStatus;//拿到前驱的状态
            if (ws == Node.SIGNAL)
                //前驱节点的状态是SIGNAL,说明前驱节点释放资源后会通知自己
                //此时当前节点可以安全的park(),因此返回true
                return true;
            if (ws > 0) {
                //进入阻塞队列排队的线程会被挂起,而唤醒的操作是由前驱节点完成的。
                //前驱节点的状态是CANCLLED,说明前置节点已经放弃获取资源了
                //此时一直往前找,直到找到最近的一个处于正常等待状态的节点
                //并排在它后面,返回false
                do {
                    node.prev = pred = pred.prev;
                } while (pred.waitStatus > 0);
                pred.next = node;
            } else {
                //前驱节点的状态是0或PROPGATE,则利用CAS将前置节点的状态置
                //为SIGNAL,让它释放资源后通知自己
                //正常情况下,前驱节点是之前的 tail,那么它的 waitStatus 应该是 0,这里是设置状态的
                compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
            }
            // 这个方法返回 false,那么会再走一次 for 循序,
            //     然后再次进来此方法,此时会从第一个分支返回 true
            return false;
        }

        // 这个方法很简单,因为前面返回true,所以需要挂起线程,这个方法就是负责挂起线程的
        // 这里用了LockSupport.park(this)来挂起线程,然后就停在这里了,等待被唤醒=======
        private final boolean parkAndCheckInterrupt() {
            LockSupport.park(this);
            return Thread.interrupted();
        }
    }

解锁:

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


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


        // 唤醒后继节点

        private void unparkSuccessor(Node node) {

            int ws = node.waitStatus;
            // 如果head节点当前waitStatus<0, 将其修改为0
            if (ws < 0)
                compareAndSetWaitStatus(node, ws, 0);
   
            // 唤醒后继节点,但是有可能后继节点取消了等待(waitStatus==1)
            // 从队尾往前找,找到waitStatus<=0的所有节点中排在最前面的
            Node s = node.next;
            if (s == null || s.waitStatus > 0) {
                s = null;
            
//这段代码不是说就是从后往前找,而是当 s.next “不正常” 的时候才是从后往前找,大概率情况下,还是不需要遍历的。
                for (Node t = tail; t != null && t != node; t = t.prev)
                    if (t.waitStatus <= 0)
                        s = t;
            }
            if (s != null)
                // 唤醒线程
                LockSupport.unpark(s.thread);
        }
    }

总结

总结一下吧。

在并发环境下,加锁和解锁需要以下三个部件的协调:

  1. 锁状态。 state 的作用,它为 0 的时候代表没有线程占有锁,可以去争抢这个锁,用 CAS 将 state 设为 1,如果 CAS 成功,说明抢到了锁,这样其他线程就抢不到了,如果锁重入的话,state进行 +1 就可以,解锁就是减 1,直到 state 又变为 0,代表释放锁,所以 lock() 和 unlock() 必须要配对啊。然后唤醒等待队列中的第一个线程,让其来占有锁。
  2. 线程的阻塞和解除阻塞。AQS 中采用了 LockSupport.park(thread) 来挂起线程,用 unpark 来唤醒线程。
  3. 阻塞队列。

 

公平锁和非公平锁

public ReentrantLock() {
    // 默认非公平锁
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

static final class FairSync extends Sync {
    final void lock() {
        acquire(1);
    }
    // AbstractQueuedSynchronizer.acquire(int arg)
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 1. 和非公平锁相比,这里多了一个判断:是否有线程在等待
            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;
    }
}


static final class NonfairSync extends Sync {
    final void lock() {
        // 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    // AbstractQueuedSynchronizer.acquire(int arg)
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
/**
 * Performs non-fair tryLock.  tryAcquire is implemented in
 * subclasses, but both need nonfair try for trylock method.
 */
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()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

总结:公平锁和非公平锁只有两处不同:

  1. 非公平锁在调用 lock 后,会调用 CAS 进行一次抢锁
  2. 在 tryAcquire 方法中,如果(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,

相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

AQS(AbstractQueuedSynchronizer)是Java中实现同步器的框架,它提供了一种基于FIFO队列阻塞和唤醒机制。AQS阻塞队列原理是通过CLH(Craig, Landin, and Hagersten)队列来实现的。 CLH队列是一种虚拟的双向链表,它仅存在节点之间的关联关系,而不存在队列的实例。每个请求共享资源的线程都会被封装成一个CLH队列的节点(Node)。当线程请求共享资源时,它会被添加到CLH队列的尾部,并进入阻塞状态。 当共享资源被占用时,其他线程请求该资源的线程会被放入CLH队列的末尾,即排队等待。这种排队等待的方式可以保证请求资源的线程按照FIFO的顺序获得资源,避免了饥饿现象。当资源释放后,AQS会自动唤醒队列中的下一个线程,使其获得资源并继续执行。 需要注意的是,AQS的同步队列(Sync queue)是一个双向链表,包括头节点(head)和尾节点(tail),用于后续的调度。而条件队列(Condition queue)是一个单向链表,只有在使用Condition时才会存在,并且可能会有多个条件队列。 总结一下,AQS实现阻塞队列的原理是通过CLH队列来实现的,当共享资源被占用时,请求资源的线程会被添加到CLH队列中排队等待。当资源释放后,AQS会自动唤醒队列中的下一个线程,使其获得资源并继续执行。同步队列用于后续的调度,而条件队列只在使用Condition时才会存在。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值