1.生产/消费者模型实现
1.1.wait/notify机制:
在前我们己实现了一种线程间通讯的方式,这种方式是我们自己编码实现,在java中,每个对象都有从Object父类继承而来的二个关与线程间通讯的方法wait()和notify(),如其方法名所示,一个是等待,一个是通知,当在一个对象上调用wait()方法时,当前线程就会进行wait状态,直到收到另一个对象的notify()发出通知,才会执行下一步计算,线程在wait中,也可能被中断,所以调用wait方法时,也要catch可能出现的InterruptedException异常。;在多线程通讯中,经常会用对象的这两个方法,一个典型的案例就是“生产/消费者模型”;
“生产/消费者”通讯模型的规则是,仅当集合中没有对象时,生产线程会放入一个对象,如有集合中有一个对象时,消费线程要马上取出这个对象,这个示例中,我们设计了三个类;
第一个是一个模拟的数据对象类Student.java,生成要向集合中放入的对象,代码很简单:
/**
* 用来交换的数据对象模型
* @kowloonchen
*/
class Student{
int id;
String name;
public String toString(){
return id+" "+name;
}
}
第二个类是生产者线程ProduceThread.java,生产者线程运象一但运行,就会尝试锁定与消费者对象共享的对象集合,一但它锁定这个集合(即进入synchronized保护的代码块,如果集合中没有对象,它就会放入一个,然后发出notify()通知;如果集合中有对象,生产者线程就会调用wait()方法,将自己处于“等待”状态,直到收到一个notify()通知:
第三个类是消费者线程CustomerThread.java,消费线程运一但运行,就会尝试锁定与生产线程对象共享的对象集合,一但它锁定这个集合(即进入synchronized保护的代码块,如果集合中没有对象,消费线程就会调用wait()方法,将自己处于“等待”状态,直到收到一个notify()通知;如果集中有对象,它将取出这个对象后,发出notify()通知告诉生产者线程,要放入数据了:
//取数据线程:当有数据时取,取完发通知
class CustomerThread extends Thread{
CustomerThread(List shareList){
this.shareList=shareList;
}
public void run(){
System.out.println("消费线程己启动...."+shareList.size() );
while(true){
try{
synchronized(shareList){
while(shareList.size()==0){
//如果没有,消费线程则等待
shareList.wait();
}
while(shareList.size()>0){
System.out.println("<---消费线程取出: "+shareList.remove(0).toString() );
//取了一个后,立即发出通知
shareList.notify();
}
}
}catch(Exception ef){
ef.printStackTrace();
}
}
}
private List shareList;
}
最后,我们需要编写一个主类,让这个系统运行起来:
/**
* 使用wait/notify进行线程间通信例子
*生产线程向队列存入一个Student对象,消费线程立即取出
* @author
*/
public class WaitNotifyDemo {
//主函数,启动存取线程
public static void main(String[] args){
// 生产/消费线程交换对象的的队列
List shareList=new java.util.LinkedList();
//启动生产线程
new ProduceThread(shareList).start();
//启动消费线程
new CustomerThread(shareList).start();
}
}
程序启动后,将输出如下结果:
生产线程己启动....0
消费线程己启动....0
--->生产线程放入对象:1 1aaa
<---消费线程取出: 1 1aaa
--->生产线程放入对象:2 2aaa
<---消费线程取出: 2 2aaa
--->生产线程放入对象:3 3aaa
<---消费线程取出: 3 3aaa
…
1.2.阻塞队列实现线程间通信
阻塞队列的概念:在JDK1.5以后,javaAPI中引入了阻塞队列的概念,阻塞队列是队列的一种,即也是java.util.Queue接口实现的一种队列;与我们使用的Set、List等集合相比,阻塞在存放数据时使用了“阻塞”的机制,即当队列中有数据时,对阻塞队列的put()方法将用将会进入等待状态,直到队列中在空间放入数据;从阻塞队列中取数据时,如果阻塞队列中还没有数据,take()方法就会一直等待,直到队列被放入一个数据对象时,take()方法才会返回这个对象;
回想List对象的add()或get()方法的调用,都会”马上”完成,即与队列的大小和其中是否有对象无关,这就作集合的“快速失败”。
利用阻塞队列的这一特点,就很容易做到多线程之间的数据通讯,阻塞队列常用的一个实现类是java.util.concurrent.LinkedBlockingQueue类,请看如下代码:
import java.util.concurrent.LinkedBlockingQueue;
/**
* 利用concurrent.LinkedBlockingQueue做线程间交换数据的测试
* 使用Customer类提取数据;
* Produce向交换区内放入数据
* @author
*/
public class SynQueueTest {
/**定义阻塞队列,队列容量为1个元素**/
private static LinkedBlockingQueue cq=new LinkedBlockingQueue(1);
//测试方法
public static void main(String args[]){
Customer cs=new Customer(cq);
Thread ct=new Thread(cs);
ct.start();
Produce pd=new Produce(cq);
Thread pt=new Thread(pd);
pt.start();
}
}
/**
* 模拟消费者,做为独生线程启动,当队列有数据时,从中取出数据
* @author
*/
class Customer implements Runnable{
//构造器
Customer(java.util.concurrent.BlockingQueue<Student> bk){
this.bk=bk;
}
//线程运行方法
public void run(){
while(true){
try{
while(true){
Object obj= bk.take();
System.out.println(" <---从阻塞队列取出对象: "+obj.toString());
}
}catch(Exception ef){
}
}
}
private java.util.concurrent.BlockingQueue<Student> bk;
}
/**
* 模拟生产者,从独立线程中向队列中放入数据
* @author
*
*/
class Produce implements Runnable{
Produce(java.util.concurrent.BlockingQueue bk){
this.bk=bk;
}
//线程运行方法
public void run(){
while(true){
try{
Student st=new Student();
count++;
st.id=count;
st.name=count+"aaa";
bk.put(st);
System.out.println(" --->向阻塞队列放入对象 ");
Thread.sleep(3000);
}catch(Exception ef){
}
}
}
private java.util.concurrent.BlockingQueue bk;
private static int count=0;
}
--->向阻塞队列放入对象
<---从阻塞队列取出对象: 1 1aaa
--->向阻塞队列放入对象
--->向阻塞队列放入对象
<---从阻塞队列取出对象: 2 2aaa
. . .
阻塞队列又叫做异步队列,常应用在系统消息通讯程序中。
2.start()与run()方法详解:线程的状态
线程执行的入口方法是其run()方法,相关与我们一个应用程序的main()方法一样,要让一个线程独立执行其run方法,不能直接调用线程对象的run方法---否则只是一个单线程的方法调用而己,run方法中的代码不会在一个并行的线程中执行。理解了这一些,就明白了为什么要通过线程的start()方法启动线程;线程对象的start()方法调用后,只是给了JVM个信号:这个线程对象可以运行了,JVM就可能当CPU时钟分配线这个线程对象,从而让其处与运行中状态。
对与一个线程对象,只能调用一次其start()方法!start调用后,线程只是处与Runnable(可运行状态)---JVM并不保证马上执行其run方法,只有分配到时间片后,线程才进入Running状态;运行中的线程,可以从sleep(),wait(),interupt(),join()中恢复重新进入Running状态,但一旦run()方法执行完毕,线程就完蛋了,处理Dead状态---再也不能通过其它方法调用恢复到run()方法中,除非你重新new一个线程对象,并设用其start()方法---这却是创建了一个新的线程。