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

一、线程间通信

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

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

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

 

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

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

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

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

 

三、示例1

四、示例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、运行结果

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

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

  3、分析原因

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

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

    看一下 wait() 方法说明:

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

 

 

     虚假唤醒:

    

    原理图:

    正常情况:

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

 

 

 

 

    四个线程时:

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

 

 

 

 

当 num = 1 的时候,加一的线程线程进来之后,判断不符合,就会阻塞在这里; 假如另一个加一的线程获取执行权,这是判断也不会符合,同时阻塞在这里; 当有一个减一的线程进来之后,执行完了之后,会把两个 加一 的线程同时唤醒, 当有一个加一的线程执行完成后,num=1,但此时是 if 判断,不会再次进行判断, 另外一个 加一的线程 也会 加一,num=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 判断;

 

 

 

上一篇:1716. 计算力扣银行的钱 分别求出整星期个不足星期的钱


下一篇:Python(2)