Java多线程之线程交互

        线程交互是指两个线程之间通过通信联系对锁的获取与释放,从而达到较好的线程运行结果,避免引起混乱的结果。一般来说synchronized块的锁会让代码进入同步状态,即一个线程运行的同时让其它线程进行等待,那么如果需要进行实现更复杂的交互,则需要学习以下几个方法:

void notify():唤醒在此对象监视器上等待的单个线程。

void notifyAll():唤醒在此对象监视器上等待的所有线程。

void wait():让占用了这个同步对象的线程,临时释放当前的占用,并且等待。

        wait()方法是使当前线程临时暂停,释放锁,并进入等待,其功能类似于sleep()方法,但是wait()需要释放锁,而sleep()不需要释放锁。以下通过例子说明wait()方法实现线程间的交互,举例子之前需要强调的是wait()方法一定存在于synchronized作用范围内,否则报错,因为它是同步对象上的方法。首先定义一个学生类:

1.理解wait()和notify()的相互关系

        首先该类有两个同步方法,存钱和取钱,首先需要保证当取钱取到只剩0元时,当前取钱的线程应该进入等待状态,此时调用同步对象的notify状态,我们为了观察notify()方法的作用,首先不使用它,进行对比:

package person;
import java.util.concurrent.locks.*;
public class Student {
	public int k=0;
	public int total;
	public int perMonth;
	public String name;
	Lock lock=new ReentrantLock();
	public synchronized void saveMoney(Student s) {
		// TODO Auto-generated method stub
		
		s.total=s.total+2*(s.perMonth);
		System.out.println(s.name+"存了"+2*s.perMonth+"当前总共拥有"+s.total+"元");
		
		
		
	}
	public synchronized void getMoney(Student s)
	{
		if(total<=0)
		{
			try
			{
				this.wait();//让占有this的取钱过程,暂时释放对this的占有,并等待,等待其它线程的调用同步对象的notify方法来唤醒它。
			}
			catch(InterruptedException e) {
				e.printStackTrace();
			}
		}

		s.total=s.total-s.perMonth;
		System.out.println(s.name+"取了"+s.perMonth+"当前总共拥有"+s.total+"元");	


	}

}

测试程序如下,这个测试程序在下文中不改变:

package waytobuildthread;

import person.Student;

public class RunnableTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		final Object obj=new Object();
		final Student s=new Student();
		final Student jack=new Student();
		s.total=1000;
		s.perMonth=200;
		s.name="Tom";
		System.out.println("Tom最开始拥有"+s.total+"元");
		Thread[] save=new Thread[6];
		Thread[] get=new Thread[10];
		for(int i=0;i<10;i++)
		{
			Thread t1= new Thread(){
	            public void run(){
	            	
	            	
	            	s.getMoney(s);;
	            	
	        		try {
	        			Thread.sleep(1000);;
	        		}
	        		catch(InterruptedException e) {
	        			e.printStackTrace();
	        		}
	            	
	            }
	        };
	        t1.start();
	        get[i]=t1;
		}
		
		for(int i=0;i<6;i++)
		{
			Thread t2= new Thread(){
	            public void run(){
	            	
	            	s.saveMoney(s);
	            	
	            	
	        		try {
	        			Thread.sleep(1000);;
	        		}
	        		catch(InterruptedException e) {
	        			e.printStackTrace();
	        		}
	            	
	            }
	        };
	        t2.start();
	        save[i]=t2;
		}
		//等待线程结束
		for(Thread t:save)
		{
			try
			{
				t.join();
				
			}
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}

		}
		//等待线程结束
		for(Thread t:get)
		{
			try
			{
				t.join();
			}
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}

		}
		
		System.out.println("最后Tom总共拥有"+s.total+"元");
		
	}

}

运行结果如下:

Tom最开始拥有1000元
Tom取了200当前总共拥有800元
Tom取了200当前总共拥有600元
Tom取了200当前总共拥有400元
Tom取了200当前总共拥有200元
Tom取了200当前总共拥有0元
Tom存了400当前总共拥有400元
Tom存了400当前总共拥有800元
Tom存了400当前总共拥有1200元
Tom存了400当前总共拥有1600元
Tom存了400当前总共拥有2000元
Tom存了400当前总共拥有2400元

        从上面结果可以看出,当取钱取到只剩0元时,取钱线程便释放对this的占有,进入等待,但是由于没有唤醒等待线程的方法notify()方法,所以取钱线程便不会被唤醒,因此剩余的取钱线程不会被执行。那么下面我们在存钱方法中添加notify()方法,用于唤醒等待中的取钱方法:

package person;
import java.util.concurrent.locks.*;
public class Student {
	public int k=0;
	public int total;
	public int perMonth;
	public String name;
	Lock lock=new ReentrantLock();
	public synchronized void saveMoney(Student s) {
		// TODO Auto-generated method stub
		
		s.total=s.total+2*(s.perMonth);
		System.out.println(s.name+"存了"+2*s.perMonth+"当前总共拥有"+s.total+"元");
		this.notify();//用于唤醒暂停中的线程
		
		
		
	}
	public synchronized void getMoney(Student s)
	{
		if(total<=0)
		{
			try
			{
				this.wait();//让占有this的取钱过程,暂时释放对this的占有,并等待,等待其它线程的调用同步对象的notify方法来唤醒它。
			}
			catch(InterruptedException e) {
				e.printStackTrace();
			}
		}

		s.total=s.total-s.perMonth;
		System.out.println(s.name+"取了"+s.perMonth+"当前总共拥有"+s.total+"元");	


	}

}

运行测试程序,结果如下:

