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();//只有一个线程访问
                }
            }
        }
    }
  • 相关阅读:
    noi 2011 noi嘉年华 动态规划
    最小乘积生成树
    noi 2009 二叉查找树 动态规划
    noi 2010 超级钢琴 划分树
    noi 2011 阿狸的打字机 AC自动机
    noi 2009 变换序列 贪心
    poj 3659 Cell Phone Network 动态规划
    noi 2010 航空管制 贪心
    IDEA14下配置SVN
    在SpringMVC框架下建立Web项目时web.xml到底该写些什么呢?
  • 原文地址:https://www.cnblogs.com/yaowen/p/11323457.html
Copyright © 2011-2022 走看看