【Java学习笔记】线程通信的几种方法

在学习Java多线程知识的时候,一定会涉及到线程通信这方面的知识。所谓线程通信,指的就是线程在系统内运行的时候,线程间的调度具有一定的透明性,也就是说线程具体的调度方式是不能够被程序直接获取到的,程序通常无法准确地控制线程的轮换执行,Java也提供了一些机制来保证线程的协调运行。

Java中的关键字synchronized用来修饰同步代码块或者同步方法,同步代码块的意思是,在synchronized修饰下的代码部分,只允许线程互斥的进行访问,而synchronized关键字后会显示的定义同步操作的对象,例如

synchronized(apple){

//此处省略代码;

}

当使用synchronized来修饰方法的时候,这样的方法就叫做同步方法,通过定义同步方法可以实现同步类,不同的线程可以正确安全地访问该类的对象。

1、传统的线程间通信所采用的一般是wait()、notify()、notifyAll()这三个方法,这三个方法不属于Thread类,而是属于Object类,但是这三个方法必须通过同步监视器对象(可以是synchronized修饰过的方法所在类的实例或同步代码块所指定的对象)来调用,

wait()导致当前线程等待,当前线程会释放线程所占资源,即释放对该资源的锁,开始等待,直至notify()或notifyAll()将其唤醒;

notify()唤醒此同步监视器上的一个等待的线程,若是有多个等待线程则随机唤醒一个;

notifyAll()唤醒此同步监视器上的所有等待的线程,只有当前线程放弃同步监视器的锁后才能调用该方法。

下面用一个例子来说明几种方法的用法:

扫描二维码关注公众号,回复: 494441 查看本文章
public class Account {    //声明一个账户类,实现存钱/取钱的进程间通信
	private String accountNo;//账户名    
	private double balance;//余额
	private boolean flag = false;//若没人存钱则为false,有人存入后设为true,取钱后设为false,存取钱只能交替进行
	public Account(){}
	public Account (String accountNo, double balance) {
		this.accountNo = accountNo;
		this.balance = balance;
	}
	public synchronized void draw(double drawAmount) {
		try {
			if(!flag)
				wait();//此时不能取钱,等待
			else {
				System.out.println(Thread.currentThread().getName()+"取钱:"+drawAmount);
				balance-=drawAmount;
				System.out.println("余额为:"+balance);
				flag = false;//取钱成功后,设为false
				notifyAll();
			}
		}
		catch(InterruptedException ex) {
			ex.printStackTrace();
		}
	}
	public synchronized void deposit(double depositAmount) {
		try {
			if(flag)
				wait();//此时不能存钱,等待
			else
			{
				System.out.println(Thread.currentThread().getName()+"存款:"+depositAmount);
				balance+=depositAmount;
				System.out.println("余额为:"+balance);
				flag = true;//存钱成功后设为true
				notifyAll();
			}
		}
		catch(InterruptedException ex) {
			ex.printStackTrace();
		}
	}
}

存钱/取钱操作的执行线程

public class DepositThread extends Thread{
	private Account account;
	private double depositAmount;
	public DepositThread(String name, Account account, double despositAmount) {
		super(name);
		this.depositAmount = despositAmount;
		this.account = account;
	}
	public void run() {
		for(int i=0; i<10; i++) {//执行十次存钱操作
			account.deposit(depositAmount);
		}
	}
}
public class DrawThread extends Thread{
	private Account account;
	private double drawAmount;
	public DrawThread(String name, Account account, double drawAmount) {
		super(name);
		this.drawAmount = drawAmount;
		this.account = account;
	}
	public void run() {
		for(int i=0; i<100; i++) {//执行十次取钱操作
			account.draw(drawAmount);
		}
	}
}

开始进行存取钱操作,存/取钱线程各自创建两个,同时开始执行

public class DrawTest {
	public static void main(String args[]) {
		Account ac = new Account("mango",0);
		new DrawThread("取钱1", ac, 500).start();
		new DrawThread("取钱2", ac, 500).start();
		new DepositThread("存钱1", ac, 500).start();
		new DepositThread("存钱2", ac, 500).start();
	}
}

运行部分结果如下,可以看到存取钱操作交替执行,互斥的访问账户对象

存钱1存款:500.0

余额为:500.0

取钱2取钱:500.0

余额为:0.0

存钱2存款:500.0

余额为:500.0

取钱1取钱:500.0

余额为:0.0

存钱1存款:500.0

余额为:500.0

2、使用Condition控制线程通信

如果不用synchronized关键字,我们还可以用Lock对象来保证同步。当用Lock来保证同步时,就不能使用上述的wait()、notify()等方法了,Java提供了一个Condition类来保持协调,在这种方法下,Lock代替了同步方法或者同步代码块,而Condition代替了同步监视器。使用Condition可以实现线程释放资源锁,也可以实现线程的唤醒,与上面提到的三个方法对应的Condition中的方法是await()、signal()和signalAll()。

创建Lock对象的方法为

private final Lock lock = new ReentrantLock();

通过调用Lock对象中的newCondition()方法来创建Condition

private final Condition cond = new lock.newCondition();

修改之前的代码为

public synchronized void draw(double drawAmount) {
		lock.lock();//加锁
		try {
			if(!flag)
				cond.await();//此时不能取钱,等待
			else {
					System.out.println(Thread.currentThread().getName()+"取钱:"+drawAmount);
					balance-=drawAmount;
					System.out.println("余额为:"+balance);
					flag = false;//取钱成功后,设为false
					cond.signalAll();
			}
		}
		catch(InterruptedException ex) {
			ex.printStackTrace();
		}
		finally{
			lock.unlock();//使用finally块释放锁
		}
	}

3、使用阻塞队列控制线程通信

阻塞队列(BlockingQueue)也可以实现进程间的通信控制。阻塞队列可以这样理解,有一个生产者和一个消费者,生产者负责向队列中增加产品,消费者负责从队列中取出产品。当队列为空时,消费者的消费行为便会被阻塞,直到队列中有产品的时候才会被唤醒;当队列满的时候,生产者的生产行为便会被阻塞,当队列中有空位的时候才会被唤醒。

BlockingQueue继承了Queue类,所以具备Queue的add()、remov()等方法,不过这些方法都不是同步方法,当队列出现空或满的时候会抛出异常。BlockingQueue定义了自己的增加和删除方法,put(E e)和take()分别完成入队和出队的操作。也可以运用阻塞队列来完成资源的各种互斥访问。






猜你喜欢

转载自blog.csdn.net/u012198209/article/details/79855330