线程并发--Condition控制线程通信

问题:wait和notify方法,只能被同步监听锁对象来调用,否则报错IllegalMonitorStateException.
那么现在问题来了,Lock机制根本就没有同步锁了,也就没有自动获取锁和自动释放锁的概念.
因为没有同步锁,所以Lock机制不能调用wait和notify方法.

解决方案:Java5中提供了Lock机制的同时提供了处理Lock机制的通信控制的Condition接口.Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

Condition常用方法:


image.png

注意:这些方法必须是在lock方法和unlock方法中间使用,和synchronized相似。

获取Condition实现类对象

我们都知道Condition是接口,如何获取Condition实现类对象呢?

使用Lock中的newCondition方法获取Condition实现类对象。以ReentrantLock为例,newCondition创建的是AQS中的ConditionObject内部类对象。

1.1.ConditionObject结构

我们通过源代码发现Condition的实现也是通过一个队列完成的,他和我们的Lock中的队列不一样,Lock中的队列叫做同步队列,用于决定那个封装了线程的节点获取到锁资源,Condition中的队列我们成为等待队列,在调用Condition的await()方法之后,线程释放锁,构造成相应的节点进入等待队列等待。

image.png

1.2.await方法源码分析:

        public final void await() throws InterruptedException {
            if (Thread.interrupted())//如果线程中断,抛出异常,所以才会说该方法不会进入等待队列
                throw new InterruptedException();
            Node node = addConditionWaiter();//将线程封装到一个节点中,并把该节点放入到队列的尾部
            int savedState = fullyRelease(node);//释放锁资源,并唤醒下一个节点(线程)
            int interruptMode = 0;
//循环判断node是否在同步队列中,不在同步队列中,拿不到锁,线程不能执行,继续等待
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);//线程挂起
//如果能够继续执行,证明挂起结束,则判断如果已经中断了,则退出循环,不再挂起线程
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //重新竞争同步锁
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            //清理队列中不是非等待状态的节点
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);//其他状态抛出异常或者是线程中断
        }

addConditionWaiter方法分析图:


image.png

同步队列和等待队列关系分析图:


image.png

调用await方法分析图:


image.png

21.3 signal方法源码分析:
        public final void signal() {
            if (!isHeldExclusively())//判断当前是否是获取锁的状态,因为线程通信前提是线程安全
                throw new IllegalMonitorStateException();//不安全报错
            Node first = firstWaiter;//获取队列头
            if (first != null)//头节点不为空
                doSignal(first);//唤醒线程
        }

doSignal方法:

        private void doSignal(Node first) {
            do {
                //如果第一个节点同时是最后一个节点
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;//因为要移除最后一个节点,所以设置为空
                first.nextWaiter = null;//第一个节点的下一个节点也设置为空
            } while (!transferForSignal(first) &&//唤醒挂起的线程
                     (first = firstWaiter) != null);//头节点不为空
        }

transferForSignal方法:

    final boolean transferForSignal(Node node) {
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))//修改节点的等待状态,还原到初始状态
            return false;//修改失败然后false,成功继续往下执行
        Node p = enq(node);//将节点放入到同步队列中
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))//唤醒状态修改成功
            LockSupport.unpark(node.thread);//唤醒线程
        return true;
    }

signal方法分析图:


image.png

代码演示:

public class ShareResource {
    private int num = 0;// 信号量

    private Lock lock = new ReentrantLock();
    /**
     * notify和signal有一点不同,notify是唤醒其他线程,
     * 而signal谁调用了await释放的时候需要谁调用才能释放对应的线程,因为lock是对象
     */
    private Condition cset = lock.newCondition(); // 生产者通信
    private Condition cget = lock.newCondition(); // 消费者通信

    // 生产操作
    public void set() {
        lock.lock();
        try {
            while (num >= 10) {// 10为盘子装满了
                System.out.println("盘子装满了,不能再装了!");
                cset.await();
            }
            this.num++;
            System.out.println("生产第" + num + "个包子");
            cget.signalAll();// 生产完唤醒消费者
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    // 消费操作
    public void get() {
        lock.lock();
        try {
            while (num <= 0) {
                System.out.println("包子吃完了,生产了才有得吃!");
                cget.await();
            }
            this.num--;
            System.out.println("消费第" + num + "个包子");
            cset.signalAll();// 消费完了唤醒生产者
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

21.4 【迅雷面试题】
编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。
代码演示:

public class ShareResource {
    private int singal = 0;// 0:A线程,1:B线程,2:C线程
    private Lock lock = new ReentrantLock();
    // 多个的时候我们使用singalAll唤醒所有线程,所以使用一个等待队列即可,但是不好,因为容易出现自旋
    private Condition c = lock.newCondition();// 等待队列

    public void getA() {
        lock.lock();
        try {
            while (singal != 0) {
                c.await();
            }
            System.out.println(Thread.currentThread().getName() + "--> A");
            singal++;
            c.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void getB() {
        lock.lock();
        try {
            while (singal != 1) {
                c.await();
            }
            System.out.println(Thread.currentThread().getName() + "--> B");
            singal++;
            c.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void getC() {
        lock.lock();
        try {
            while (singal != 2) {
                c.await();
            }
            System.out.println(Thread.currentThread().getName() + "--> C");
            singal = 0;
            c.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

注意:这里使用singalAll效率没有singal效率高,因为唤醒所有的线程,如果执行a之后抢到锁的是c不是b,那么c要重新回到等待队列中,知道b抢到锁为止,出现自旋的情况,降低性能。
最优方案代码:

public class ShareResource {
    private int singal = 0;// 0:A线程,1:B线程,2:C线程
    private Lock lock = new ReentrantLock();
    private Condition ca = lock.newCondition();// a等待队列
    private Condition cb = lock.newCondition();// b等待队列
    private Condition cc = lock.newCondition();// c等待队列

    public void getA() {
        lock.lock();
        try {
            while (singal != 0) {
                ca.await();
            }
            System.out.println(Thread.currentThread().getName() + "--> A");
            singal++;
            cb.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void getB() {
        lock.lock();
        try {
            while (singal != 1) {
                cb.await();
            }
            System.out.println(Thread.currentThread().getName() + "--> B");
            singal++;
            cc.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void getC() {
        lock.lock();
        try {
            while (singal != 2) {
                cc.await();
            }
            System.out.println(Thread.currentThread().getName() + "--> C");
            singal = 0;
            ca.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容