Java多线程(3)——多线程通信(传智播客毕老师视频讲解)

多线程通信:

其实就是多个线程操作同一个资源,但操作动作不同。

1、安全问题

即由于非同步时,引起的安全问题,如下代码

public class Factory 
{
	private int num;
	private int value;
	private boolean flag=true;
	public  void setMessage(int num,int value)
	{
		try{Thread.sleep(100);}catch(Exception e){}
		//while(!flag)
			//try{wait();}catch(Exception e){}
		this.num=num;
		this.value=value;
		System.out.println(Thread.currentThread().getName()+"......."+"生产"+".."+this.num);
		flag=false;
		//notifyAll();
	}
	public  void getMessage()
	{
		try{Thread.sleep(100);}catch(Exception e){}
		//while(flag)
			//try{wait();}catch(Exception e){}
		System.out.println(Thread.currentThread().getName()+"......."+"消费"+"......."+this.num);
		flag=true;
		//notifyAll();
	}
}
public class Producer implements Runnable
{
	private Factory r;
	int num=0;
	int value=5;
	public  Producer(Factory r)
	{
		this.r=r;
	}
	public void run()
	{
		while(true)
		{
			r.setMessage(num++,value);
		}
	}
}
public class Consumer implements Runnable
{
	private Factory r;
	public Consumer(Factory r)
	{
		this.r=r;
	}
	public void run()
	{
		while(true)
		{
			r.getMessage();
		}
	}
}
public class Wakeup 
{
	public static void main(String arg[])
	{
		Factory r=new Factory();
		Producer r1=new Producer(r);//线程1
		Consumer r2=new Consumer(r);//线程2
		//Consumer r3=new Consumer(r);
		
		Thread t1=new Thread(r1);
		Thread t2=new Thread(r2);
		//Thread t3=new Thread(r3);
		
		t1.start();
		t2.start();	
		//t3.start();
	}
}
运行结果如下:


由结果可以看出,尚未生产8号时,就已经消费了8号,这不科学,所以要引用同步来解决上述问题,即在操作的方法上将其变为同步方法,修改部分代码如下:

public class Factory 
{
	private int num;
	private int value;
	private boolean flag=true;
	public  <span style="color:#ff0000;">synchronized</span> void setMessage(int num,int value)
	{
		try{Thread.sleep(100);}catch(Exception e){}
		//while(!flag)
			//try{wait();}catch(Exception e){}
		this.num=num;
		this.value=value;
		System.out.println(Thread.currentThread().getName()+"......."+"生产"+".."+this.num);
		flag=false;
		//notifyAll();
	}
	public <span style="color:#ff0000;">synchronized</span>  void getMessage()
	{
		try{Thread.sleep(100);}catch(Exception e){}
		//while(flag)
			//try{wait();}catch(Exception e){}
		System.out.println(Thread.currentThread().getName()+"......."+"消费"+"......."+this.num);
		flag=true;
		//notifyAll();
	}
}
结果如下:

这样是没问题了,可是也不能一直消费10啊,我想要生产一个消费一个

2、等待唤醒

wait();               等待,当线程调用了wait()方法时,它会释放掉对象的锁。

notify();            一般唤醒线程池中同一把锁的第一个等待线程

notifyAll();        唤醒线程池中同一把锁所有线程

都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才有锁;

为什么这些操作线程的方法要定义在Object类中呢?

因为这些方法在操作同步中线程时,都必须要标识它们所操作的线程只有锁,只有同一个锁上的被等

待线程,可以被同一个锁上的notify唤醒,不可以对不同锁上的线程进行唤醒;

也就是说,等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义

Object类中。

生产者消费者:

2.1

public class Factory 
{
	private int num;
	private int value;
	private boolean flag=true;      //生产消费标识符
	public  synchronized void setMessage(int num,int value)
	{
		try{Thread.sleep(100);}catch(Exception e){}
		if(!flag)               //如果flag为真,表示现在还没有生产,应进行生产;如果为假,表示现在有存货,等着消费,不进行生产
			try{this.wait();}catch(Exception e){}
		this.num=num;
		this.value=value;
		System.out.println(Thread.currentThread().getName()+"......."+"生产"+".."+this.num);
		flag=false;
		this.notify();
	}
	public synchronized  void getMessage()
	{
		try{Thread.sleep(100);}catch(Exception e){}
		if(flag)                 <span style="font-family: Arial, Helvetica, sans-serif;">//如果flag为真,表示现在还没有消费,应进行消费;如果为假,表示现在无存货,等着生产,不进行消费</span>
			try{this.wait();}catch(Exception e){}
		System.out.println(Thread.currentThread().getName()+"......."+"消费"+"......."+this.num);
		flag=true;
		this.notify();
	}
}
运行结果如下:

