Java线程之生产消费者模型详解

1.生产/消费者模型实现

1.1.wait/notify机制:

在前我们己实现了一种线程间通讯的方式,这种方式是我们自己编码实现,在java中,每个对象都有从Object父类继承而来的二个关与线程间通讯的方法wait()和notify(),如其方法名所示,一个是等待,一个是通知,当在一个对象上调用wait()方法时,当前线程就会进行wait状态,直到收到另一个对象的notify()发出通知,才会执行下一步计算,线程在wait中,也可能被中断,所以调用wait方法时,也要catch可能出现的InterruptedException异常。;在多线程通讯中,经常会用对象的这两个方法,一个典型的案例就是“生产/消费者模型”;

Java线程之生产消费者模型详解_第1张图片

 

    “生产/消费者”通讯模型的规则是,仅当集合中没有对象时,生产线程会放入一个对象,如有集合中有一个对象时,消费线程要马上取出这个对象,这个示例中,我们设计了三个类;

第一个是一个模拟的数据对象类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 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 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()方法---这却是创建了一个新的线程。如下图示,是线程可能导致其所处状态与对应方法的示意图:

 

Java线程之生产消费者模型详解_第2张图片

你可能感兴趣的:(JavaSE)