多线程-5-线程通信

本文引用的例子:当线程在系统内运行时,线程的调度具有一定透明性,程序通常无法准确控制线程轮换执行,但是我们可以通过一些机制来保证线程协调运行。为了能说明这个问题,专门引入一个题目:假设现在系统中有两个线程,这两个线程分别代表存款者和取钱者——现在假设系统要求存款者和取钱者不断地重复存款、取钱动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱,不允许存款者连续两次存钱,也不允许取钱者连续两次取钱。


0.知识体系


线程通信知识体系如下图所示:


1.传统线程通信


class Account 
{
	//定义账户、账户余额
	private String accountNo;
	private double balance;
	//标识账户中是否已有存款
	private boolean flag = false;
	//构造器
	public Account(String accountNo, double balance){
		this.accountNo = accountNo;
		this.balance = balance;
	}
	//accountNo的set()与get()方法
	public void setAccountNo(String accountNo){
		this.accountNo = accountNo;
	}
	public String getAccountNo(){
		return accountNo;
	}
	//balance的set()与get()方法
	public double getBalance(){
		return balance;
	}
	
	public synchronized void draw(double drawAmount){
		try{
			if(!flag){
				wait();
			}else{
				//执行取钱操作
				balance -= drawAmount;
				System.out.println(Thread.currentThread().getName() + " 取钱:" + drawAmount + "--账户余额为 " + balance);
				flag = false;
				//唤醒其它
				notifyAll();
			}
		}catch(InterruptedException ex){
			ex.printStackTrace();
		}
	}
	public synchronized void deposit(double depositAmount){
		try{
			//如果flag为true,表明账户中有钱,存钱方法阻塞
			if(flag){
				wait();
			}else{
				//执行存钱操作
				balance += depositAmount;
				System.out.println(Thread.currentThread().getName() + " 存款:" + depositAmount + "--账户余额为:" + balance);
				flag = true;
				notifyAll();
			}
		}catch(InterruptedException ex){
			ex.printStackTrace();
		}
	}
}
class DrawThread extends Thread
{
	//用户账号
	private Account account;
	private double drawAmount = 100;
	public DrawThread(String name, Account account){
		super(name);
		this.account = account;
	}
	public void run(){
		while(true){
			account.draw(drawAmount);
		}
	}
}
//业务模型与运转模型耦合在一起
class DepositThread extends Thread
{
	//用户账号
	private Account account;
	private double depositAccount = 100;
	public DepositThread(String name, Account account){
		super(name);
		this.account = account;
	}
	public void run(){
		while(true){
			account.deposit(depositAccount);
		}
	}
}
public class ThreadComTest
{
	public static void main(String[] args){
		//创建账户
		Account account = new Account("232323",0);
		new DepositThread("存钱者",account).start();
		new DrawThread("取钱者",account).start();
	}
}


2.使用Condition控制线程通信


当使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,Condition对象也可以唤醒其他处于等待的线程。

Condition将同步监视器方法(wait()、notify()、notifyAll())分解成截然不同的对象,以便通过将这些对象与Lock对象组合使用,为每个对象提供多个等待集。如此一来,Lock替代同步方法或同步代码块,Condition替代了同步监视器的功能。

Condition实例被绑定在一个Lock对象上。要获得特定Lock实例的Condition实例,调用Lock对应的newCondition()方法即可。


Condition类提供如下3个方法:

1)await()

类似于隐式同步监视器上的wait()方法,导致当前线程等待,直到其他线程调用该Condition的signal()方法或signaAll()方法来唤醒该线程。该await()方法有多种变种:如long awaitNanos(long nanosTimeout)、void awaitUninterruptibly()、awaitUntil(Date deadline)等,可以完成更丰富的等待操作。


2)signal()


唤醒在此Lock对象上等待的单个线程。如果所有线程都在该Lock对象上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该Lock对象的锁定后(await()方法),才可以执行被唤醒的线程。


3)signalAll()


