大纲:java线程知识体系
一、概念
生产者消费者问题也称有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。 要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。
二、问题分析
- 在缓冲区为空时,消费者不能再进行消费
- 在缓冲区为满时,生产者不能再进行生产
- 在一个线程进行生产或消费时,其余线程不能再进行生产或消费等操作,即保持线程间的同步
- 注意条件变量与互斥锁的顺序
由于前两点原因,因此需要保持线程间的同步,即一个线程消费(或生产)完,其他线程才能进行竞争CPU,获得消费(或生产)的机会。对于这一点,可以使用条件变量进行线程间的同步:生产者线程在product之前,需要wait直至获取自己所需的信号量之后,才会进行product的操作;同样,对于消费者线程,在consume之前需要wait直到没有线程在访问共享区(缓冲区),再进行consume的操作,之后再解锁并唤醒其他可用阻塞线程。
在访问共享区资源时,为避免多个线程同时访问资源造成混乱,需要对共享资源加锁,从而保证某一时刻只有一个线程在访问共享资源。
/*
解题思路
1、分清资源类和线程类 => 资源类是将其封装成Clerk类,由Clerk操作共享资源。线程类分别是消费者线程和生成者线程
2、确定共享资源 => Clerk类或产品数量
3、是否涉及线程通信 => 为了协调生产和消费的步调必须进行线程通信
4、是否涉及线程安全 => 两类线程操作共享资源比如产生线程安全问题(用同步方法,同步代码块,Lock解决)
5、对共享资源的操作也需要在资源类中去完成,然后在线程的run方法中直接调用封装好的操作方法
*/
class Clerk{
//产品数上限为10
public static int MAX_PRODUCT = 10;
//当前第几个产品
public static int num;
public synchronized void product(){
String name = Thread.currentThread().getName();
notifyAll();
if(num <= MAX_PRODUCT){
num++;
System.out.println("生产者" +name + " 生产第:" +num +"件商品");
}else{
System.out.println("生产过多,停止生产");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consumer(){
String name = Thread.currentThread().getName();
notifyAll();
if(num > 0){
System.out.println("消费者" +name + " 消费第:" +num +"件商品");
num--;
}else{
try {
System.out.println("产品不足,停止消费");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true){
clerk.consumer();
}
}
}
class Productor implements Runnable{
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
while (true){
clerk.product();
}
}
}
/*
1、该模拟中一个生产者对应两个消费者,生产者和消费者必须用同一个clerk来构造否则无法协调工作
2、两个消费者线程必须用同一个customer构造,否则会出现线程安全问题,如果用不同的customer构造
需要将同步方法声明为静态同步方法
3、一个生产者对应多个消费者就会经常出现消费大于生成的场景,反之就会出现生成大于消费的场景,我们还
可以使用sleep来阻碍生产或消费
*/
public class Test {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Consumer consumer = new Consumer(clerk);
Thread customer = new Thread(new Consumer(clerk));
customer.setName("customer");
Thread customer2 = new Thread(new Consumer(clerk));
customer2.setName("customer2");
Thread productor = new Thread(new Productor(clerk));
customer.setName("productor");
customer.start();
// customer2.start();
productor.start();
}
}
结果是随机的,无限循环的。大体如上分析