文章导读
【经过前面的学习,现在已经了解了多线程概念、Thread类的应用、多线程数据同步存在的问题及解决方法。本文给出一个多线程编程实例应用实例,通过实例的学习,可以灵活地把多线程知识应用到实际编程中,解决在项目开发过程中遇到的多线程编程问题。】
在进入案例之前,先了解一下实现Java同步机制的几个方法,Java.lang.object类提供了wati()、notify()、notifyAll()方法,它们与synchronized关键字结合使用,可以建立很多优秀的同步模型。
当synchronized修饰的方法或代码块中的wati()方法被调用时,当前线程将被中断运行,并且放弃该对象的锁。
当另外的线程执行了某个对象的notify()方法后,会唤醒在此对象等待池中的某个线程,使之成为可运行的线程。notifyAll()方法会唤醒所有等待这个对象的线程,使之成为可运行的线程。
下面来看一个比较经典的问题:生产者(Producer)和消费者(Consumer)问题。这个问题的解决就是通过灵活使用wati()、notify()、notifyAll()方法来实现的。
任务要求如下:
生产者将产品交给店铺,消费者从店铺取走产品,店铺一次只能存储固定数量的产品,如果生产者生产了过多产品,店铺会让生产者等一下,如果店中有空间存储产品了,再通知生产者继续生产;如果店中没有产品了,店铺会告诉消费者等一下,如果店中有产品了,再通知消费者来取走产品。
要实现上述任务要求,我们需要定义一个生产者线程类和消费者线程类。再建立一个全局数组作为存储产品的缓冲区。其控制过程是,生产者向缓冲区存入产品,消费者从缓冲区取走产品。当缓冲区满时,生产者必须阻塞,等待消费者取走产品后将其唤醒。当缓冲区空时,消费者被阻塞,等待生产者生产了产品后将其唤醒。
(1)定义产品类,产品包含一个产品标识的id属性。另外要在生产或消费时打印产品的详细内容,因此重写toString()方法,产品类的代码如下所示:
package com.milihua.product;
public class Product {
int id;
public Product(int id)
{
this.id = id;
}
public String toString()
{
return "Product" + id;
}
}(2)定义店铺类,店铺一次只能持有10份产品,如果生产者生产的产品多余10分,则会让当前正在此对象上操作的线程等待。一个线程访问addProduct方法时,它已经拿到这个锁了,当遇到产品大于10份时,它会阻赛。如果没有大于10份,则继续生产产品,并且调用notify方法,叫醒一个正在当前这个对象上等待的线程。这里请注意,notify和wait一般是一一对应的。代码如下所示:
package com.milihua.product;
public class Shop {
//默认为0个产品
int nIndex = 0;
Product[] pro = new Product[10];
//产品交给店员
public synchronized void addProduct( Product pd )
{
while( nIndex == pro.length )
{
try {
//产品已满,稍后生产
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//通知等待区的消费者可以取产品了
this.notify();
pro[nIndex] = pd;
nIndex++;
}
//消费者从店员处取走产品
public synchronized Product getProduct()
{
while(nIndex == 0)
{
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.notify();
nIndex--;
return pro[nIndex];
}
}(3)定义生产者线程类,生产者负责生产产品,每生产完一个产品,调用Thread类的sleep方法休眠一段时间,模拟生产过程。代码如下:
package com.milihua.product;
public class Producer implements Runnable{
//引用shop类
Shop shop;
public Producer(Shop inShop)
{
this.shop = inShop;
}
@Override
public void run() {
// TODO Auto-generated method stub
//生产产品,生产数量要大于店员能够存储的数量
for( int i = 0; i < 15; i++ )
{
Product pro = new Product(i);
shop.addProduct(pro);
System.out.println("生产了:" + pro);
//模拟生产一件产品花费的时间
try {
Thread.sleep((long)(Math.random()*1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}(4)定义消费者线程类,消费者负责消费产品,消费者每消费完一个产品,调用Thread类的sleep方法休眠一段时间,模拟消费过程。代码如下:
package com.milihua.product;
public class Consumer implements Runnable {
//引用shop类
Shop shop;
public Consumer(Shop inShop)
{
this.shop = inShop;
}
@Override
public void run() {
// TODO Auto-generated method stub
for( int i = 0; i < 15; i++ )
{
Product pd = shop.getProduct();
System.out.println("消费了:" + pd);
//模拟消费一件产品要花费的时间
try {
Thread.sleep((long)(Math.random()*1000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}(5)定义主线程类。代码如下:
package com.milihua.product;
public class MainTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
Shop shop = new Shop();
Thread t1 = new Thread(new Producer(shop));
t1.start();
Thread t2 = new Thread(new Consumer(shop));
t2.start();
}
}主线程类首先实例化Shop对象,然后分别实例化生产者线程和消费者线程。程序输出结果如下图所示:

图1 生产者消费者示例程序输出结果
文章小结
案例有两个线程类,分别是Producer类(生产者)和Consumer类(消费者)。Shop类(店铺)负责产品的购进(存储产品的数量有限)与销售,因此Shop类需要协调Producer类和Consumer类,当Producer类生产过多产品时,Shop类需要调用wait方法让Producer类不要再继续生产,同时调用notify方法通知消费者来取走产品(如果有正在等待产品的消费者)。反之亦然,当无产品可取时,Shop类需要调用wait方法让Consumer类等待产品,同时调用notify方法通知生产者继续生产产品。