(51)线程间通信,等待唤醒机制
**线程间通讯:
其实就是多个线程在在操作同一资源。
但是操作功能不同**
功能简介:有个资源类,输入类就是向资源类中输入男、女信息,输出类就是输出相匹配的男女信息。创建多个线程,执行代码
public class Res {
String name;
String sex;
}
public class Input implements Runnable {
private Res r;
Input(Res r){
this.r=r;
}
@Override
public void run() {
// TODO Auto-generated method stub
int x=0;
while(true) {
synchronized(r) {
if(x==0) {
r.name="make";
r.sex="man";
}
else {
r.name="丽丽";
r.sex="女女女女女";
}
x=(x+1)%2;}
}
}
}
public class Output implements Runnable {
private Res r;
Output(Res r){
this.r=r;
}
@Override
public void run() {
while(true) {
synchronized(r) {
System.out.println(r.name+"--------"+r.sex);
}
}
}
}
public class InputOutputDemo {
public static void main(String[] args) {
Res r=new Res();
Input in=new Input(r);
Output out=new Output(r);
Thread t1=new Thread(in);
Thread t2=new Thread(out);
t1.start();
t2.start();
}
}
因为代码较长,所以这里就只说明注意事项
①共享资源是name,sex,所以要将Input、Output类中的run方法相关部分写成同步代码块
②对于synchronized中的参数,因为只有一个共享资源,所以应该用同一把锁,各种创建Object对象,不正确。应该选取不变的东西,可以用r(只要不变的对象就行)
从以上的输出结果中可以看出,一组数据会重复打印多次,原因是:
资源类只有两个变量,输入类输入数据,输入线程获得cpu,是个数据 不停覆盖的过程,当输出线程获得cpu后,打印的是输入的最后一组数据,因为输出线程会占一段时间的cpu,所以会打印这个数据多次。
但是这样的话,输出相同数据成片,不太易于观看,那么输入一组数据,打印这组数据,重复进行,该怎么做呢?引入等待唤醒机制
wait:
notify:
notifyall:
都要使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用同步中,因为只有同步才有锁。
为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操作同步中的线程时,都必须要标识他们所操作线程才有的锁。
只有同一个锁上的被等待线程,可以被同一把锁上的notify唤醒。
也就是说,等待和唤醒必须是同一个锁.
锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中
package com.Thread_communction;
public class Res {
String name;
String sex;
boolean flag=false;
}
public class Input implements Runnable {
private Res r;
Input(Res r){
this.r=r;
}
@Override
public void run() {
// TODO Auto-generated method stub
int x=0;
while(true) {
synchronized(r) {
if(r.flag)
try {r.wait();} catch (Exception e) {}
if(x==0) {
r.name="make";
r.sex="man";
}
else {
r.name="丽丽";
r.sex="女女女女女";
}
x=(x+1)%2;
r.flag=true;
r.notify();
}
}
}
}
public class Output implements Runnable {
private Res r;
Output(Res r){
this.r=r;
}
@Override
public void run() {
while(true) {
synchronized(r) {
if(!r.flag)
try {r.wait();} catch (Exception e) {}
System.out.println(r.name+"--------"+r.sex);
r.flag=false;
r.notify();
}
}
}
}
因为单纯的抽象描述比较难,所以把写好的代码关键部分进行分析:
输入线程:当输入线程执行run后,if(r.flag)为假,不用等待,因为资源池中没数据,输入一组数据,因为该线程会持有一段时间的cpu,所以还会输入覆盖原来的数据,为了避免这种情况,r.flag=true来标识资源区已经有数据,不用再输入数据,唤醒输出线程,可以打印数据,然后继续执行输入线程,判断if(r.flag)为假,等待,等待输出线程输出一组数据后唤醒该输入线程。
**输出线程:**if(!flag)为真,即资源池没数据,因为刚才的输入线程已经置flag为真,所以可以输出数据,相当于资源池为空,即r.flag=false;唤醒输入线程可以输入数据了。
这样就能实现每组数据输出一次,交替输出。
对此代码进行优化:让Res中的成员变量设为private,对外提供访问方法
public class Res {
private String name;
private String sex;
private boolean flag=false;
public synchronized void set(String name,String sex) {
if(flag)
try {this.wait();} catch (Exception e) {}
this.name=name;
this.sex=sex;
flag=true;
this.notify();
}
public synchronized void out() {
if(!flag)
try {this.wait();} catch (Exception e) {}
System.out.println(name+"-------"+sex);
flag=false;
this.notify();
}
}
public class Input implements Runnable {
private Res r;
Input(Res r){
this.r=r;
}
public void run() {
int x=0;
while(true) {
synchronized(r) {
if(x==0)
r.set("make", "man");
else
r.set("丽丽", "女女女女");
x=(x+1)%2;
}
}
}
}
public class Output implements Runnable {
private Res r;
Output(Res r){
this.r=r;
}
@Override
public void run() {
while(true) {
synchronized(r) {
r.out();
}
}
}
}
public class InputOutputDemo {
public static void main(String[] args) {
Res r=new Res();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}