描述
hello,这里旨在向大家介绍生产者-消费者模式和wait、notify的使用。整体的思路是:
1、探讨一下什么是生产者-消费者模式。
2、线程不安全的生产者-消费者模式的实现。
3、线程安全的生产者-消费者模式的实现。
4、wait()和notify()的原理和使用。
5、使用wait()和notify()对生产者-消费者模式进行优化。
生产者-消费者模式
首先,生产者-消费者问题,也称为有限缓冲问题,是一个多线程同步问题的经典案例。
生产者-消费者问题所涉及到的角色有:
1、缓冲区: 用来存放数据;
2、生产者:多个生产者同时向缓冲区写入数据;
3、消费者:多个消费者同时消费缓冲区的数据;
生产者-消费者问题最重要的是:
1、当缓冲区满了的时候,生产者不能再写入数据,当缓冲区为空的时候,消费者不能再消费数据;
2、要保证在生产者加入、消费者消耗的过程中,不会产生错误的数据和行为。
线程不安全的生产者-消费者模式的实现
首先我们来实现一个非线程安全版本的生产者-消费者模式。它主要包括:
NotSafeDataBuffer:非线程安全的数据缓冲区类
Producer: 生产者实例
Consumer: 消费者者实例
NotSafePetStore: 缓冲区实例、生产动作、消费动作实例。
上面我们将生产者和生产动作、消费者和消费动作进行解耦,使得生产者、消费者可以实现复用。代码如下:
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @desc 不安全的生产者、消费者
* */
public class Demo001_a {
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(NotSafePetStore.produceAction,500));
//消费者
threadPool.submit(new Producer(NotSafePetStore.consumerAction,1500));
}
}
/**
* @desc 非线程安全的数据缓冲区类
* */
static 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){
System.out.println("队列已经满了!!!");
return;
}
dataList.add(element);
System.out.println( "add " + element);
amount.incrementAndGet();
//如果数据不一致,就抛出异常
if(amount.get()!=dataList.size()){
throw new Exception(amount + "!=" + dataList.size() + " add");
}
}
//从数据读出一个元素
public T fetch() throws Exception{
if(amount.get()<=0){
System.out.println("队列已经空了!!!");
return null;
}
T element = dataList.remove(0);
System.out.println( "remove " + element);
amount.decrementAndGet();
//如果数据不一致,就抛出异常
if(amount.get()!=dataList.size()){
throw new Exception(amount + "!=" + dataList.size() + " fetch");
}
return element;
}
}
/**
* @desc 生产者实例
* */
static class Producer implements Runnable{
//生产的时间间隔,200毫秒
public static final int PRODUCER_GAP = 200;
//总次数
static final AtomicInteger TURN = new AtomicInteger(0);
//生产者对象编号
static final AtomicInteger PRODUCER_NO = new AtomicInteger(1);
String name = null; //生产者名称
Callable action = null; //生产动作
int gap = PRODUCER_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 out = action.call();
//输出生产结果
if(null != out){
System.out.println(name + ":第" + TURN.get() + "轮生产:" + out);
}
//每一轮生产之后,稍微等待一下
Thread.sleep(gap);
//增加生产轮次
TURN.incrementAndGet();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
/**
* @desc 消费者者实例
* */
static class Consumer implements Runnable{
//消费的时间间隔,100毫秒
public static final int CONSUMER_GAP = 100;
//总次数
static final AtomicInteger TURN = new AtomicInteger(0);
//消费者对象编号
static final AtomicInteger CONSUMER_NO = new AtomicInteger(1);
String name = null; //消费者名称
Callable action = null; //消费者动作
int gap = CONSUMER_GAP;
public Consumer(Callable action, int gap){
this.action = action;
this.gap = gap;
name = "消费者-" + CONSUMER_NO.incrementAndGet();
}
@Override
public void run() {
while(true){
try {
//增加消费次数
TURN.incrementAndGet();
//执行消费
Object out = action.call();
//输出生产结果
if(null != out){
System.out.println(name + "第" + TURN.get() + "轮消费:" + out);
}
//每一轮消费之后,稍微等待一下
Thread.sleep(gap);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
static class NotSafePetStore{
//缓冲区
private static NotSafeDataBuffer<String>
buffer = new NotSafeDataBuffer();
//生产者执行动作
static Callable<String> produceAction = () ->{
String str = randomStr();
try{
buffer.add(str);
}catch (Exception ex ){
ex.printStackTrace();
}
return str;
};
//消费者执行动作
static Callable<String> consumerAction = () ->{
String str = null;
try{
str = buffer.fetch();
}catch (Exception ex ){
ex.printStackTrace();
}
return str;
};
/**
* 生成随机字符串
* */
public static String randomStr(){
Random random = new Random();
random.setSeed(1000L);
return random.nextInt() + " - producer";
}
}
}
程序开始并发执行一段时间之后,就会出现下面的问题:
java.lang.Exception: 2!=1 add
at com.java.basic.thread.demo004.Demo001_a$NotSafeDataBuffer.add(Demo001_a.java:49)
at com.java.basic.thread.demo004.Demo001_a$NotSafePetStore.lambda$static$0(Demo001_a.java:162)
at com.java.basic.thread.demo004.Demo001_a$Producer.run(Demo001_a.java:95)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
这是因为多线程并发操作amount、dataList两个成员操作时次序混乱导致的。
线程安全的生产者-消费者模式的实现
解决线程安全问题很简单,为临界区代码加上 synchronized关键字即可。修改的地方在于创建一个数据缓存区安全的类SafeDataBuffer。
/**
* @desc 线程安全的数据缓冲区类
* */
static class SafeDataBuffer<T>{
public static final int MAX_AMOUNT = 10;
private List<T> dataList = new LinkedList<>();
//保存数量
private AtomicInteger amount = new AtomicInteger(0);
//向数据区增加一个元素
public synchronized void add(T element) throws Exception{
if(amount.get()>MAX_AMOUNT){
System.out.println("队列已经满了!!!");
return;
}
dataList.add(element);
System.out.println( "add " + element);
amount.incrementAndGet();
//如果数据不一致,就抛出异常
if(amount.get()!=dataList.size()){
throw new Exception(amount + "!=" + dataList.size() + " add");
}
}
//从数据读出一个元素
public synchronized T fetch() throws Exception{
if(amount.get()<=0){
System.out.println("队列已经空了!!!");
return null;
}
T element = dataList.remove(0);
System.out.println( "remove " + element);
amount.decrementAndGet();
//如果数据不一致,就抛出异常
if(amount.get()!=dataList.size()){
throw new Exception(amount + "!=" + dataList.size() + " fetch");
}
return element;
}
}
其它的代码一行不动。我们发现现在多个生产者和多个消费者已经可以正常的生产消费了。但是上面的解决方式使用了对象锁作为同步锁,使得所有的生产者和消费者都抢占同一个同步锁,最终所有的生产、消费动作都被串行化。
wait()和notify()的原理和使用
java对象中的wait()、notify()两个方法就如同信号开关,用于等待方和通知方之间的交互。
wait():阻塞当前线程并等待被唤醒。
notify(): 唤醒使用wait()进入阻塞状态的线程。
wait()方法的核心原理大致如下:
1、当线程调用了某个锁对象的wait方法之后,JVM会将当前线程加入锁对象的监视器的WaitSet(等待集合),等待被其他线程唤醒,关于锁对象的监视器(Monitor),大家可自行查阅资料了解。
2、当前线程会释放锁对象监视器的Owner权利,让其它线程抢夺锁对象监视器。
3、当前线程状态变成WAITING。
notify 方法有两个版本:
1、void notify():唤醒锁对象的监视器的WaitSet中的第一条等待线程。
2、void notifyAll():唤醒锁对象的监视器的WaitSet中的所有线程。
当等待线程被唤醒之后,会从监视器的WaitSet移动到EntryList,线程变为BLOCKED状态,重新竞争锁对象。
wait、notify 案例演示如下:
import java.util.Scanner;
/**
* @desc wait 和 notify原理
* */
public class Demo001_c {
public static void main(String[] args) {
new Thread(new WaitTarget(),"waitThread").start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(new NotifyTarget(),"notifyThread").start();
}
static Object locko = new Object();
//等待线程
static class WaitTarget implements Runnable{
@Override
public void run() {
synchronized (locko){ //加锁
try {
System.out.println("开始等待...");
locko.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("收到通知,当前线程继续执行...");
}
}
}
//通知线程
static class NotifyTarget implements Runnable{
@Override
public void run() {
synchronized (locko){ //加锁
//接收屏幕输入yes
Scanner input = new Scanner(System.in);
System.out.println("是否唤醒等待线程:");
if(input.next().equals("yes")) {
locko.notifyAll();
}
System.out.println("通知线程发出通知...");
}
}
}
}
在屏幕中输入"yes"之后,等待的线程被唤醒。
需要注意的是 wait、notify 必须在synchronized同步块的内部使用,因为它们是锁对象提供的方法。
使用wait()和notify()对生产者-消费者模式进行优化
我们在上文实现的生产者-消费者模式,当缓冲区写满之后,生产者线程会进入轮询状态;当缓冲区为空的时候,消费者会进入轮询状态,这种轮询是比较消耗cpu的性能的。比较好的方式是通过wait()让线程进入阻塞状态,然后等待notify()唤醒。
我们会在SafeDataBuffer中加入3把锁:
LOCK_BOJECT :保护临界区资源。保护dataList和amount的操作。
NOT_FULL : 用于缓冲区满了之后,使得生产者线程进入阻塞;当有消费者消费之后,通知生产者进行生产。
NOT_EMPTY: 用于缓冲区为空,使得消费者线程进入阻塞;当有生产者生产之后,通知阻塞的消费者进行消费。
代码大致如下:
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @desc wait notify 和生产者消费者模式
* */
public class Demo001_d {
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 Consumer(SafePetStore.consumerAction,1500));
//生产者
threadPool.submit(new Producer(SafePetStore.produceAction,500));
}
}
/**
* @desc 数据缓冲区类
* */
static class SafeDataBuffer<T>{
public static final int MAX_AMOUNT = 10; //数据缓冲区的最大长度
private List<T> dataList = new LinkedList<>();
private Integer amount = 0; //数据缓冲区长度
private final Object LOCK_BOJECT = new Object(); //保护临界区资源
//用于缓冲区满了之后,使得生产者线程进入阻塞;当有消费者消费之后,通知生产者进行生产。
private final Object NOT_FULL = new Object();
//用于缓冲区为空,使得消费者线程进入阻塞;当有生产者生产之后,通知阻塞的消费者进行消费。
private final Object NOT_EMPTY = new Object();
//向数据区增加一个元素
public void add(T element) throws Exception{
while(amount>MAX_AMOUNT)
{
synchronized (NOT_FULL){
System.out.println("队列已经满了!!!");
NOT_FULL.wait();
}
}
synchronized (LOCK_BOJECT){
dataList.add(element);
amount++;
System.out.println( "add " + element);
}
synchronized (NOT_EMPTY){
NOT_EMPTY.notify(); //发送未空通知
}
}
//从数据读出一个元素
public T fetch() throws Exception{
while(amount<=0)
{
synchronized (NOT_EMPTY){
System.out.println("队列已经空了!!!");
NOT_EMPTY.wait();
}
}
T element = null;
synchronized (LOCK_BOJECT){
element = dataList.remove(0);
System.out.println( "remove " + element);
amount--;
}
synchronized (NOT_FULL){
NOT_FULL.notify(); //发送未满通知
}
return element;
}
}
/**
* @desc 生产者实例
* */
static class Producer implements Runnable{
//生产的时间间隔,200毫秒
public static final int PRODUCER_GAP = 200;
//总次数
static final AtomicInteger TURN = new AtomicInteger(0);
//生产者对象编号
static final AtomicInteger PRODUCER_NO = new AtomicInteger(1);
String name = null; //生产者名称
Callable action = null; //生产动作
int gap = PRODUCER_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 out = action.call();
//输出生产结果
if(null != out){
System.out.println(name + "-第" + TURN.get() + "轮生产:" + out);
}
//每一轮生产之后,稍微等待一下
Thread.sleep(gap);
//增加生产轮次
TURN.incrementAndGet();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
/**
* @desc 消费者者实例
* */
static class Consumer implements Runnable{
//消费的时间间隔,100毫秒
public static final int CONSUMER_GAP = 100;
//总次数
static final AtomicInteger TURN = new AtomicInteger(0);
//消费者对象编号
static final AtomicInteger CONSUMER_NO = new AtomicInteger(1);
String name = null; //消费者名称
Callable action = null; //消费者动作
int gap = CONSUMER_GAP;
public Consumer(Callable action, int gap){
this.action = action;
this.gap = gap;
name = "消费者-" + CONSUMER_NO.incrementAndGet();
}
@Override
public void run() {
while(true){
try {
//增加消费次数
TURN.incrementAndGet();
//执行消费
Object out = action.call();
//输出生产结果
if(null != out){
System.out.println(name + "第" + TURN.get() + "轮消费:" + out);
}
//每一轮消费之后,稍微等待一下
Thread.sleep(gap);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
static class SafePetStore{
//缓冲区
private static SafeDataBuffer<String>
buffer = new SafeDataBuffer();
//生产者执行动作
static Callable<String> produceAction = () ->{
String str = randomStr();
try{
buffer.add(str);
}catch (Exception ex ){
ex.printStackTrace();
}
return str;
};
//消费者执行动作
static Callable<String> consumerAction = () ->{
String str = null;
try{
str = buffer.fetch();
}catch (Exception ex ){
ex.printStackTrace();
}
return str;
};
/**
* 生成随机字符串
* */
public static String randomStr(){
Random random = new Random();
random.setSeed(1000L);
return random.nextInt() + " - producer";
}
}
}