唤醒在此Lock对象上等待的所有线程。只有当前线程放弃对该Lock对象的锁定后才可以执行被唤醒的线程。

代码有点长,需要认真研究。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
class Account 
{
	//定义账户、账户余额
	private String accountNo;
	private double balance;
	//标识账户中是否已有存款
	private boolean flag = false;

	//显式定义Lock对象
	private final Lock lock =new ReentrantLock();
	private final Condition condition = lock.newCondition();


	//构造器
	public Account(String accountNo, double balance){
		this.accountNo = accountNo;
		this.balance = balance;
	}
	//accountNo的set()与get()方法
	public void setAccountNo(String accountNo){
		this.accountNo = accountNo;
	}
	public String getAccountNo(){
		return accountNo;
	}
	//balance的set()与get()方法
	public double getBalance(){
		return balance;
	}
	
	public void draw(double drawAmount){
		lock.lock();
		try{
			if(!flag){
				condition.await();
			}else{
				//执行取钱操作
				balance -= drawAmount;
				System.out.println(Thread.currentThread().getName() + " 取钱:" + drawAmount + "--账户余额为 " + balance);
				flag = false;
				//唤醒其它
				condition.signalAll();
			}
		}catch(InterruptedException ex){
			ex.printStackTrace();
		}
		finally{
			lock.unlock();
		}
	}
	public void deposit(double depositAmount){
		lock.lock();
		try{
			//如果flag为true,表明账户中有钱,存钱方法阻塞
			if(flag){
				condition.await();
			}else{
				//执行存钱操作
				balance += depositAmount;
				System.out.println(Thread.currentThread().getName() + " 存款:" + depositAmount + "--账户余额为:" + balance);
				flag = true;
				condition.signalAll();
			}
		}catch(InterruptedException ex){
			ex.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
}
class DrawThread extends Thread
{
	//用户账号
	private Account account;
	private double drawAmount = 100;
	public DrawThread(String name, Account account){
		super(name);
		this.account = account;
	}
	public void run(){
		while(true){
			account.draw(drawAmount);
		}
	}
}
//业务模型与运转模型耦合在一起
class DepositThread extends Thread
{
	//用户账号
	private Account account;
	private double depositAccount = 100;
	public DepositThread(String name, Account account){
		super(name);
		this.account = account;
	}
	public void run(){
		while(true){
			account.deposit(depositAccount);
		}
	}
}
public class ThreadComTest
{
	public static void main(String[] args){
		//创建账户
		Account account = new Account("232323",0);
		new DepositThread("存钱者",account).start();
		new DrawThread("取钱者",account).start();
	}
}


3.使用阻塞队列(BlockingQueue)控制线程通信


1)通信原理


Java 5提供了BlockingQueue接口,虽然BlockingQueue也是Queue的子接口,但它的主要用途并不是作为容器,而作为线程同步的工具。BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中放入元素时,如果该队列已空,则该线程被阻塞。
程序中的两个线程通过交替向BlockingQueue中放入元素、取出元素,即可很好地控制线程的通信。

2)阻塞方法

BlockingQueue提供如下两个支持阻塞的方法:
(1)put(E e):尝试将E元素放入BlockingQueue中,如果该队列的元素已满,则阻塞该线程。
(2)take():尝试从BlockingQueue的头部取出元素,如果队列的元素已空,则阻塞该线程。

3)Queue接口中的方法

BlockingQueue继承了Queue接口,当然也可使用Queue接口中的方法,方法归纳起来有以下3组:
(1)在队列尾部插入元素。包括add(E e)、offer(E e)和put(E e)方法,当该队列已满时,这3个方法分别会抛出异常、返回false、阻塞队列。
(2)在队列头部删除并返回删除的元素,包括remove()、poll()和take()方法。当该队列已空时,这个3个方法分别会抛出异常、返回false、阻塞队列。
(3)在队列头部取出但不删除元素。包括element()和peek()方法,当队列已空时,这两个方法会分别抛出异常、返回false。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值