condition

Condition接口主要是signal和await两个方法的变种
condition作用是当某个条件不成立的时候,我们需要不满足该条件的线程都等待,而当条件满足后,我们只会唤醒等待这个条件成立的线程

object的wait和lock的condition对比.png

最明显的区别支持等待的队列个数,这就导致了object的notifyall 会唤醒条件不满足的对象,而condtion却没这种问题

其中objecy的wait和notify必须在synchronized方法体内执行,即必须获得对象的monitor,是因为我们wait是让线程都处在跟当前对象关联的队列,而notify则需要找到其应该唤起哪个对象的队列。

整个condition方法的逻辑是:独占锁 获取到之后我们把当前节点包装一下加入condition队列(其实是链表),然后释放锁然后检测当前节点是否已经有condition队列移动到sync队列 去等待获取锁,如果获取锁成功,则进行下一步,其中唤醒有可能是是咱们的signal去唤醒或者被中断。signalAll是把所有队列都放入sync队列中

object的wait会释放锁

condition的await和signal 也必须在lock的获取锁内,当然了其是在不同的队列里面,且 wait 也会释放锁

注意condition只能用于独占锁 对于共享锁是不支持的,因为没有意义共享锁的话,本身就不该需要等待资源

condition的await 支持中断

  public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
加入condition等待队列
            Node node = addConditionWaiter();
释放锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
如果当前节点没有在同步队列,说明 还没被signal
            while (!isOnSyncQueue(node)) {
尝试让其沉睡
                LockSupport.park(this);
如果被唤醒或者重点,我们检测下,如果是中断直接break,否则检测下释放已经在队列里面 不在队列里面继续沉睡
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
到这一步肯定已经因为中断或者唤醒进入了sync
这个时候尝试去获取锁,获取不到就沉睡,下面条件代表我们在获取锁中代表我们没有中断了 且interruptMode != THROW_IE代表没有进入sync
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
如果存在下一个waiter 我们清理下Cancelled
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
如果interruptMode != 0,我们准备抛出异常
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
检测是否中断过,如果中断 把node从的condition变为0即初始化
如果设置成功说明我们已经把node从condition转化到sync,我们就可以throw_in,即跳出循环,如果我们放入sync失败我们重新进行判断 然后进入park
   private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }

   final boolean transferAfterCancelledWait(Node node) {
如果设置condition成功 则放入sync
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            enq(node);
            return true;
        }
如果失败说明此时正在取消,即已经进入sync队列但是我们的waitstatus是取消
那我们此时正在循环的把取消的节点从sync队列中剔除出去 所以我们在这边要等待
并让出cpu
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }
THROW_IE是直接抛出异常 ,REINTERRUPT是重置中断
 private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }

 final boolean isOnSyncQueue(Node node) {
如果还是在CONDITION ,或者在node的prev为null 说明没在sync队列
因为在condition队列 都是nextwaiter 。而prev是sync里面的 如果我们连prev都没有
说明还没进入enq
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
如果next存在说明一定在sync队列(不能判断prev 因为我们把condtion转移到sync
然后我们都会先设置prev,但是设置prev并不带就在sync队列)
        if (node.next != null) // If has successor, it must be on queue
            return true;

        return findNodeFromTail(node);
    }
我们从队列尾部找寻该节点
   private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }

  private Node addConditionWaiter() {
            Node t = lastWaiter;
          如果最后一个waiter已经被取消了就清除
这是因为如果最后一个不是取消状态 前面肯定也不是
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

取消从condition的队列里面
  private void unlinkCancelledWaiters() {
            Node t = firstWaiter;
            Node trail = null;
            while (t != null) {
                Node next = t.nextWaiter;
                if (t.waitStatus != Node.CONDITION) {
说明取消了,把t从队列里面取消
                    t.nextWaiter = null;
如果trail == null说明第一次遇到取消节点,会把next赋值给firstWaiter 
trail代表是最后一个正常的节点
                    if (trail == null)
                        firstWaiter = next;
                    else
把下一个可能的正常节点放入trail下面
                        trail.nextWaiter = next;
如果next==null 说明是进入了最后一个 把trail赋值给lastWaiter  都是正常的节点
                    if (next == null)
                        lastWaiter = trail;
                }
                else
把正常的赋值给trail
                    trail = t;
                t = next;
            }
        }
释放线程
final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
释放锁
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
释放失败代表异常
                throw new IllegalMonitorStateException();
            }
        } finally {
如果失败 了标识node状态等于取消
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

  public final boolean release(int arg) {
释放锁
        if (tryRelease(arg)) {
因为是独占锁,所以此时一定是head ,当waitstatus不等于0,唤醒下一个
如果waitStatus ==0 或者h ==null 说明这个时候有可能是被其他线程给抢到了
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
     public final void signal() {
如果不是独占锁 直接抛出异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
否则获取第一个waiter
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
   private void doSignal(Node first) {
            do {
如果firstWaiter 下面没有waiter 直接把lastWaiter  为null
把当前节点的nextWaiter 置位null 然后转移到sync队列
只要转移成功了直接跳出来
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
改变节点的状态为0初始化
 final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

      放入sync队列
        Node p = enq(node);
  这边就是如果已经是取消或者设置signal失败,我们直接唤醒线程,这样其最终会进入shouldParkAfterFailedAcquire,将自己直接清除
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

 public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);
        }
必须把所有的condition的waiter 都转移为sync
 private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);
        }

await方法的逻辑
1.首先判断是否中断过,中断抛出异常。
2.把当前线程加入一个condition队列,在这过程中如果lastWaiter状态不为condition,代表condition队列有取消者 清除取消者。
3.是否独占锁,如果释放失败,则设置该节点为取消状态
4.判断当前节点是否在sync里面
,先判断waitstatus 如果waitstatus不满足 我们在判断prev,因为我们在把节点放入队列里都先设置prev
当node 有next 肯定是因为已经在进入队列的时候设置了next,如果这些条件都不满足,则我们去队列里面从后往前找(这边不从前往后找是因为我们都是先设置pre 很容易导致我们漏了最后一个因为我们next并没有设置,如果我们从tail找,tail此时肯定合规)
5.如果不在sync 就park 如果线程醒来 先去检测是否中断 如果没有中断就是0那么再去进行步骤四,如果中断了 我们在去尝试把改线程加入sync队列,如果加入成功我们可以直接抛出异常,如果加入失败我们可以只是坐下中断标识
6.到第六步肯定已经进入队列,如果在这个过程中我们发现没有中断(有可能是因为我们已经清除了中断标识)我们重置
7.清除取消任务
8.REINTERRUPT我们设置interupt标识,THROW_IE 我们抛出异常

dosignal 和dosignal
1.前者只需要把一个线程放入sync队列,后者则把所有的都放入sync队列
2.首先都是检测是否是独占锁,如果不是直接抛出异常
3.然后对于firstWaiter为null的不处理
4.一个个处理线程放入sync队列,在这过程中如果设置waitstatus是0失败,说明已经被取消那么把他加入sync队列唤醒该线程,去获取锁,在这过程中其会自己把自己从sync队列剔除
5.最终循环到第一个节点结束进入或者全部节点都结束
6.根据第四部我们知道 当我们只唤醒一个 会有可能存在该 节点已经取消了导致我们浪费这次可以唤醒的机会

condition和lock的关系,我们通过lock生成condition,每个condition可以理解为一个队列,当我们调用contidition的await方法其释放的是lock的锁,然后将当前线程包装成一个节点,放入condition自己的链表队列里面,当我们调用该condition的signal时候,是把condition队列里面的节点放入lock里面的等待队列去获取锁

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容