Java-wait、notify、notifyAll

关键字wait、notify、notifyAll

大家都知道wait、notify、notifyAll这三个是Object提供的线程间协作的方法,常用在生产消费者模式,那么wait跟sleep有什么区别呢?wait、notify、notifyAll又该如何使用呢。

wait跟sleep区别

(1)共同点,wait、sleep都会让当前线程进入阻塞等待状态,并释放CPU时间片,在满足某个条件后被唤醒,例如timeout、中的;
(2)不同点,wait是Objec的方法,sleep是Thread的方法;
(3)异同点,wait会释放锁,而sleep不会释放;

wait、notify、notifyAll使用

场景:生产消费者模式
mProducerThread是生产者线程,负责生产商品,如果不能继续生产则wait;mConsumerThread是消费者线程,负责消费商品,如果不能继续消费则wait。
使用规则:
(1)wait、notify、notifyAll必须在同步代码中执行;
(2)wait必须在while循环内执行。

public class Goods {    
    //生产者线程
    private Thread mProducerThread = new Thread() {
        @Override
        public void run() {
            produce();
        }
    };

    //消费者线程
    private Thread mConsumerThread = new Thread() {
        @Override
        public void run() {
            consume();
        }
    };

    public void produce() {
        while (true) {
            synchronized (Goods.this) {
                while (是否不能生产) {//不满足生产条件
                    try {
                        Goods.this.wait();//释放锁,让出CPU
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //生产商品
                Goods.this.notifyAll();
            }
        }
    }

    public void consume() {
        while (true) {
            synchronized (Goods.this) {
                while (是否不能消费) {//不满足消费条件
                    try {
                        Goods.this.wait();//释放锁,让出CPU
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //消费商品
                Goods.this.notifyAll();
            }
        }
    }

    //开启任务
    public void start() {
        mProducerThread.start();
        mConsumerThread.start();
    }
}

以下是消费者的流程图,生产者也类似,


消费者.png
使用规则思考
wait、notify、notifyAll,为什么必须在同步代码中执行

(1)以上述代码为例,当不能继续生产或不能继续消费时,需要其他线程消费或者生成商品,则调用wait释放锁让出CPU,wait是释放锁,只有获得锁才能释放锁,没有获得锁哪来释放锁呢,另外,线程间协作通常会共享变量,对共享变量的操作放在同步代码块中,可以解决线程安全问题,所以wait必须在同步代码执行,否则会抛出java.lang.IllegalMonitorStateException: object not locked by thread before wait()异常;
(2)notify、notifyAll同理,生产完商品或消费完商品,需要唤醒其他线程并且释放锁,那么其他线程才有机会获得锁,所以在退出同步代码时调用notify、notifyAll,如果不在同步代码中执行,则抛出java.lang.IllegalMonitorStateException: object not locked by thread before notify();
(3)总结一点,没有获取锁,那就谈不上wait、notify、notifyAll;

notify和notifyAll区别

(1)notify,唤醒等待同一个对象的某个线程去竞争锁,至于是哪个线程获取锁,由CPU调度;
(2)notifyAll,唤醒等待同一个对象的所有线程去竞争锁,至于是哪个线程获取锁,由CPU调度,听起来跟notify一样;
(3)上述代码,将Goods.this.notifyAll()换成Goods.this.notify效果一样。假如,mConsumerThread消费者先获得锁进入同步代码,但是发现不能继续消费商品,则wait释放锁让出CPU处于等待状态,那么mProducerThread生产者线程竞争获得锁进入同步代码而且可以继续生产商品,则开始生产商品,生产完以后在退出同步代码之前调用notifyAll,那么mConsumerThread 就会被唤醒去竞争锁。试想着,在等待的只有mConsumerThread,所以无论是用notifyAll唤醒多个,还是notify唤醒某一个,效果都一样。如果是只有一个mConsumerThread和一个mProducerThread的情况,用notifyAll或notify都一样,因为在等待的只有一个。
如果是多个ConsumerThread或者多个
ProducerThread的情况,用notifyAll显然更适合。假如,mProducerThread生产完商品,希望唤醒消费者来消费,那么所有消费者都应该得到唤醒,如果使用notify,极端情况,只有其中某个消费者一直被唤醒消费,属于过得饱和,而其他消费者 过度饥饿,显示不是使用多线程的初衷;还有一种极端情况,还是只有其中某个消费者一直被唤醒,但是因为某个条件不满足,无法消费商品,而生产的商品已经满了,一直处于wait,而消费者一直不消费商品,任务无法继续执行下去,假死现象。
(4)永远是单个线程处于等待,用notify或notifyAll效果都一样,如果是多线程处于等待,显然用notifyAll更合适。

wait为什么必须在while循环内执行

上述代码的consume方法,将while改成if,当mProducerThread生产者生产完商品唤醒消费者,mConsumerThread获得锁不再判断消费条件是否成立,直接去消费商品,这样没有如何问题,因为很明确知道mConsumerThread被唤醒且获得锁,肯定是有商品可以消费,所以无需再判断条件是否成立。
但如果是以下场景则不同,
mConsumerThread1 与mProducerThread1,以及mConsumerThread2 与mProducerThread2是成对关系。
mConsumerThread1 只能消费mProducerThread1生产的商品,mConsumerThread2 只能消费mProducerThread2生产的商品。
当mConsumerThread1 、mConsumerThread2都不满足消费条件处于等待状态,这时mProducerThread1 生产了商品,调用notifyAll唤醒线程来消费,因为调用的是 notifyAll,所以mConsumerThread1、mConsumerThread2都会被唤醒,如果正好是mConsumerThread2获得了锁,需要重新判断条件是否满足,因为mConsumerThread2是跟mProducerThread2对应的,这时候while循环就起用了。
如果是有多组执行条件,唤醒存在假唤醒的情况(中断唤醒),那么用while循环显然更合适,这也是Java推荐的。

public class Goods {
    //生成者线程1
    private Thread mProducerThread1 = new Thread() {
        @Override
        public void run() {
            produce1();
        }
    };

    //消费者线程2
    private Thread mConsumerThread1 = new Thread() {
        @Override
        public void run() {
            consume1();
        }
    };

    //生成者线程2
    private Thread mProducerThread2 = new Thread() {
        @Override
        public void run() {
            produce2();
        }
    };

    //消费者线程2
    private Thread mConsumerThread2 = new Thread() {
        @Override
        public void run() {
            consume2();
        }
    };

    public void produce1() {
        while (true) {
            synchronized (Goods.this) {
                while (是否不能生产) {//不满足生成条件1
                    try {
                        Goods.this.wait();//释放锁,让出CPU
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //生成商品
                Goods.this.notifyAll();
            }
        }
    }

    public void consume1() {
        while (true) {
            synchronized (Goods.this) {
                while (是否不能消费) {//不满足消费条件1
                    try {
                        Goods.this.wait();//释放锁,让出CPU
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //消费商品
                Goods.this.notifyAll();
            }
        }
    }

    public void produce2() {
        while (true) {
            synchronized (Goods.this) {
                while (是否不能生产) {//不满足生产条件2
                    try {
                        Goods.this.wait();//释放锁,让出CPU
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //生成商品
                Goods.this.notifyAll();
            }
        }
    }

    public void consume2() {
        while (true) {
            synchronized (Goods.this) {
                while (是否不能消费商品) {//不满足消费条件2
                    try {
                        Goods.this.wait();//释放锁,让出CPU
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //消费商品
                Goods.this.notifyAll();
            }
        }
    }

    //开启任务
    public void start() {
        
        mProducerThread1.start();
        mConsumerThread1.start();

        mProducerThread2.start();
        mConsumerThread2.start();
    }
}
总结:

(1)wait、notify、notifyAll必须在同步代码中执行;
(2)wait必须在while循环内执行;
(3)notify比notifyAll更容易出现死锁。

以上分析有不对的地方,请指出,互相学习,谢谢哦!

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