• 第三章:(1)线程间通信—Synchronized实现线程通信&虚假唤醒


    一、线程间通信

      线程间通信的模型有两种:共享内存 和 消息传递,以下方式都是基本这两种模型来实现的。

      当调用线程 start() 方法后,是由操作系统来调度的,执行顺序是不固定的。

      如果想让线程按照要求的顺序来执行,这就需要进行线程间通信。

    二、多线程编程步骤(中)

      第一步:创建资源类,在资源类创建数据和操作方法;

      第二步:在资源类操作方法
      (1)判断
      (2)干活
      (3)通知

      第三步:创建多个线程,调用资源类的操作方法;

    三、示例1

      1、要求

      两个线程,一个线程打印1-52,另一个打印字母A-Z打印顺序为12A34B...5152Z,要求用线程间通信

      2、代码实现

    class Resource {
    
        //信号量:当等于1 的时候输出输出字符,等于 0 输出数字
        private int flag= 1;
    
        private int count = 1;
    
        //打印数字
        public synchronized void printNum() throws InterruptedException {
            //判断
            while (flag != 1) {
                this.wait();
            }
    
            //干活
            System.out.println(2 * count - 1);
            System.out.println(2 * count);
    
            //通知
            flag = 0;
            this.notifyAll();
        }
    
        //打印字符
        public synchronized void printChar() throws InterruptedException {
            //判断
            while (flag != 0) {
                this.wait();
            }
    
            //干活
            System.out.println((char)(count - 1 + 'A'));
            count++;
    
            //通知
            flag = 1;
            this.notifyAll();
        }
    }
    
    public class Test {
    
        public static void main(String[] args) {
            Resource resource = new Resource();
    
            //创建多个线程,操作资源类
            new Thread(() -> {
                for (int i = 0; i < 26; i++) {
                    try {
                        resource.printNum();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "AA").start();
    
            new Thread(() -> {
                for (int i = 0; i < 26; i++) {
                    try {
                        resource.printChar();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "BB").start();
        }
    }

    四、示例2

      要求:有两个线程,实现对一个初始值是 0 的变量进行操作,一个线程对当前数值加 1,另一个线程对当前数值减 1,这两个线程交替完成效果,要求用线程间通信。

      Synchronized 实现:

    //第一步,创建资源类,定义属性和操作方法
    class Share {
        //初始值
        private int number = 0;
    
        //+1
        public synchronized void incr() throws InterruptedException {
            //第二步 判断,干活,通知
            //1.判断:number 值是否为0,如果不是0,等待
            if (number != 0) {
                this.wait();
            }
    
            //2.干活:如果 number 值是0,就 +1 操作
            number++;
            System.out.println(Thread.currentThread().getName() + "::" + number);
    
            //3.通知
            this.notifyAll();
    
        }
    
        //-1
        public synchronized void decr() throws InterruptedException {
            //第二步 判断,干活,通知
            //1.判断:number 值是否为1,如果不是1,等待
            if (number != 1) {
                this.wait();
            }
    
            //2.干活:如果 number 值是1,就 -1 操作
            number--;
            System.out.println(Thread.currentThread().getName() + "::" + number);
    
            //3.通知
            this.notifyAll();
        }
    
    }
    
    public class ThreadDemo1 {
    
        public static void main(String[] args) {
            //第三步  创建多个线程,调用资源类的操作方法;
            Share share = new Share();
    
            //创建线程
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.incr();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "AA").start();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.decr();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "BB").start();
        }
    }

    五、虚假唤醒问题

      synchronized 实现多线程通知等待
      对于上面 打印 0 和 1 的案例,如果换成4个线程会怎么样呢?

      1、修改为四个线程

    //第一步,创建资源类,定义属性和操作方法
    class Share {
        //初始值
        private int number = 0;
    
        //+1
        public synchronized void incr() throws InterruptedException {
            //第二步 判断,干活,通知
            //1.判断:number 值是否为0,如果不是0,等待
            if (number != 0) {
                this.wait();
            }
    
            //2.干活:如果 number 值是0,就 +1 操作
            number++;
            System.out.println(Thread.currentThread().getName() + "::" + number);
    
            //3.通知
            this.notifyAll();
    
        }
    
        //-1
        public synchronized void decr() throws InterruptedException {
            //第二步 判断,干活,通知
            //1.判断:number 值是否为1,如果不是1,等待
            if (number != 1) {
                this.wait();
            }
    
            //2.干活:如果 number 值是1,就 -1 操作
            number--;
            System.out.println(Thread.currentThread().getName() + "::" + number);
    
            //3.通知
            this.notifyAll();
        }
    
    }
    
    public class ThreadDemo1 {
    
        public static void main(String[] args) {
            //第三步  创建多个线程,调用资源类的操作方法;
            Share share = new Share();
    
            //创建线程
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.incr();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "AA").start();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.decr();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "BB").start();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.incr();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "CC").start();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.decr();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "DD").start();
        }
    }

      2、运行结果

        

        可以发现,这时的运行结果并不正确。
     

      3、分析原因

        换成了4个或多个线程会导致错误,虚假唤醒

        原因:使用了 if 判断,如果 if 判断生效了,就不会出现问题了,为什么 if 判断没有生效?

        看一下 wait() 方法说明:

         虚假唤醒原因:

        ① 当number = 1时,如果是 +1 的AA线程获取执行权,不符合条件,就会阻塞;

        ② 假设是 +1 的线程CC 获取执行权,进行 if 判断,不符合条件,就会堵塞;

        ③ 假设 -1 的线程 BB 获取执行权,执行完 -1,唤醒所有线程;

        ④ 此时 AA 线程被唤醒,不再进行 if 判断,执行 + 1,再唤醒其他线程;

        ⑤ 此时 CC 线程被唤醒,也不再进行 if 判断,执行 +1,此时 number =2;

        主要是在多线程判断时,使用了 if 判断。

        如果一个线程进入到了 if了,突然中断失去了控制权,等再次被唤醒,就不再进行验证,而是直接执行下面的代码(wait 在哪里睡,就在哪里醒),就会出现虚假唤醒情况。

        

        原理图

        正常情况:

        

        四个线程时:

        

        当 number = 1 的时候,加一的线程线程进来之后,判断不符合,就会阻塞在这里;
        假如另一个加一的线程获取执行权,这是判断也不会符合,同时阻塞在这里;
        当有一个减一的线程进来之后,执行完了之后,会把两个 加一 的线程同时唤醒,
        当有一个加一的线程执行完成后,number =1,但此时是 if 判断,不会再次进行判断,
        另外一个 加一的线程 也会 加一,number =2,所以就出现了虚假唤醒情况。

      4、解决办法

        wait() 应该使用在方法体中。
    //第一步,创建资源类,定义属性和操作方法
    class Share {
        //初始值
        private int number = 0;
    
        //+1
        public synchronized void incr() throws InterruptedException {
            //第二步 判断,干活,通知
            //1.判断:number 值是否为0,如果不是0,等待
            while (number != 0) {
                this.wait();  //在哪里睡,就会在哪里醒
            }
    
            //2.干活:如果 number 值是0,就 +1 操作
            number++;
            System.out.println(Thread.currentThread().getName() + "::" + number);
    
            //3.通知
            this.notifyAll();
    
        }
    
        //-1
        public synchronized void decr() throws InterruptedException {
            //第二步 判断,干活,通知
            //1.判断:number 值是否为1,如果不是1,等待
            while (number != 1) {
                this.wait();
            }
    
            //2.干活:如果 number 值是1,就 -1 操作
            number--;
            System.out.println(Thread.currentThread().getName() + "::" + number);
    
            //3.通知
            this.notifyAll();
        }
    
    }
    
    public class ThreadDemo1 {
    
        public static void main(String[] args) {
            //第三步  创建多个线程,调用资源类的操作方法;
            Share share = new Share();
    
            //创建线程
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.incr();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "AA").start();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.decr();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "BB").start();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.incr();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "CC").start();
    
            new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    try {
                        share.decr();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "DD").start();
        }
    }
        为了防止虚假唤醒用 while 判断
        注意:一定要注意多线程之间的虚假唤醒

    六、多线程编程步骤(下)

      第一步:创建资源类,在资源类创建数据和操作方法;

      第二步:在资源类操作方法
      (1)判断
      (2)干活
      (3)通知

      第三步:创建多个线程,调用资源类的操作方法;

      第四步:为了防止虚假唤醒用 while 判断;

     

     

  • 相关阅读:
    Winform Treeview 的按需加载
    Dynamic CRM 2013学习笔记(十)客户端几种查询数据方式比较
    Dynamic CRM 2013学习笔记(九)CrmFetchKit.js介绍:Fetchxml、多表联合查询, 批量更新
    Dynamic CRM 2013学习笔记(八)过滤查找控件 (类似省市联动)
    iOS Programming
    Hello Socket
    解决ARC的循环引用问题
    解决Eclipse下不自动拷贝apk到模拟器问题( The connection to adb is down, and a severe error has occured)
    解决Android NDK 报jxxx编译找不到
    做一个创建cocos2d-x新项目的shell脚本
  • 原文地址:https://www.cnblogs.com/niujifei/p/15820141.html
Copyright © 2020-2023  润新知