一、线程间通信
线程间通信的模型有两种:共享内存 和 消息传递,以下方式都是基本这两种模型来实现的。
当调用线程 start() 方法后,是由操作系统来调度的,执行顺序是不固定的。
如果想让线程按照要求的顺序来执行,这就需要进行线程间通信。
二、多线程编程步骤(中)
第一步:创建资源类,在资源类创建数据和操作方法;
第二步:在资源类操作方法
(1)判断
(2)干活
(3)通知
第三步:创建多个线程,调用资源类的操作方法;
三、示例1
1、要求
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();
}
}
五、虚假唤醒问题
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 在哪里睡,就在哪里醒),就会出现虚假唤醒情况。
原理图:
正常情况:
四个线程时:
4、解决办法
//第一步,创建资源类,定义属性和操作方法
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();
}
}
六、多线程编程步骤(下)
第一步:创建资源类,在资源类创建数据和操作方法;
第二步:在资源类操作方法
(1)判断
(2)干活
(3)通知
第三步:创建多个线程,调用资源类的操作方法;
第四步:为了防止虚假唤醒用 while 判断;