JAVA 多线程通信 详解与举例

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yulei_qq/article/details/8978456

1.使用Object类的方法来实现线程之间的通信

为了实现线程通信,我们可以使用Object类提供的wait()、notify()、notifyAll()三个方法。调用wait()方法会释放对该同步监视器的锁定这三个方法必须由同步监视器对象来调用,这可分成两种情况:

  • 对于使用synchronized修饰的同步方法,因为该类的默认实例是(this)就是同步监视器,所以可以直接调用这三使用个方法。
  • 对于synchronized修饰的同步代码块,同步监视器是synchronized括号里的对象,所以必须使用该对象调用这三个方法。

这次我们还是拿银行账户的取钱、存款来作为本文的例子演示。

假设系统中有两条线程,这两条线程分别代表取钱者和存钱者。现在系统有一种特殊的要求,系统要求存款者和取钱者不断的实现存款和取钱动作,而且要求每当存款者将钱存入指定账户后,取钱者立即将钱取走.不允许存款者两次存钱,也不允许取钱者两次取钱.

我们通过设置一个旗标来标识账户中是否已有存款,有就为true,没有就标为false。具体代码如下:

首先我们定义一个Account类,这个类中有取钱和存钱的两个方法,由于这两个方法可能需要并发的执行取钱、存钱操作,所有将这两个方法都修改为同步方法.(使用synchronized关键字)。

public class Account {
	private String accountNo;
	private double balance;
	//标识账户中是否有存款的旗标
	private boolean flag=false;
	
	public Account() {
		super();
	}

	public Account(String accountNo, double balance) {
		super();
		this.accountNo = accountNo;
		this.balance = balance;
	} 
	
	public synchronized void draw (double drawAmount){
		
		try {
			  if(!flag){
			  this.wait();
		     }else {
		    	 //取钱
		    	 System.out.println(Thread.currentThread().getName()+" 取钱:"+drawAmount);
		    	 balance=balance-drawAmount;
		    	 System.out.println("余额 : "+balance);
		    	 //将标识账户是否已有存款的标志设为false
		    	 flag=false;
		    	 //唤醒其它线程
		    	 this.notifyAll();    	 
		     }
	      	} catch (Exception e) {
	      		e.printStackTrace();
	  	}
	}
	
	
   public synchronized void deposit(double depositAmount){
	  try {
			  if(flag){
				this.wait();
	          }
			  else{
				  System.out.println(Thread.currentThread().getName()+"存钱"+depositAmount);
				  balance=balance+depositAmount;
				  System.out.println("账户余额为:"+balance);
				  flag=true;
				  //唤醒其它线程
				  this.notifyAll();
			  }
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
   }

}

接下来创建两个线程类,分别为取钱和存钱线程!

取钱线程类:

public class DrawThread implements Runnable {

	private Account account;
	private double drawAmount;
	
	
	public DrawThread(Account account, double drawAmount) {
		super();
		this.account = account;
		this.drawAmount = drawAmount;
	}

	public void run() {
		for(int i=0;i<100;i++){
		   account.draw(drawAmount);  	
		}
	}
}

存钱线程类:

 

public class depositThread implements Runnable{
    private Account account;
    private double depositAmount;
	 
	public depositThread(Account account, double depositAmount) {
		super();
		this.account = account;
		this.depositAmount = depositAmount;
	}


	public void run() {
	for(int i=0;i<100;i++){
		 account.deposit(depositAmount);
	  }
	}

}

最后我们测试一下这个取钱和存钱的操作!
 

 

public  class TestDraw {

	public static void main(String[] args) {
		//创建一个账户
		Account account=new Account();
	    new Thread(new DrawThread(account, 800),"取钱者").start();
	    new Thread(new depositThread(account, 800),"存款者甲").start();
	    new Thread(new depositThread(account, 800),"存款者乙").start();
	    new Thread(new depositThread(account, 800),"存款者丙").start();

	}

}

大致的输出结果:


 

存款者甲存钱800.0
账户余额为:800.0
取钱者 取钱:800.0
余额 : 0.0
存款者丙存钱800.0
账户余额为:800.0
取钱者 取钱:800.0
余额 : 0.0
存款者甲存钱800.0
账户余额为:800.0
取钱者 取钱:800.0
余额 : 0.0
存款者丙存钱800.0
账户余额为:800.0
取钱者 取钱:800.0
余额 : 0.0
存款者甲存钱800.0
账户余额为:800.0
取钱者 取钱:800.0
余额 : 0.0
存款者丙存钱800.0
账户余额为:800.0
取钱者 取钱:800.0
余额 : 0.0
存款者甲存钱800.0
账户余额为:800.0
取钱者 取钱:800.0
余额 : 0.0
存款者丙存钱800.0
账户余额为:800.0
取钱者 取钱:800.0
余额 : 0.0
存款者甲存钱800.0
账户余额为:800.0
取钱者 取钱:800.0
余额 : 0.0

由上述结果我们可以看到,存款者线程和取钱者线程交替执行的情形。至此简单的线程通信问题已经模拟完毕!

2.使用java.util.concurrent.locks下的Condition类来实现线程间的通信

如何程序不使用synchronized关键字来保持同步,而是直接适用Lock对像来保持同步,则系统中不存在隐式的同步监视器对象,也就不能使用wait()、notify()、notifyAll()来协调线程的运行.

当使用LOCK对象保持同步时,JAVA为我们提供了Condition类来协调线程的运行。关于Condition类,JDK文档里进行了详细的解释.,再次就不啰嗦了。

我们就拿Account类进行稍微的修改 一下吧!

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Account {
	
	//显示定义Lock对象
	private final Lock lock=new ReentrantLock();
	//获得指定Lock对象对应的条件变量
    private final  Condition con=lock.newCondition();	

	private String accountNo;
	private double balance;
	//标识账户中是否有存款的旗标
	private boolean flag=false;
	
	public Account() {
		super();
	}

	public Account(String accountNo, double balance) {
		super();
		this.accountNo = accountNo;
		this.balance = balance;
	} 
	
	public void draw (double drawAmount){
		
		//加锁
		lock.lock();
		try {
			  if(!flag){
//			  this.wait();
			  con.await();
		     }else {
		    	 //取钱
		    	 System.out.println(Thread.currentThread().getName()+" 取钱:"+drawAmount);
		    	 balance=balance-drawAmount;
		    	 System.out.println("余额 : "+balance);
		    	 //将标识账户是否已有存款的标志设为false
		    	 flag=false;
		    	 //唤醒其它线程
//		    	 this.notifyAll();  
		    	 con.signalAll();
		     }
	      	} catch (Exception e) {
	      		e.printStackTrace();
	  	}
	      	finally{
	      		lock.unlock();
	      	}
	}
	
	
   public void deposit(double depositAmount){
	   //加锁
	   lock.lock();
	   try {
			  if(flag){
//				this.wait();
				  con.await();
	          }
			  else{
				  System.out.println(Thread.currentThread().getName()+"存钱"+depositAmount);
				  balance=balance+depositAmount;
				  System.out.println("账户余额为:"+balance);
				  flag=true;
				  //唤醒其它线程
//				  this.notifyAll();
				  con.signalAll();
			  }
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
   }

}


输出结果和上面是一样的! 只不过这里 显示的使用Lock对像来充当同步监视器,使用Condition对象来暂停指定线程,唤醒指定线程!

 

Java8 新锁

从Java8 开始,新增了一个 StampedLock 锁,该锁提供了一种乐观的机制,性能在目前来说是最好的,因此被称为lock家族的 “宠儿”.

 

 

展开阅读全文

没有更多推荐了,返回首页