多线程中有个很经典的,关于生产者与消费者的的问题,大致就是,有多个生产者生产同一种产品,另外有多个消费者消费这个产品,当生产者生产到一定程度时停止生产,这时通知消费者赶快消费,当消费者把产品消费完以后停止消费,并通知生产者开始生产。
映射到程序,我是这样设计的,有一个生产者的线程类(Producter),以及消费者的线程类(Consumer),还有一个产品类(Product),以及提供了对产品进行管理的类(ProducerAndConsumer),里面有一个产品的数组,并对外提供了添加产品,消费产品的方法(addProduct、getProduct),考虑到多个线程访问资源的安全,这两个方法都做了同步,保证一个时间点只有一个线程使用资源
具体代码如下:
Product类,每个产品都有一个id,并重写了toString方法
//产品类
class Product {
//产品id
private int id;
public Product(int id) {
this.id = id;
}
@Override
public String toString() {
return "id=" + id;
}
}
生产者类(Producter),从Thread继承,保留一个资源管理类ProducerAndConsumer 的引用
class Producer extends Thread{
//保留ProducerAndConsumer的引用
private ProducerAndConsumer pac;
public Producer(ProducerAndConsumer pac) {
this.pac = pac;
}
@Override
public void run() {
System.out.println("Producer run");
Random r = new Random();//随机数生成器,
int index = 0;
//生产15个产品
while (index++ < 15) {
Product product = new Product(index);//new出一个id为i的产品
pac.addProduct(product);//向pac添加一个产品,就是生产一个产品
System.out.println(Thread.currentThread().getName() + " produce product " + product);
try {
Thread.sleep(r.nextInt(1000));//随机休眠0至一秒,休眠时不放开锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者类(Consumer),和生产者类一样,也是从Thread继承,保留一个资源管理类ProducerAndConsumer 的引用
class Consumer extends Thread {
//保留ProducerAndConsumer的引用
private ProducerAndConsumer pac;
public Consumer(ProducerAndConsumer pac) {
this.pac = pac;
}
@Override
public void run() {
System.out.println("Consumer run");
Random r = new Random();
int index = 15;
//消费15个产品
while (index-- > 0) {
Product product = pac.getProduct();//问pac要一个产品,消费掉
System.out.println(Thread.currentThread().getName() + " consume product " + product);
try {
Thread.sleep(r.nextInt(2000));//随机休眠0至2秒,休眠时不放开锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
资源管理的类(ProducerAndConsumer),也是main方法所在类,主要提供了两个对产品操作的方法
/**
* 多线程同步经典问题,生产者和消费者,
* @author my
*
*/
public class ProducerAndConsumer {
//记录目前的索引
private int index = 0;
//产品的容器,容量是10个产品
private Product[] products = new Product[10];
public static void main(String[] args) {
ProducerAndConsumer pac = new ProducerAndConsumer();
new Producer(pac).start();
new Consumer(pac).start();
}
//像容器中添加产品,此方法需要同步,以防止多线程访问造成的资源不一致
public synchronized void addProduct(Product product) {
//条件反转,拒绝添加空的产品
if (product == null) {
return;
}
//若目前的索引等于products的长度,把生产者线程暂停,并唤醒消费者线程
if (products.length == index) {
try {
this.wait();//暂停生产者线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify(); //唤醒消费者线程去消费产品
//把产品添加到products的index索引处
products[index] = product;
//把索引加1
index ++;
}
//得到容器中的产品,此方法需要同步,以防止多线程访问造成的资源不一致
public synchronized Product getProduct() {
//若没有产品了,就不在消费
if (index == 0) {
try {
this.wait();//暂停消费
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();//唤醒生产者,生产产品
index--;//把索引减去1
//返回索引处的产品
return products[index];
}
}
运行结果:
Producer run
Consumer run
Thread-0 produce product id=1
Thread-1 consume product id=1
Thread-0 produce product id=2
Thread-1 consume product id=2
Thread-0 produce product id=3
Thread-1 consume product id=3
Thread-0 produce product id=4
Thread-1 consume product id=4
Thread-0 produce product id=5
Thread-0 produce product id=6
Thread-0 produce product id=7
Thread-1 consume product id=7
Thread-0 produce product id=8
Thread-0 produce product id=9
Thread-0 produce product id=10
Thread-1 consume product id=10
Thread-0 produce product id=11
Thread-1 consume product id=11
Thread-0 produce product id=12
Thread-0 produce product id=13
Thread-1 consume product id=13
Thread-0 produce product id=14
Thread-0 produce product id=15
Thread-1 consume product id=15
Thread-1 consume product id=14
Thread-1 consume product id=12
Thread-1 consume product id=9
Thread-1 consume product id=8
Thread-1 consume product id=6
Thread-1 consume product id=5
程序成功模拟了这个过程,但是出现了一个非常奇怪的问题,刚开始Producter、Consumer,这两个类是实现Runnable接口,结果是消费者类无法启动,改为从Thread继承后,程序正常了,真是一个非常奇怪的问题,查了资料、问了群里的兄弟都无解,无语。