Tom最开始拥有1000元
Tom取了200当前总共拥有800元
Tom取了200当前总共拥有600元
Tom取了200当前总共拥有400元
Tom取了200当前总共拥有200元
Tom取了200当前总共拥有0元
Tom存了400当前总共拥有400元
Tom存了400当前总共拥有800元
Tom取了200当前总共拥有600元
Tom存了400当前总共拥有1000元
Tom取了200当前总共拥有800元
Tom取了200当前总共拥有600元
Tom取了200当前总共拥有400元
Tom存了400当前总共拥有800元
Tom存了400当前总共拥有1200元
Tom取了200当前总共拥有1000元
Tom存了400当前总共拥有1400元
最后Tom总共拥有1400元

        对比两者关系可以看出notify的作用,上面例子说明当取钱取到只剩0元时,便会进入暂停,此时等待着的存钱线程便会被执行,每次执行之后便会唤醒暂停着的线程,使其进入等待状态。这就是notify()的作用。

2.深入交互

        以上只是对取钱进行了临时暂停,释放占有,那么接下来如果对存钱过程也添加wait()方法又会怎样的呢?以下便是对其进行了两个同步方法都添加了暂停与唤醒功能,如下所示:

package person;
import java.util.concurrent.locks.*;
public class Student {
	public int k=0;
	public int total;
	public int perMonth;
	public String name;
	Lock lock=new ReentrantLock();
	public synchronized void saveMoney(Student s) {
		// TODO Auto-generated method stub
		if(total>=2000)
		{
			try
			{
				this.wait();
			}
			catch(InterruptedException e) {
				e.printStackTrace();
			}
		}
		s.total=s.total+2*(s.perMonth);
		System.out.println(s.name+"存了"+2*s.perMonth+"当前总共拥有"+s.total+"元");
		this.notify();
		
		
	}
	public synchronized void getMoney(Student s)
	{
		if(total<=0)
		{
			try
			{
				this.wait();
			}
			catch(InterruptedException e) {
				e.printStackTrace();
			}
		}

		s.total=s.total-s.perMonth;
		System.out.println(s.name+"取了"+s.perMonth+"当前总共拥有"+s.total+"元");	
		this.notify();


	}

}

运行结果如下:

Tom最开始拥有1000元
Tom取了200当前总共拥有800元
Tom取了200当前总共拥有600元
Tom取了200当前总共拥有400元
Tom取了200当前总共拥有200元
Tom取了200当前总共拥有0元
Tom存了400当前总共拥有400元
Tom取了200当前总共拥有200元
Tom取了200当前总共拥有0元
Tom取了200当前总共拥有-200元
Tom存了400当前总共拥有200元
Tom取了200当前总共拥有0元
Tom存了400当前总共拥有400元
Tom存了400当前总共拥有800元
Tom存了400当前总共拥有1200元
Tom取了200当前总共拥有1000元
Tom存了400当前总共拥有1400元
最后Tom总共拥有1400元

        观察上述结果,会发现结果并未如我们期待那样,出现了一个负值,这是为什么呢因为当取钱取到只剩0时,接下去又调用了一个notify()方法,可能唤醒了另一个取钱线程,即继续取钱,所以会出现负值,那么怎么解决这一问题呢?很简单,就是将if改为while,表示如果账户余额如果不大于0,则不会取钱。则一直等待。同理存钱有上限,所以如果余额不小于上限,那么也会一直等待。因此,修改如下所示:

package person;
import java.util.concurrent.locks.*;
public class Student {
	public int k=0;
	public int total;
	public int perMonth;
	public String name;
	Lock lock=new ReentrantLock();
	public synchronized void saveMoney(Student s) {
		// TODO Auto-generated method stub
		while(total>=2000)
		{
			try
			{
				this.wait();
			}
			catch(InterruptedException e) {
				e.printStackTrace();
			}
		}
		s.total=s.total+2*(s.perMonth);
		System.out.println(s.name+"存了"+2*s.perMonth+"当前总共拥有"+s.total+"元");
		this.notify();//唤醒那些等待着取钱的过程,可以醒过来了。
		
		
	}
	public synchronized void getMoney(Student s)
	{
		while(total<=0)
		{
			try
			{
				this.wait();//让占有this的取钱过程,暂时释放对this的占有,并等待,等待其它线程的调用同步对象的notify方法来唤醒它。
			}
			catch(InterruptedException e) {
				e.printStackTrace();
			}
		}

		s.total=s.total-s.perMonth;
		System.out.println(s.name+"取了"+s.perMonth+"当前总共拥有"+s.total+"元");	
		this.notify();


	}

}
测试结果如下:
Tom最开始拥有1000元
Tom取了200当前总共拥有800元
Tom取了200当前总共拥有600元
Tom取了200当前总共拥有400元
Tom取了200当前总共拥有200元
Tom取了200当前总共拥有0元
Tom存了400当前总共拥有400元
Tom取了200当前总共拥有200元
Tom存了400当前总共拥有600元
Tom取了200当前总共拥有400元
Tom取了200当前总共拥有200元
Tom存了400当前总共拥有600元
Tom取了200当前总共拥有400元
Tom存了400当前总共拥有800元
Tom取了200当前总共拥有600元
Tom存了400当前总共拥有1000元
Tom存了400当前总共拥有1400元
最后Tom总共拥有1400元
        根据上述结果可以知道,我们在进行多线程交互时,如果要对共享数据有约束条件,需要注意对wait()方法与notify()方法使用的时机与条件。    
        总之,wait()、notify()、notifyAll()三个方法必须在synchronized代码块中使用,否则会报错,另外,如果使用了wait()方法,如果想再次使用该方法,当前同步方法,则需要使用notify()唤醒。
  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值