zoukankan      html  css  js  c++  java
  • JAVA 多线程(6):等待、通知 (1)

    一、wait、notify、notifAll

    所有Object都有这三个方法。

    wait :当前线程等待锁(放弃当前线程持有的锁)

    notify:随机通知等待此锁的线程准备获取锁

    notifyALL:释放锁并通知所有等待此锁的线程

    整个等待与通知的过程,类似餐馆上菜,服务员等待菜,厨师(线程)做好菜(锁)通知服务员(线程)。

    在等待通知的过程中,必须要有锁的存在,也就是说必须要持有锁,才能进行等待与通知,如果没有则会出现异常。

    如下:

    public static void test(TestWait lock){
            synchronized (lock){
                System.out.println(Thread.currentThread().getName()+":"+"等菜");
                try {
                    lock.wait();
                    System.out.println(Thread.currentThread().getName()+":"+"上完了,再点菜");
                    lock.notify();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void test2(TestWait lock){
            synchronized (lock){
                System.out.println(Thread.currentThread().getName()+":"+"做好了");
                try {
                    lock.notify();
                    System.out.println(Thread.currentThread().getName()+":"+"等客人");
                    lock.wait();
                    System.out.println("厨师发现没有原料了,下班");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            TestWait testWait = new TestWait();
    
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                        test(testWait);
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    test2(testWait);
                }
            });
            t.start();
            Thread.sleep(1000);
            t2.start();
        }

    输出:

    由结果看出,线程1与线程2通过wait、notify 实现了通信(前提是锁一致)

    notify针对的是随机通知一个线程,如果争夺锁的线程太多,而又一个线程的优先级打个比方较低,有可能会一致都争夺不到锁。

    上面的这种可能性较低,但是如果调用notify次数不够怎么办,假设有A,B 2个线程调用wait方法放弃锁,然后等待,线程C 拿到锁后调用notify,那么线程A与线程B只有一个能获取到锁,另一个只能继续等待通知,等待一个永远获取不到的锁。

    这时候就必须通过notifyAll 来通知A和B,这样把wait状态的他们解放出来,加入到争夺锁的队列中去。

    还有一种情况就是通知过早,如果线程Await,线程Bnotify,锁为C,如果线程B先通知了,那么A 就永远在等待C锁了。

    如果遇到感觉可能会出现这种情况,最好加上一个标识用来判断是否有必要等待。

    private static boolean isWait = true;
    
        public static void test(TestWait lock){
            synchronized (lock){
                try {
                    // 还要不要等菜
                    if(isWait){
                        lock.wait();
                        System.out.println(Thread.currentThread().getName()+":"+"等菜");
                    }
                    System.out.println(Thread.currentThread().getName()+":"+"上完了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void test2(TestWait lock){
            synchronized (lock){
                System.out.println(Thread.currentThread().getName()+":"+"做好了");
                lock.notify();
                isWait = false;
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            TestWait testWait = new TestWait();
    
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                        test(testWait);
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    test2(testWait);
                }
            });
    
            Thread A = new Thread(t);
            t2.start();
            Thread.sleep(2000);
            A.start();
        }

    输出:

    结果看出,服务员不要再等待了,菜已经做好,直接上就行了。

    wait等待造成状态改变

    多个现在执行wait等待后,在某些条件改变后,有可能会出现意想不到的异常,如:

    private static boolean isWait = true;
        private static List list = new ArrayList();
    
        public static void test(TestWait lock){
            synchronized (lock){
                try {
                    // 还要不要等菜
                    if(isWait){
                if(list.size() == 0){   lock.wait();   System.out.println(Thread.currentThread().getName()
    +":"+"等菜");
                } } list.remove(
    0); System.out.println(Thread.currentThread().getName()+":"+"上完了"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void test2(TestWait lock){ synchronized (lock){ System.out.println(Thread.currentThread().getName()+":"+"做好了"); list.add("菜"); lock.notifyAll(); isWait = false; } } public static void main(String[] args) throws InterruptedException { TestWait testWait = new TestWait(); Thread t = new Thread(new Runnable() { @Override public void run() { test(testWait); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { test2(testWait); } }); Thread A = new Thread(t); A.start(); Thread B = new Thread(t); B.start(); Thread.sleep(2000); t2.start(); }

    输出:

     

    结果是越界了,因为厨师只做了一道菜。

    解决方法:轮询

    while (list.size() == 0){
                            System.out.println(Thread.currentThread().getName()+":"+"等菜");
                            lock.wait();
                        }

    输出:

    第二个服务员继续等菜。(此事程序未结束)

    生存者/消费者

    private String str = "";
        class P {
            public void setV(Object o){
                synchronized (o){
                    if(!str.equals("")){
                        try {
                            o.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    str = Math.random() + "";
                    System.out.println("Set的值:"+str);
                    o.notifyAll();
                }
    
            }
        }
    
        class C {
            public void getV(Object o){
                synchronized (o){
                    if(str.equals("")){
                        try {
                            o.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("Get的值:"+str);
                    str = "";
                    o.notifyAll();
                }
            }
        }
    
        public static void main(String[] args){
            Object lock = new Object();
            TestPC pc = new TestPC();
            P p = pc.new P();
            C c = pc.new C();
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        c.getV(lock);
                    }
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        p.setV(lock);
                    }
                }
            });
    
            t.start();
            t2.start();
        }

     输出:

     上面是1对1,所以使用notify也是可以的,但是如果为多生产和多消费的话,就必须使用notifyAll 了。

    还有如果对应的是1生产多消费,那么除了使用notifyAll 还要使用轮询机制,也就是while,类似上面的2个服务员一个厨师的例子。

    成灰之前,抓紧时间做点事!!
  • 相关阅读:
    将model注册进单例中,每次用的时候从单例里面取
    构建ASP.NET MVC4&JQuery&AJax&JSon示例
    ajax用法
    MVC 后台向前台传值,同一Controller下Action之间的传值,Controller与Controller之间的传值
    mvc5入门指南
    在EF中做数据索引
    json to Object
    ajax请求后返回的时间转换格式
    Target JRE version (1.8.0_101) does not match project JDK version (unknown), will use sources from JDK: 1.8
    Idea 配置启动JDK___在windows中使用Intellij Idea时选择自定义的64位JVM
  • 原文地址:https://www.cnblogs.com/jony-it/p/10816809.html
Copyright © 2011-2022 走看看