一、线程间通信
线程间通信的模型有两种:共享内存 和 消息传递,以下方式都是基本这两种模型来实现的。
当调用线程 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、运行结果
可以发现,这时的运行结果并不正确。
3、分析原因
换成了4个或多个线程会导致错误,虚假唤醒!
原因:使用了 if 判断,如果 if 判断生效了,就不会出现问题了,为什么 if 判断没有生效?
看一下 wait() 方法说明:
虚假唤醒:
原理图:
正常情况:
四个线程时:
当 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 判断;