39. [Java]生产者消费者模型问题
Java中的多线程问题
生产者消费者模型是多线程中的典型案例。当多线程在运行过程中涉及到了对共享资源进行修改时,就会引起线程安全问题,为了解决此安全问题,便引入了同步机制。
解决方案:
本质:把多线程变成单线程
引入线程同步机制(三种方法)
同步代码块
针对run方法中的代码,使用synchronized关键字,把部分代码添加同步机制
同步机制:在同一个而时间,只能有一个线程执行
原理:线程在执行之前先获取到一个锁,然后开始执行线程任务,只有拿到锁的线程执行完包含在synchronized代码块中的内容之后,才会释放锁,才可以让其他线程拿到这个锁
同步机制,关键是实现利用:锁(对象锁)
锁: 对象锁。任何对象都可以当锁使用
String lock = new String()
Object lock = new Object()
同步方法
针对方法进行同步,同步方法只能用在方法上
格式:public synchronized void method(){…}
同步方法中的锁:
非静态方法:锁是this(锁,当前对象)
静态方法:静态方法没有对象的概念。所示Class(锁,类名.class)
锁机制
39.1. 典型案例一:milk_glass:牛奶生产者和消费者问题
奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
实现代码:
奶箱:
/**
* ClassName: Box
* Author: Roohom
* Function:奶箱
* Date: 2020/8/2 21:09
* Software: IntelliJ IDEA
*/
public class Box {
//定义一个成员变量,表示第x瓶奶
private int milk;
//定义一个成员变量,表示奶箱的状态
private boolean state = false;
//提供存储牛奶和获取牛奶的操作
public synchronized void put(int milk) {
//如果有牛奶,等待消费
if(state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有牛奶,就生产牛奶
this.milk = milk;
System.out.println("送奶工将第" + this.milk + "瓶奶放入奶箱");
//生产完毕之后,修改奶箱状态
state = true;
//唤醒其他等待的线程
notifyAll();
}
public synchronized void get() {
//如果没有牛奶,等待生产
if(!state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果有牛奶,就消费牛奶
System.out.println("用户拿到第" + this.milk + "瓶奶");
//消费完毕之后,修改奶箱状态
state = false;
//唤醒其他等待的线程
notifyAll();
}
}
Producer and Customer:
/**
* ClassName: Producer
* Author: Roohom
* Function:生产者
* Date: 2020/8/2 21:10
* Software: IntelliJ IDEA
*/
public class Producer implements Runnable {
private Box b;
public Producer(Box b) {
this.b = b;
}
@Override
public void run() {
for(int i=1; i<=5; i++) {
b.put(i);
}
}
}
/**
* ClassName: Customer
* Author: Roohom
* Function:消费者
* Date: 2020/8/2 21:10
* Software: IntelliJ IDEA
*/
public class Customer implements Runnable {
private Box b;
public Customer(Box b) {
this.b = b;
}
@Override
public void run() {
while (true) {
b.get();
}
}
}
/**
* ClassName: Customer
* Author: Roohom
* Function:消费者
* Date: 2020/8/2 21:10
* Software: IntelliJ IDEA
*/
public class Customer implements Runnable {
private Box b;
public Customer(Box b) {
this.b = b;
}
@Override
public void run() {
while (true) {
b.get();
}
}
}
测试类:
/**
* ClassName: ProducerAndCustomer
* Author: Roohom
* Function:
* Date: 2020/8/2 21:08
* Software: IntelliJ IDEA
*/
public class ProducerAndCustomer {
public static void main(String[] args) {
//创建奶箱对象,这是共享数据区域
Box b = new Box();
//创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
Producer p = new Producer(b);
//创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
Customer c = new Customer(b);
//创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
//启动线程
t1.start();
t2.start();
}
}
39.2. 典型案例二:多线程猜数字游戏
问题描述:
* 用两个线程玩猜数字游戏,第一个线程负责随机给出1~100之间的一个整数,第二个线程负责猜出这个数。
* 要求每当第二个线程给出自己的猜测后,第一个线程都会提示“猜小了”、“猜大了”或“猜对了”。 猜数之前,要求第二个线程要等待第一个线程设置好要猜测的数。
* 第一个线程设置好猜测数之后,两个线程还要相互等待。
* 其原则是:第二个线程给出自己的猜测后,等待第一个线程给出的提示;第一个线程给出提示后,等待给第二个线程给出猜测。
* 如此进行,直到第二个线程给出正确的猜测后,两个线程进入死亡状态
实现代码:
猜数字类,类似于奶箱:
/**
* ClassName: NumGuess
* Author: Roohom
* Function:两个线程猜数字游戏
* Request: * 用两个线程玩猜数字游戏,第一个线程负责随机给出1~100之间的一个整数,第二个线程负责猜出这个数。
* * 要求每当第二个线程给出自己的猜测后,第一个线程都会提示“猜小了”、“猜大了”或“猜对了”。 猜数之前,要求第二个线程要等待第一个线程设置好要猜测的数。
* * 第一个线程设置好猜测数之后,两个线程还要相互等待。
* * 其原则是:第二个线程给出自己的猜测后,等待第一个线程给出的提示;第一个线程给出提示后,等待给第二个线程给出猜测。
* * 如此进行,直到第二个线程给出正确的猜测后,两个线程进入死亡状态
* Date: 2020/8/2 20:10
* Software: IntelliJ IDEA
*/
public class NumGuess {
//线程一随机生成的让线程二猜的数字
private int num;
//线程二随机生成的猜的数字
private int gsNum;
//猜没猜数字
private boolean guess = false;
//给没给数字
private boolean give = false;
//记录开关状态,用于停止程序
boolean stop = false;
//用来记录猜数字的区间,为了将猜数字的范围缩小
private int min = 1;
private int max = 100;
//用来计数猜了多少次的计数器
int count = 1;
public synchronized void generateNum() {
//如果当前执行的线程是生成数者,也就是第一个线程,并且还没有给出一个数字让第二个线程来猜,那么就需要生成一个数字
if (!give) {
//如果没有数字就生成数字
num = (int) (Math.random() * 100) + 1;
System.out.println("第一个线程正在设置数字...");
System.out.println("本次设置的数字是:" + num);
//生成数字之后,现在有数字了,修改数字状态为true
give = true;
}
//如果没猜,就等着第二个线程来猜数字
while (!guess) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (num > gsNum) {
min = gsNum + 1;
System.out.println("第1个线程回答:猜小了");
} else if (num < gsNum) {
System.out.println("第1个线程回答:猜大了");
max = gsNum - 1;
} else {
System.out.println("第1个线程回答:你猜的数字是" + gsNum);
System.out.println("猜对啦!");
//猜对了程序就可以停下来了,将stop置为true
stop = true;
//return;
}
//没猜,或者猜的都不对,就相当于没猜,那么将guess置为false
guess = false;
//通知第二个线程,现在可以来猜了,也就是唤醒第二个线程
notifyAll();
}
public synchronized void guessNum() {
//如果第二个线程猜了,等待线程一给出提示
while (guess) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果猜的不对,程序就不需要停止,线程二还需要继续猜,也就是再生成一个数
if (!stop) {
gsNum = (int) (Math.random() * (max - min)) + min;
System.out.println("第2个线程第" + (count++) + "次猜的数字是:" + gsNum);
//猜过数字了
guess = true;
}
//通知线程一线程二猜过了,可以进行判断了,也就是唤醒等待中的线程一
notifyAll();
}
}
给出数字让猜的类:
/**
* ClassName: NumGenerator
* Author: Roohom
* Function:
* Date: 2020/8/2 20:44
* Software: IntelliJ IDEA
*/
public class NumGenerator implements Runnable{
private NumGuess n;
public NumGenerator(NumGuess n) {
this.n = n;
}
@Override
public void run() {
//如果猜的不对,线程一就不需要结束
//一开始没有数字就生成一个数字,有了数字就进行给线程二提示
while (!n.stop )
n.generateNum();
}
}
猜数字者:
/**
* ClassName: NumGuesser
* Author: Roohom
* Function:
* Date: 2020/8/2 20:46
* Software: IntelliJ IDEA
*/
public class NumGuesser implements Runnable {
private NumGuess n;
public NumGuesser(NumGuess n) {
this.n = n;
}
@Override
public void run() {
//如果猜的不对,线程二就一直猜,直到猜对
while (!n.stop)
n.guessNum();
}
}
测试类:
/**
* ClassName: GuessNumTest
* Author: Roohom
* Function:
* Date: 2020/8/2 20:47
* Software: IntelliJ IDEA
*/
public class GuessNumTest {
public static void main(String[] args) {
//公用的资源类,里面有共享的变量,类似于生产者和消费者共用的资源,
NumGuess n = new NumGuess();
//将资源作为参数传入数字生成者和猜数者
NumGenerator gen = new NumGenerator(n);
NumGuesser gue = new NumGuesser(n);
//实例化两个线程,启动两个线程
Thread t1 = new Thread(gen,"generator");
Thread t2 = new Thread(gue,"guesser");
t1.start();
t2.start();
}
}