zoukankan      html  css  js  c++  java
  • AQS2--出队

     队列不卡死,一定要:前面节点变成头结点唤醒时候能够唤醒后面节点,依次类推。

    设置前面节点=-1就是为了前面节点走的时候,唤醒自己。

      

    正常没有阻塞节点,设置前面=-1,再旋转一次尝试获取锁,才阻塞。即使设置前面节点=-1之前,前面节点=0唤醒失败走了,也不要紧,自己会再次旋转一次获取锁。设置-1之后,前面节点=-1唤醒成功。自己旋转获取锁失败阻塞,前面节点=0唤醒失败走了,是不可能的,因为阻塞前会设置前面=-1.

    正常节点阻塞了,前面正常节点=0,那么中间至少有一个异常节点,并且阻塞在异常节点上了。异常节点会去设置前面节点=-1。异常节点赶在前面节点唤醒之前设置-1就可以,赶在之后,那么异常节点就要自己唤醒后面节点。

      

    可以把异常节点作为前驱(错了),就是不能把已经执行了if判断的头节点作为前驱,不然就卡死了。

    public final boolean release(int arg) {
            if (tryRelease(arg)) {
                Node h = head;
                if (h != null && h.waitStatus != 0) 
                    unparkSuccessor(h);
    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);

    一定要注意,前面节点变成head执行if (h != null && h.waitStatus != 0) 之前要设置为-1否则就不能唤醒后面status正常节点(特例:除非后面节点自行出队,还在旋转)


     所以正常节点要阻塞之前设置-1.

    上图中,A没有阻塞,head出队执行if (h != null && h.waitStatus != 0)时候可以=0可以=-1head=0出队就A自己去获取锁,head=-1出队唤醒A。所以只要A没有阻塞,就不需要前面节点出队执行if判断之前=-1。


     如果A阻塞了,就要异常的B去设置前面=-1,此时,一定要在异常节点B前面节点C出队执行if (h != null && h.waitStatus != 0) 之前把前面C设置-1如果前面节点出队时候=0A又阻塞了,A就永远获取不了锁,队列卡死。

    但是,问题在于,正常节点和异常节点都无法知道前面节点是否已经出队执行过了if (h != null && h.waitStatus != 0)。但是前面节点变成head时候thread=null,所以后面节点能够知道的前面节点是否是head。所以一定要在异常节点B前面节点C变成head之前把前面C设置-1(或者已经变成了head但是还没有执行if(h.wautStatus!-0)的判断),否则就唤醒A

     下面看异常节点设置前面节点=-1逻辑:

    if (pred != head
    &&( (ws = pred.waitStatus) == Node.SIGNAL|| ( ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) //pred有可能=head,
        && pred.thread != null //pred.thread!=null,那么pred肯定不=1,因为等于1之前thread就设为null了,那么此时prev肯定=-1(-1不可能变成0)。
    或者pred异常了,但是thread!=null status=-1,作为前驱也可以,相当于先认定一个前驱,再这个前驱异常,只要不把执行了if判断的头结点作为前驱就可以。
                    //pred有可能=head(head=node,node.thread=null),只是还没有执行thread=null,prev肯定也没有执行if(h.waitStatus!=0)的判断。
                    //走到第三个判断,前面节点=-1,并且还没有执行出队判断,那么可以建立后驱,即使建立后驱前出队了,也可以唤醒后面 ) { Node next
    = node.next; if (next != null && next.waitStatus <= 0)
    //pred如果正常,肯定=-1,刚刚pred.thread!=null,现在可能thread=null了,也就是可能作为head进行if判断过了,但是设置-1之前如果是头结点肯定没有执行if判断,可以唤醒。
    //pred如果是异常,建立后驱关系也不要紧,这个异常的pred会再去调整。这里也是尽可能使得队列不卡死. compareAndSetNext(pred, predNext, next); }
    else { unparkSuccessor(node); }
    head = node;
    node.thread = null;
    node.prev = null;

    1.如果异常节点前面节点=head

     此时不知道head有没有执行if (h != null && h.waitStatus != 0),如果没有执行,此时设置head=-1,那么head执行if (h != null && h.waitStatus != 0)就回去唤醒A,如果已经执行了,就不会唤醒任何节点,再去将head=-1,也没用,所以就去唤醒异常节点的后面节点。

    2.如果C不是头节点,能够设置C=-1,并且设置C=-1之后C还不是头节点(C.thread != null)(或者是头节点但是没有执行if(h!=null&&h.waitStatus!=0))(异常节点也可以作为前驱),就建立后驱关系(即使还没有建好后驱关系,C就开始出队,但是C=-1了,C一定能够唤醒A)。这就保证了在异常节点B前面节点C变成head之前把前面C设置-1(即使C变成head但是没有执行if(h.waitStatus!=0)判断。其余情况都不行。

     如果异常节点前面C!=head,但是C=1了(此时C可以=head),肯定不能建立后驱关系,C节点就一定可以保证A能够唤醒,如果B再向前找没必要。B来唤醒,我个人感觉是没必要的,因为C可以一定唤醒A

     如果异常节点前面C!=headC没有异常(此时C可以=head),但是C是头节点了(C.thread = null),此时唤醒A

      

    注意:C变成head,再去unlock执行if (h != null && h.waitStatus != 0)中间有很多时间。所以C变成了head不一定执行了if(h.waitStatus!=0)的判断。

    public class testLock {
        public static void main(String[] args) {
            Lock lock = new ReentrantLock1();
            Ticket ticket = new Ticket(lock);
            Thread t0 = new Thread(ticket, "0号窗口");
            t0.start();
            Thread t1 = new Thread(ticket, "1号窗口");
            t1.start();
            Thread t2 = new Thread(ticket, "2号窗口");
            t2.start();
            Thread t3 = new Thread(ticket, "3号窗口");
            t3.start();
            Thread t4 = new Thread(ticket, "4号窗口");
            t4.start();
            Thread t5 = new Thread(ticket, "5号窗口");
            t5.start();
            Thread t6 = new Thread(ticket, "6号窗口");
            t6.start();
            
            t1.interrupt();
        }
    
        static class Ticket implements Runnable {
            private int num = 3;
            private Lock lock = null;
            Ticket(Lock lock){
                this.lock = lock;
            }
            @Override
            public void  run() {
                lock.lock();
                try {
                    System.out.println("AAAAAAAAAAAA");
                } finally {
                    lock.unlock();//只有一个线程访问
                }
            }
        }
    }
  • 相关阅读:
    cocos2dx
    读书日记-快速排序算法
    vs重装找不到 $(WindowsSdkDir) 配置问题
    IOS应用FFMPEG库
    OpenGL ES2.0贴图
    OpenGL ES2.0光照
    IOS系统配置FFMEPG
    GPUImage库的使用
    spring08事务
    java10---点餐系统
  • 原文地址:https://www.cnblogs.com/yaowen/p/11323457.html
Copyright © 2011-2022 走看看