- Input(往内存数据)与Output(往外取数据)线程不一样。
/*
需求:有一堆媒,有一卡车来拉煤,还有一卡车送煤来;
分析:煤堆就是一个对象;一辆卡车就是一个线程。
类似的:一个仓库,存放注册者的姓名和性别,分别有注册和输出两个线程。
*/
class Res{
String name;
String sex;
}
class Input implements Runnable{
private Res r; //不搞对象,直接引用。这里也可以用单例实现!
Input(Res r){
this.r = r;
}
public void run(){
int x = 0;
while(true){
if(x == 0){
r.name = "mike";
r.sex = "man";
}
else{
r.name = "Lily";
r.sex = "woman";
}
x = (x+1)%2;
}
}
}
class Output implements Runnable{
private Res r ;
Output(Res r){
this.r = r;
}
public void run(){
while(true){
System.out.println(r.name+" "+r.sex);
}
}
}
class InputOutputDemo{
public static void main(String[] args){
Res r = new Res();
Input in = new Input(r);
Output ou = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(ou);
t1.start();
t2.start();
}
}
-
结果:
image.png- 可以看出两个线程沟通不够,存在安全问题。
- 究其原因:就是没有加锁,两个线程分别争抢执行赋值和打印代码,刚赋值完姓名还没赋值性别就被打印了,从而出现姓名性别错乱问题。
- 解决办法
/*
需求:有一堆媒,有一卡车来拉煤,还有一卡车送煤来;
分析:煤堆就是一个对象;一辆卡车就是一个线程。
类似的:一个仓库,存放注册者的姓名和性别,分别有注册和输出两个线程。
*/
class Res{
String name;
String sex;
}
class Input implements Runnable{
private Res r; //不搞对象,直接引用。这里也可以用单例实现!
Input(Res r){
this.r = r;
}
public void run(){
int x = 0;
while(true){
synchronized(Input.class) //之所以用Input.class作为对象,而没有用其他比如Object类对象,是因为保证两个线程使用的是同一个锁!
if(x == 0){ //其实这里用 r 比较合适,r也是唯一的。
r.name = "mike";
r.sex = "man";
}
else{
r.name = "Lily";
r.sex = "woman";
}
x = (x+1)%2;
}
}
}
}
class Output implements Runnable{
private Res r ;
Output(Res r){
this.r = r;
}
public void run(){
while(true){
synchronized(Input.class){ //添加锁,解决代码同步问题。
System.out.println(r.name+" "+r.sex);
}
}
}
}
class InputOutputDemo{
public static void main(String[] args){
Res r = new Res();
Input in = new Input(r);
Output ou = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(ou);
t1.start();
t2.start();
}
}
- 但现在,还有问题:并没有实现输入一个后就不能再次输入,只有仓库里面有注册者时才可以输出;
- 下面进行解决:我们可以设置一个flag来标示仓库里面有没有资源!同时设置等待、唤醒让两个线程接替执行同步代码块,防止死锁;
wait():
notiyf():
notiyfAll():唤醒全部线程。
- 都使用在同步中,因为要对监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才有锁。
为什么这些操作线程的方法要定义Object类中呢?
- 任意对象能定义的方法只能定义在上帝(Object)类中,任意对像都继承Object类。
- 因为这些方法在操作同步中的线程时,都必须要标识它们锁操作的线程只有一个锁,
- 只有同一个锁上的被等待的线程,可以被同一个锁上notify唤醒。
- 不可对不同的锁中的线程进行唤醒。
- 也就是说,等待和唤醒必须是同一个锁。
- 而锁可以时任意对象,所以可以被任意对象调用的方法定义Object类中。
/*
需求:有一堆媒,有一卡车来拉煤,还有一卡车送煤来;
分析:煤堆就是一个对象;一辆卡车就是一个线程。
类似的:一个仓库,存放注册者的姓名和性别,分别有注册和输出两个线程。
*/
class Res{
String name;
String sex;
boolean flag = false; //设置flag来表示仓库里面有没有资源。
}
class Input implements Runnable{
private Res r; //不搞对象,直接引用。这里也可以用单例实现!
Input(Res r){
this.r = r;
}
public void run(){
int x = 0;
while(true){
synchronized(r){
if(r.flag) //使用wait(),notify()交替执行,保证输入、输出两个线程交替执行,不会出现死锁的问题!
try{r.wait();} catch(Exception e){} //wait()方法在Object类中,不在Thread()类中!Thread继承了Object类的。
if(x == 0){
r.name = "mike";
r.sex = "man";
}
else{
r.name = "Lily";
r.sex = "woman";
}
x = (x+1)%2;
r.flag = true;
r.notify(); //唤醒线程池中的线程,一般是线程池里的第一个线程。
}
}
}
}
class Output implements Runnable{
private Res r ;
Output(Res r){
this.r = r;
}
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();
}
}
}
}
class InputOutputDemo{
public static void main(String[] args){
Res r = new Res();
Input in = new Input(r);
Output ou = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(ou);
t1.start();
t2.start();
}
}
-
结果:完全OK了!!!!
- image.png
-
代码优化(代码优化是程序建设中非常关键的一步,不能偷懒!):
class Res{ //set()和out()都是对本类对象。
String name;
String sex;
boolean flag = false;
public synchronized void set(String name, String sex){
if(this.flag)
try{this.wait();} catch(Exception e){}
this.name = name;
this.sex = sex;
this.flag = true;
this.notify();
}
public synchronized void out(){
if(!this.flag)
try{this.wait();} catch(Exception e){}
System.out.println(this.name+" "+this.sex);
this.flag = false;
this.notify();
}
}
class Input implements Runnable{
private Res r; //对象引用,不创建对象,对象由外面导入。
Input(Res r){
this.r = r;
}
public void run(){
int x = 0;
while(true){
if(x == 0){
r.set("mike","man");
}
else{
r.set("Lily","woman");
}
x = (x+1)%2;
}
}
}
class Output implements Runnable{
private Res r ;
Output(Res r){
this.r = r;
}
public void run(){
while(true){
r.out();
}
}
}
class InputOutputDemo1{
public static void main(String[] args){
Res r = new Res();
new Thread(new Input(r)).start(); //线程对象都用匿名对象类创建对象,启动线程。
new Thread(new Output(r)).start();
}
}
网友评论