线程间的通信:当多个线程同时处理同一个资源时,但是线程各自处理的动作(线程的任务)却不同时,例如一个线程负责变量赋值,另一个线程同时负责打印赋值的变量,可能就会出现混乱的情况
例如:
public class InputThread implements Runnable {
// 不能创建对象,因为读取和写入的事同意对象
// 利用构造方法,传参时将资源类的对象传入
public void run() {
int a = 0;
Lock l = new ReentrantLock();
while (true) {
if (a % 2 == 0) {
Resource.r.name = "张三";
Resource.r.sex = "男";
} else {
Resource.r.name = "王五";
Resource.r.sex = "女";
}
a++;
}
}
}
这里我偶数赋值为 张三…男,奇数赋值为 王五…女,用两个线程,一个线程赋值,一个线程输出
public class OutputThread implements Runnable {
public void run() {
Lock l = new ReentrantLock();
while (true) {
System.out.println(Resource.r.name + "..." + Resource.r.sex);
Resource.r.falg = false;
}
}
}
结果就出现了"人妖"现象…
图中可以看出,已经完全的混乱了,张三和王五又是男又是女。
对于这种情况,Java中有一个重要的知识点,可以帮助我们解决这个问题,那就是–等待和唤醒机制,其实就是几个方法的使用。
(1) void wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
(2) void notify()
唤醒在此对象监视器上等待的单个线程。
(3)void notifyAll()
唤醒在此对象监视器上等待的所有线程。
注意:这三个方法都不是Thread类中的方法,而是属于Object基础类,即每个对象都有这三个方法,因为每个对象都有锁,锁是每个对象的基础,当然操作锁的方法也是最基础了。
当需要调用以上的方法的时候,一定要对竞争资源进行加锁,如果不加锁的话,则会报 IllegalMonitorStateException 异常。
接下来,这里用一个赋值打印的例子来完成等待和唤醒机制
资源类,这里也把他写成了一个锁
package cn.itcast.LianXi;
/*
* 资源类,作为数据的共享中心
* 属性,姓名和年龄
* 线程1 实现赋值
* 线程2 实现变量的打印
*/
public class Resource {
//变量写成公共的格式,这样方便调用
public String name;
public String sex;
public boolean flag = true;
//不让new,可以避免输入输出线程同时创建对象,导致的输入和输出对象不一致
//私有化资源类
private Resource(){}
//固定一个资源类的对象,不变化了
public static final Resource r = new Resource();
}
赋值线程
package cn.itcast.LianXi;
/*
* 线程1 用于实现对资源类的变量赋值
*线程需要同步技术,否则有安全隐患
*不用lock是因为,后面的wait()和notify()方法都需要同一对象锁调用
*写一个锁对象,私有化,静态不可变
*/
public class InputThread implements Runnable {
// 不能创建对象,因为读取和写入的事同意对象
// 利用构造方法,传参时将资源类的对象传入
public void run() {
int i = 0;
while (true) {
// 同步中用的就是私有化的资源锁,保证了使用的是同一个锁
synchronized (Resource.r) {
// 利用奇偶数实现不同进程调用
if (Resource.r.flag) {
try {
// 让打印线程等待
//注意要两个线程要用同一个锁
Resource.r.wait();
} catch (InterruptedException e) {
}
}
if (i % 2 == 0) {
Resource.r.name = "张三";
Resource.r.sex = "男";
} else {
Resource.r.name = "王五";
Resource.r.sex = "女";
}
// 赋值完成,将flag置为true,目的是让打印线程的if语句完成判断,开始打印
Resource.r.flag = false;
// 唤醒打印线程
Resource.r.notify();
i++;
}
}
}
}
打印线程
package cn.itcast.LianXi;
/*
* 输出线程,只需要负责资源类变量的输出
*/
public class OutputThread implements Runnable {
public void run() {
while (true) {
synchronized (Resource.r) {
// 同步中用的就是私有化的资源锁,保证了使用的是同一个锁
if (!Resource.r.flag) {
try {
// 让赋值线程等待,开始打印
//注意要两个线程要用同一个锁
Resource.r.wait();
} catch (InterruptedException e) {
}
}
System.out.println(Resource.r.name + " " + Resource.r.sex);
// 打印结束,将flag置为false,目的是让赋值线程的if完成判断,开始赋值
Resource.r.flag = false;
// 唤醒赋值线程
Resource.r.notify();
}
}
}
}
main
package cn.itcast.LianXi;
/*
* 此类用于开启线程,完成输入输出
*/
public class ThreadDemo {
public static void main(String[] args) {
//创建输入输出实现类
InputThread in = new InputThread();
OutputThread out = new OutputThread();
//创建线程
Thread t_in = new Thread(in);
Thread t_out = new Thread(out);
t_in.start();
t_out.start();
}
}
结果如图:
就不会发生人妖现象了 -.-