zoukankan      html  css  js  c++  java
  • 并发编程大师系列之:wait/notify/notifyAll/condition

    1. wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。

    2. 调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)。

    3. 调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程。

    4. 调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程。

    5. 如果调用某个对象的wait()方法,当前线程必须拥有这个对象的锁,因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

    6. 调用某个对象的wait()方法,相当于让当前线程交出此对象的锁,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);

    7. notify()方法能够唤醒一个正在等待该对象锁的线程,当有多个线程都在等待该对象的锁的话,则只能唤醒其中一个线程,具体唤醒哪个线程由虚拟机确定。

    8. nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。

    *** 一个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。 ***

    例子:

    /**
     * @author 70KG
     * @Title: Test02
     * @Description: test
     * @date 2018/7/5下午9:49
     */
    public class Test02 {
    
        public static Object object = new Object();
    
        public static void main(String[] args) {
    
            // 启动两个线程
            Thread thread1 = new Thread1("1号");
            Thread thread2 = new Thread2("2号");
    
            thread1.start();
    
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            thread2.start();
        }
    
    
        // 线程1,处于等待状态
        static class Thread1 extends Thread {
    
            Thread1(String name) {
                this.setName(name);
            }
    
            @Override
            public void run() {
                synchronized (object) {
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                    }
                    System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁");
                }
            }
        }
    
        // 线程2调用notify
        static class Thread2 extends Thread {
    
            Thread2(String name) {
                this.setName(name);
            }
    
            @Override
            public void run() {
                synchronized (object) {
                    object.notify();
                    System.out.println("线程" + Thread.currentThread().getName() + "调用了object.notify()");
                }
                System.out.println("线程" + Thread.currentThread().getName() + "释放了锁");
            }
        }
    
    }

    结果都是一样的,验证了上面的内容。

    线程2号调用了object.notify()
    线程2号释放了锁
    线程1号获取到了锁

    notify和notifyAll例子:

    调用wait方法必须在同步块中进行。用线程来监听快递的信息,包括里程数的变化和地点的变化。

    /**
     * @author 70KG
     * @Title: Express
     * @Description: 快递类
     * @date 2018/7/4下午10:27
     */
    public class Express {
    
        // 始发地
        private final static String CITY = "ShangHai";
    
        // 里程变化
        private int km;
    
        // 地点变化
        private String site;
    
        Express() {
    
        }
    
        Express(int km, String site) {
            this.km = km;
            this.site = site;
        }
    
        // 里程数变化,会唤起线程
        public synchronized void changeKm() {
            this.km = 101;
            notify();
        }
    
        // 地点变化会唤起线程
        public synchronized void changeSite() {
            this.site = "BeiJing";
            notify();
        }
    
        // 用来监听里程数的变化
        public synchronized void waitKm() {
            while (this.km <= 100) {
                try {
                    wait();
                    System.out.println(Thread.currentThread().getId() + "-号监听===里程变化===的线程被唤醒了。。。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getId() + "-号监听===里程变化===的线程去做相应的事了");
        }
    
        // 用来监听地点的变化
        public synchronized void waitSite() {
            while (CITY.equals(this.site)) {
                try {
                    wait();
                    System.out.println(Thread.currentThread().getId() + "-号监听===地点变化===的线程被唤醒了。。。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getId() + "-号监听===地点变化===的线程去做相应的事了");
        }
    
    }
    /**
     * @author itachi
     * @Title: Test
     * @Description: 测试
     * @date 2018/7/4下午10:40
     */
    public class Test {
    
        // 初始化快递
        private static Express express = new Express(0, "ShangHai");
    
        // 用来监听里程数变化的线程
        static class CheckKm implements Runnable {
            @Override
            public void run() {
                express.waitKm();
            }
        }
    
        // 用来监听地点变化的线程
        static class CheckSite implements Runnable {
            @Override
            public void run() {
                express.waitSite();
            }
        }
    
        public static void main(String[] args) throws InterruptedException{
            // 启动三个线程去监听里程数的变化
            for (int i = 0; i <= 2; i++) {
                new Thread(new CheckKm()).start();
            }
    
            // 启动三个线程去监听地点的变化
            for (int i = 0; i <= 2; i++) {
                new Thread(new CheckSite()).start();
            }
    
            // 主线程睡眠一秒,异常信息抛出去
            Thread.sleep(1000);
    
            // 让快递的地点发生变化
            express.changeSite();
        }
    
    }

     结果:

    可见虽然是让地点发生了变化,但却随机唤醒了一个监听里程数变化的线程,并且使用整个程序处于无限等待状态。

    9-号监听===里程变化===的线程被唤醒了。。。

    如果将notify换成notifyAll的话,运行结果:

    三个监视地点的线程都被唤醒了,各自去做各自的事情了,未被唤醒的用来监听里程数变化的线程依然处于监听状态,因为它的里程数没有变化。

    14-号监听===地点变化===的线程被唤醒了。。。
    14-号监听===地点变化===的线程去做相应的事了
    13-号监听===地点变化===的线程被唤醒了。。。
    13-号监听===地点变化===的线程去做相应的事了
    12-号监听===地点变化===的线程被唤醒了。。。
    12-号监听===地点变化===的线程去做相应的事了
    11-号监听===里程变化===的线程被唤醒了。。。
    10-号监听===里程变化===的线程被唤醒了。。。
    9-号监听===里程变化===的线程被唤醒了。。。

    总结起来等待和通知的标准范式:

    等待方:

    1. 获取对象的锁

    2. 循环里面判断条件是否满足,不满足调用wait方法继续等待

    3. 条件满足的话就去执行相应的业务逻辑

    通知方:

    1. 获取对象的锁

    2. 改变条件

    3. 通知所有等待在对象上的线程

  • 相关阅读:
    关于<form:select>
    关于AJAX
    关于Listener
    分页显示数据----前端(将数据库中的信息分页显示到网页)
    分页显示数据----后台(将数据库中的信息分页显示到网页)
    关于Filter
    Myeclise下tomcat启动报错,启动超时
    启动tomcat一闪而过问题调试并解决
    MyEclipse快捷键大全【转】
    JSON和JS对象之间的互转【转】
  • 原文地址:https://www.cnblogs.com/zhangjianbing/p/9265910.html
Copyright © 2011-2022 走看看