生产者-消费者问题
生产者-消费者问题也叫做有限缓冲问题,是一个多线程同步的经典案例.
问题描述
生产者线程和消费者线程在实际运行发生的问题.生产者不断生产数据放到缓冲区,消费者不断地去缓冲区消费数据.
问题关键
1:保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区为空时消耗数据.
2:保证生产者加入过程和消费者消费过程中不会产生错误的数据和行为.
生产者-消费者是一个经典的设计模式,比如线程的阻塞队列,synchronized内置锁还有显示锁lock
还有mq等都有这种思想.
生产者-消费者模式
关键点
1:生产者与生产者之间 消费者与消费者之间,对数据缓存区的操作是同步进行的.
2:数据缓冲区是有上限的,数据满后,生产者不能再加入数据.缓冲区为空的话,消费者不能再取出数据.
3:数据缓冲区是线程安全的.在并发操作过程中不会出现数据不一致的情况,或者多线程操作并发更改数据后,不会造成出现脏数据的情况.
4:生产者和消费者线程在空闲时,需要尽可能阻塞而不是执行无效的空操作,尽量节省CPU资源.
线程不安全的缓冲区实现版本
public class NotSafeDataBuffer<T> {
public static final int MAX_AMOUNT = 10;
private List<T> dataList = new LinkedList<>();
//保存数量.
private AtomicInteger amount = new AtomicInteger(0);
//向数据区添加一个元素.
public void add(T element) throws Exception {
if (amount.get() > MAX_AMOUNT) {
throw new Exception("队列已满");
}
dataList.add(element);
amount.incrementAndGet();
//如果数据不一致抛出异常.
if (amount.get() != dataList.size()) {
throw new Exception(amount.get() + "!=" + dataList.size());
}
}
//从数据区中取出元素.
public T get() throws Exception {
if (amount.get() <= 0) {
System.out.println("队列已经空了.");
return null;
}
T value = dataList.remove(0);
amount.decrementAndGet();
//数据不一致抛出异常.
if (amount.get() != dataList.size()) {
throw new Exception(amount.get() + "!=" + dataList.size());
}
return value;
}
}
生产者 消费者的逻辑解耦
通过分离变与不变的原则,通过这种方式不同的生产者和消费者公共部门拆分出来,只需要在构建生产者消费者的时候传入对应的动作就可以.增强了灵活性.
通用生产者实现
public class Producer implements Runnable {
//生产的时间间隔.
public static final int PRODUCE_GAP = 200;
//总次数.
static final AtomicInteger TURN = new AtomicInteger(0);
//生产者对象编号.
static final AtomicInteger PRODUCER_NO = new AtomicInteger(0);
//生产者名称.
String name = null;
//生产者的动作.
Callable action = null;
int gap = PRODUCE_GAP;
public Producer(Callable action, int gap) {
this.action = action;
this.gap = gap;
name = "生产者" + PRODUCER_NO.incrementAndGet();
}
@Override
public void run() {
while (true) {
//执行生产者的动作.
try {
Object value = action.call();
//输出生产结果.
if (null != value) {
System.out.println("第" + TURN.get()
+ "轮生产:" + value);
}
Thread.sleep(gap);
TURN.incrementAndGet();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
通用消费者实现
public class Consumer implements Runnable {
//生产的时间间隔.
public static final int PRODUCE_GAP = 200;
//总次数.
static final AtomicInteger TURN = new AtomicInteger(0);
//生产者对象编号.
static final AtomicInteger PRODUCER_NO = new AtomicInteger(0);
//消费者名称.
String name = null;
//消费者的动作.
Callable action = null;
int gap = PRODUCE_GAP;
public Consumer(Callable action, int gap) {
this.action = action;
this.gap = gap;
name = "消费者" + PRODUCER_NO.incrementAndGet();
}
@Override
public void run() {
while (true) {
//执行消费者的动作.
try {
Object value = action.call();
//输出消费结果.
if (null != value) {
System.out.println("第" + TURN.get()
+ "轮生产:" + value);
}
Thread.sleep(gap);
TURN.incrementAndGet();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
数据缓冲区实例 生产动作 消费动作
public class NotSafePetStore {
//数据缓冲区静态实例.
private static NotSafeDataBuffer<IGoods> notSafeDataBuffer
= new NotSafeDataBuffer<>();
//生产者执行的动作.
static Callable<IGoods> produceAction = () -> {
//生成一个产品.
IGoods goods = Goods.produceOne();
try {
notSafeDataBuffer.add(goods);
} catch (Exception e) {
e.printStackTrace();
}
return goods;
};
//消费者的动作.
static Callable<IGoods> consumerAction = () -> {
//从petStore获取商品.
IGoods goods = null;
try {
goods = notSafeDataBuffer.get();
} catch (Exception e) {
e.printStackTrace();
}
return goods;
};
}
不安全的生产者和消费者的版本
public class NotSafePetStore {
//数据缓冲区静态实例.
private static NotSafeDataBuffer<IGoods> notSafeDataBuffer
= new NotSafeDataBuffer<>();
//生产者执行的动作.
static Callable<IGoods> produceAction = () -> {
//生成一个产品.
IGoods goods = Goods.produceOne();
try {
notSafeDataBuffer.add(goods);
} catch (Exception e) {
e.printStackTrace();
}
return goods;
};
//消费者的动作.
static Callable<IGoods> consumerAction = () -> {
//从petStore获取商品.
IGoods goods = null;
try {
goods = notSafeDataBuffer.get();
} catch (Exception e) {
e.printStackTrace();
}
return goods;
};
public static void main(String[] args) {
//同时并发执行的线程数.
final int THREAD_TOTAL = 20;
//线程池多用于线程测试模拟.
ExecutorService threadPool
= Executors.newFixedThreadPool(THREAD_TOTAL);
for (int i = 0; i < 5; i++) {
//生产者实例每生产一个产品,间隔五百毫秒.
threadPool.submit(new Producer(produceAction, 500));
//消费者实例消费一个产品间隔1500
threadPool.submit(new Consumer(consumerAction, 1500));
}
}
}
从运行结果可以看出出现了共享数据不一致问题.
线程安全的实现版本
public class SafeDataBuffer<T> {
public static final int MAX_AMOUNT = 10;
private LinkedList<T> dataList = new LinkedList<>();
//保存数量.
private AtomicInteger amount = new AtomicInteger(0);
//向数据区添加一个元素.
public synchronized void add(T element) throws Exception {
if (amount.get() > MAX_AMOUNT) {
throw new Exception("队列已满");
}
dataList.add(element);
amount.incrementAndGet();
//如果数据不一致抛出异常.
if (amount.get() != dataList.size()) {
throw new Exception(amount.get() + "!=" + dataList.size());
}
}
//从数据区中取出元素.
public synchronized T get() throws Exception {
if (amount.get() <= 0) {
System.out.println("队列已经空了.");
return null;
}
T value = dataList.remove(0);
amount.decrementAndGet();
//数据不一致抛出异常.
if (amount.get() != dataList.size()) {
throw new Exception(amount.get() + "!=" + dataList.size());
}
return value;
}
}
很简单就是给两个方法加上synchronized关键字就不会出现对应的问题了.