可见是生产一个消费一个,具体流程如下;

开启线程t1后,主线程可能也会离开开启线程t2,如果t2掌握了cpu执行权,则进入t2的run()方法中,然后调用r.getMessage()方法,由于该方法是同步方法,如果其有执行资格,便能进入该方法中,进入后,先上锁(this锁),然后判断flag,发现是true,于是进入this.wait(),放弃执行资格;然后线程t1就进入setMessage()方法中,判断flag,发现是true,不进入this.wait(),进行设定值的操作,然后在最后将标志flag设置为假,并用this.notify()唤醒线程t2;使得线程t2从之前的this.wait()语句后开始执行,不必判断flag,进行输出打印,然后在最后将flag标志设置为true,并用this.notify()唤醒线程t1(如果线程t1被wait()的话);依次不断重复……

2.2

在上述代码的基础上,再增加一个线程t3

public class Wakeup 
{
	public static void main(String arg[])
	{
		Factory r=new Factory();
		Producer r1=new Producer(r);//线程1
		Consumer r2=new Consumer(r);//线程2
		Consumer r3=new Consumer(r);
		
		Thread t1=new Thread(r1);
		Thread t2=new Thread(r2);
		Thread t3=new Thread(r3);
		
		t1.start();
		t2.start();	
		t3.start();
	}
}
结果运行如下:


发现一个生产有时变成了两个消费,这明显不对,其过程如下

  • 假设t1获取到执行权,判断flag,为true,不需要等待,开始生产,设置flag为false,norify完后,t1可能还持有执行权,则循环回来继续执行,判断flag为false,则进入wait(),t1放弃资格;
  • 此时t2、t3、t4都有可能抢到资格,假设t2抢到了,进来判断flag为false,则进入wait(),t2放弃资格;
  • 此时t3、t4有可能抢到资格,假设t3抢到了,进来判断flag为false,进行消费操作,然后设置flag为true,notify完后,把t1唤醒了,假设t3还持有执行权,循环回来继续执行,判断flag为true,进入wait();
  • 此时t4、t1有可能抢到资格,假设t4抢到了,进来判断flag为true,则进入wait(),t4放弃资格;
  • 此时就只t1有可能抢到资格,t1进来,不用判断flag,从wait()后开始生产,然后设置flag为false,notify完后,t2被唤醒,假设t1还持有执行权,循环回来继续执行,判断flag为false,进入wait();
  • 此时只有t2有资格,不用判断flag,从wait()后开始生产,然后设置flag为false,再notify。

然后我们程序就悲剧了,连续生产了两个;连续消费两个也是同理,原因就在于被唤醒后,不用判断flag,那么我们修改程序,用while代替if,让其被唤醒后依然判断flag,代码如下

public class Factory 
{
	private int num;
	private int value;
	private boolean flag=true;
	public  synchronized void setMessage(int num,int value)
	{
		try{Thread.sleep(10);}catch(Exception e){}
		<span style="color:#ff0000;">while</span>(!flag)
			try{this.wait();}catch(Exception e){}
		this.num=num;
		this.value=value;
		System.out.println(Thread.currentThread().getName()+"......."+"生产"+".."+this.num);
		flag=false;
		this.notify();
	}
	public synchronized  void getMessage()
	{
		try{Thread.sleep(100);}catch(Exception e){}
		<span style="color:#ff0000;">while</span>(flag)
			try{this.wait();}catch(Exception e){}
		System.out.println(Thread.currentThread().getName()+"......."+"消费"+"......."+this.num);
		flag=true;
		this.notify();
	}
}
运行结果如下:



然后发现,倒是生产一个消费一个了,但是只运行到1就不运行了,为什么呢?死锁!!!!

即在上述第5步中,t2被唤醒了,然后因为while判断flag为false,其也进入wait中,结果t1、t2、t3、t4均处于等待状态,则相当于死锁!

解决办法:notifyAll(),全部唤醒

代码如下:

public class Factory 
{
	private int num;
	private int value;
	private boolean flag=true;
	public  synchronized void setMessage(int num,int value)
	{
		try{Thread.sleep(10);}catch(Exception e){}
		while(!flag)
			try{this.wait();}catch(Exception e){}
		this.num=num;
		this.value=value;
		System.out.println(Thread.currentThread().getName()+"......."+"生产"+".."+this.num);
		flag=false;
		this.notifyAll();
	}
	public synchronized  void getMessage()
	{
		try{Thread.sleep(100);}catch(Exception e){}
		while(flag)
			try{this.wait();}catch(Exception e){}
		System.out.println(Thread.currentThread().getName()+"......."+"消费"+"......."+this.num);
		flag=true;
		this.notifyAll();
	}
}

此时正常了!




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值