一、线程的协调运行
Object类提供了wait(),notify()和notifyAll()三种方法来实现线程之间的协调运行,但是
这三种方法必须由同步监视器对象来调用。
wait():导致当前线程等待,直到其它线程调用该同步监视器的notify()或者notifyAll()方法
来唤醒该线程。
notify():唤醒在此同步监视器上等待的单个线程,如果有多个线程在此同步监视器上等待
选择唤醒其中一个线程,并且选择是任意的。
notifyAll():唤醒在此同步监视器上等待的所有线程。
代码示例:
package defaultpackage;
public class Account {
private String accountNo;
private double balance;
//标示账户中是否已有存款的标志
private boolean flag = false;
public Account (){
}
public Account (String accountNo,double balance){
this.accountNo = accountNo;
this.balance = balance;
}
public double getBalance(){
return this.balance;
}
public synchronized void draw(double drawAmount){
try{
//如果flag为假,表明账户中还没有人存钱进去,则取钱方法阻塞
if(!flag){
wait();
}
else{
//执行取钱
System.out.println(Thread.currentThread().getName()+
"取钱:"+drawAmount);
balance -= drawAmount;
System.out.println("账户余额为:"+balance);
//将标示账户是否有存款的标志设为flase
flag= false;
//唤醒其他线程
notifyAll();
}
}catch(InterruptedException ex){
ex.printStackTrace();
}
}
public synchronized void deposit (double depositAmount){
try{
//如果flag为真,表明账户中已有人存钱进去,则存钱方法阻塞
if(flag){
wait();
}
else{
//执行存款
System.out.println(Thread.currentThread().getName()+
"存款:"+depositAmount);
balance += depositAmount;
System.out.println("账户余额为:"+balance);
//将标示账户是否已有存款的标志设为true
flag = true;
//唤醒其它线程
notifyAll();
}
}catch(InterruptedException ex){
ex.printStackTrace();
}
}
//此处省略了hashCode和equals两个方法
}
二、条件变量
1、使用Lock对象来保持同步而不是使用synchronized关键字来保证同步,则系统
中不存在同步监视器对象,不能使用wait,notify,notifyAll方法来协调进程的运行。
2、在使用Lock对象保持同步时,java提供Condition类来保持协调;这样Lock替代
了同步方法或同步代码块,Condition替代了同步监视器的功能。即将Condition对象
绑定到一个Lock对象上,要获得特定Lock实例的Condition实例,调用Lock对象的
newCondition()方法即可。Condition类提供如下三个方法:
await():类似于wait()方法,导致当前线程等待,直到其他线程调用该Condition的
signal()方法或signslAll()方法来唤醒该线程。
signal():唤醒在此Lock对象上等待的单个线程,若有多个线程等待,则唤醒其中
之一,选择是任意的。
signalAll():唤醒在此Lock对象上等待的所有线程。
代码示例:
package defaultpackage;
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 cond = lock.newCondition();
private String accountNo;
private double balance;
//标示账户中是否已经有存款的标志
private boolean flag = false;
public Account(){}
public Account(String accountNo,double balance){
this.accountNo = accountNo;
this.balance = balance;
}
public double getBalance(){
return this.balance;
}
public 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);
//将标示是否成功存入存款的标志设为false
flag = false;
//唤醒该Lock对象对应的其他线程
cond.signalAll();
}
}catch(InterruptedException ex){
ex.printStackTrace();
}
//使用finally块来确保释放锁
finally{
lock.unlock();
}
}
public void deposit(double depositAmount){
lock.lock();
try{
//如果账户中已经存入了存款,该线程等待
if(flag){
cond.await();
}
else{
//执行存款操作
System.out.println(Thread.currentThread().getName()+
"存款:"+depositAmount);
balance += depositAmount;
System.out.println("账户余额为:"+balance);
//将标示是否成功存入存款的标志设为true
flag = true;
//唤醒该Lock对象对应的其他线程
cond.signalAll();
}
}catch(InterruptedException ex){
ex.printStackTrace();
}
//使用finally块来确保释放锁
finally {
lock.unlock();
}
}
//此处省略了hashCode和equals方法
}
三、管道
管道有3种存在形式:PipeInputStream和PipeOutputStream;PipedReader和PipedWriter;
Pipe.SinkChannel和Pipe.SourceChannel,分别是管道字节流,管道字符流和新IO的管道Channel。
用管道实现多线程通信的步骤如下:
1、使用new 操作符分别创建管道输入流和管道输出流。
2、使用管道输入流和管道输出流的connect方法把两个输入流和输出流连接起来。
3、将管道输入流和管道输出流分别传入两个线程。
4、两个线程可以分别依赖各自管道输入流、管道输出流进行通信。
代码示例:
package defaultpackage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
public class ReaderThread {
private PipedReader pr;
//用于包装管道流的BufferReader对象
private BufferedReader br;
public ReaderThread(){}
public ReaderThread(PipedReader pr){
this.pr = pr;
this.br = new BufferedReader(pr);
}
public void run(){
String buf = null;
try{
//逐行读取管道输入流中的内容
while((buf = br.readLine())!=null){
System.out.println(buf);
}
}
catch(IOException ex){
ex.printStackTrace();
}
//使用finally块来关闭输入流
finally{
try{
if(br != null)
br.close();
}catch(IOException ex){
ex.printStackTrace();
}
}
}
}
public class WriteThread extends Thread{
String[] books = new String[]{
"Structs2 权威指南",
"ROR敏捷开发指南",
"基于J2EE的Ajax宝典",
"轻量级J2EE企业应用指南"
};
private PipedWriter pw;
public WriterThread(){}
public WriterThread(PipedWriter pw){
this.pw = pw;
}
public void run(){
try{
//循环100次,向管道输出流中写入100个字符串
for(int i = 0;i<100;i++)
pw.write(books[i%4] + "\n");
}catch(IOException ex){
ex.printStackTrace();
}
//使用finally块来关闭管道输出流
finally{
try{
if(pw != null)
pw.close();
}catch(IOException ex){
ex.printStackTrace();
}
}
}
}
public class PipedCommunicationTest{
public static void main (String[] args){
PipedWriter pw = null;
PipedReader pr = null;
try{
//分别创建两个独立的管道输出流、输入流
pw = new PipedWriter();
pr = new PipedReader();
//连接管道输入流、输出流
pw.connect(pr);
//将连接好的管道分别传入两个线程
//就可以让两个线程通过管道流进行通信
new WriterThread(pw).start();
new ReaderThread(pr).start();
}catch(IOException ex){
ex.printStackTrace();
}
}
}