1. wait与notify方法
这两个方法实现线程间同步(通信);
调用wait()和notify()方法都需要先获取到该对象的Monitor锁,即调用这两个方法必须包含在synchronized代码块中;
每一个对象都有两个队列:
黄色部分正是证明了: 在线程被notify方法唤醒后,并不会马上继续执行这个线程,而是要等到调用notify的那个线程执行结束后释放了对象锁,这个线程才有机会去获取到锁;
2. 单线程版生产与消费者模型
单线程的生产消费者模型:
class Goods {
private String goodsName;
private int count;
public synchronized void set(String goodsName) {
if(count == 1) {
System.out.println("此时还有商品,需要等待消费者消费了再继续生产!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.goodsName = goodsName;
count++;
System.out.println(Thread.currentThread().getName()+"生产:"+ this);
//唤醒消费者线程
this.notify();
}
public synchronized void get() {
if(count == 0) {
System.out.println("此时商品卖完了,需要等待生产者生产!");
try {
//this代表Goods对象,就是生产者或者消费者传进来的那个产品对象
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println(Thread.currentThread().getName() + "消费:"+this);
//唤醒生产者
this.notify();
}
@Override
public String toString() {
return "Goods{" +
"goodsName='" + goodsName + '\'' +
", count=" + count +
'}';
}
}
class Producer implements Runnable{
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
this.goods.set("macbook");
}
}
class Consumer implements Runnable {
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
this.goods.get();
}
}
public class PCTest {
public static void main(String[] args) throws Exception{
Goods goods = new Goods();
Producer producer = new Producer(goods);
Consumer consumer = new Consumer(goods);
Thread thread = new Thread(producer, "生产者");
Thread thread1 = new Thread(consumer,"消费者");
thread1.start();
Thread.sleep(1000);
thread.start();
}
}
3. 完备的生产与消费者模型⭐
首先注意下面几点:
- 我们的生产者消费者操作的都是同一个队列,即多个线程要同时对队列进行增和删的操作,所以我们需要在每个线程中先获取的这个队列对象的锁,再进行操作,这样就是线程安全的,所以无论何时,多个生产者和多个消费者中有且只有一个在操作商品队列; ⭐
- 唤醒方法用notifyAll()而不能用notify(),在多消费者与多生产者时,如果用notify(),可能会造成假死的状况,也就是所有的生产者与消费者都进入了waiting状态,没有线程再能够将他们唤醒;
具体原因是notify()只能唤醒一个线程,当连续唤醒的都是同一类型时(即生产者唤醒了生产者,消费者唤醒了消费者),很可能出现假死的情况;
第一步:先来创建一个商品类,这个类很简单,代码如下:
public class Goods {
private String name;
private double price;
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
接下来创建生产者: 特别注意下面几点
-
run方法中的wile(true)的目的是让每个生产者可以持续生产,不至于一次生产后就线程结束了;
-
run方法中的那个延时有两个目的,一是为了让每个线程间的竞争更加公平(⭐),二是模拟生产者的生产需要一定时间;
-
栗子:假如现在队列中有多个商品,假如现在消费者1拿到了商品队列的对象锁,进入了synchronized块中进行了消费,此时结束后又回到了下一次循环,这时没有延时,该线程又立马进入synchronized块进行下一次消费,让其他消费线程无法夺取到对象锁,直到这个线程把所有的商品消费完,然后才调用wait方法,此时其他线程才有机会夺取到锁;
注意:虽然消费者1执行完一次消费后会释放锁了再进入下一次循环,看起来是和其他线程公平竞争的,但是线程在刚释放锁后更容易马上又抢到锁,这里我们去掉消费者和生产者的延时,运行得到下面结果:Producer–0生成商品:Goods{name=‘商品-9698’, price=1.6664665647848553}
Producer–0生成商品:Goods{name=‘商品-9699’, price=9.463335771991765}
Producer–0生成商品:Goods{name=‘商品-9700’, price=2.6666139072229678}
Producer–0生成商品:Goods{name=‘商品-9701’, price=9.840984890797923}
Producer–0生成商品:Goods{name=‘商品-9702’, price=3.7271200220546374}
Producer–0 商品队列已满…等待。。。。
Consumer–3获取到商品 :Goods{name=‘商品-9695’, price=1.2373569995397937}
Consumer–3获取到商品 :Goods{name=‘商品-9696’, price=6.987477844596761}
Consumer–3获取到商品 :Goods{name=‘商品-9697’, price=1.5927845982781375}
Consumer–3获取到商品 :Goods{name=‘商品-9698’, price=1.6664665647848553}
Consumer–3获取到商品 :Goods{name=‘商品-9699’, price=9.463335771991765}
Consumer–3获取到商品 :Goods{name=‘商品-9700’, price=2.6666139072229678}
Consumer–3获取到商品 :Goods{name=‘商品-9701’, price=9.840984890797923}
Consumer–3获取到商品 :Goods{name=‘商品-9702’, price=3.7271200220546374}
Consumer–3 商品队列已空,通知生产
Consumer–4 商品队列已空,通知生产
Consumer–0 商品队列已空,通知生产
Consumer–2 商品队列已空,通知生产
Consumer–1 商品队列已空,通知生产
Producer–1生成商品:Goods{name=‘商品-9703’, price=9.886119511951076}
Producer–1生成商品:Goods{name=‘商品-9704’, price=2.2185389445487824}
Producer–1生成商品:Goods{name=‘商品-9705’, price=8.570631825557799}
Producer–1生成商品:Goods{name=‘商品-9706’, price=3.974329606563911}可以看到,去掉延时后,生产与消费总是谁先夺取到锁,就会一直消费或者生产直到消费到0或者生产满队列;
-
-
while (this.goodsQueue.size() >= this.maxGoods)这一句中的wile不能改为if哟,如果是if,当商品为0时,此时多个生产者wait在这,当随便一个消费者或其他生产者调用notiyfAll()后,这几个wait的生产者都会继续执行下面的生产代码,并没有再次判断商品总量是否超标,所以会导致商品数量超过我们设定的值;
对应的消费者中如果改成if,则可能会出现消费过度,即没有了还在消费,将会造成队列为空的时候还在出队;
public class Producer implements Runnable{
private final Queue<Goods> goodsQueue;
private final Integer maxGoods;
private final long speed;
//goodID用来给产品编号
private final AtomicInteger goodsID = new AtomicInteger(0);
public Producer(Queue<Goods> goodsQueue, Integer maxGoods, long speed) {
this.goodsQueue = goodsQueue;
this.maxGoods = maxGoods;
this.speed = speed;
}
@Override
public void run() {
while(true) {
//这个延时一定得放在这,不能放在synchronized块中
try {
Thread.sleep(speed);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this.goodsQueue){
while (this.goodsQueue.size() >= this.maxGoods) {
try {
System.out.println(Thread.currentThread().getName()+" 商品队列已满...等待。。。。");
this.goodsQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Goods goods = new Goods("商品-"+goodsID.getAndIncrement(),Math.random()*10);
this.goodsQueue.add(goods);
System.out.println(Thread.currentThread().getName()+"生成商品:"+goods.toString());
this.goodsQueue.notifyAll();
}
}
}
}
消费者与生产者差不多,代码如下:
public class Consumer implements Runnable{
private final Queue<Goods> goodsQueue;
private final long speed;
public Consumer(Queue<Goods> goodsQueue, long speed) {
this.goodsQueue = goodsQueue;
this.speed = speed;
}
@Override
public void run() {
while(true) {
try {
Thread.sleep(speed);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this.goodsQueue) {
while(this.goodsQueue.isEmpty()) {
try {
System.out.println(Thread.currentThread().getName()+" 商品队列已空,通知生产");
this.goodsQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Goods goods = this.goodsQueue.poll(); //检索并删除此队列的头,如果此队列为空,则返回 null 。
if(goods != null) {
System.out.println(Thread.currentThread().getName()+"获取到商品 :"+goods);
}
this.goodsQueue.notifyAll();
}
}
}
}
接下来我们创建一个管理类,用来管理生产与消费者:
public class Manager {
final long Pspeed;
final long Cspeed;
final int numOfProducer;
final int numOfComsumer;
final int maxGoods;
final Queue<Goods> goodsQueue = new LinkedList<>();
final Producer producer;
final Consumer consumer;
public Manager(Properties properties) {
Pspeed = Integer.parseInt(properties.getProperty("producer.speed"));
Cspeed = Integer.parseInt(properties.getProperty("consumer.speed"));
numOfProducer = Integer.parseInt(properties.getProperty("producer.num"));
numOfComsumer = Integer.parseInt(properties.getProperty("consumer.num"));
maxGoods = Integer.parseInt(properties.getProperty("good.maxGoods"));
producer = new Producer(goodsQueue,maxGoods,Pspeed);
consumer = new Consumer(goodsQueue,Cspeed);
}
public void start() {
for(int i=0; i<numOfProducer; i++) {
new Thread(producer,"Producer--"+i).
start();
}
for(int i=0; i<numOfComsumer; i++) {
new Thread(consumer,"Consumer--"+i).
start();
}
}
//打印配置信息
public String getCurrentInfo() {
StringBuilder sb = new StringBuilder();
sb.append("-- 当前生产者消费者模型中的配置信息如下 --");
sb.append("\n");
sb.append("容量:");
sb.append(this.maxGoods);
sb.append("\n");
sb.append("生产者数量:");
sb.append(this.numOfProducer);
sb.append("\n");
sb.append("生产速率(ms):");
sb.append(this.Pspeed);
sb.append("\n");
sb.append("消费者数量:");
sb.append(this.numOfComsumer);
sb.append("\n");
sb.append("消费速率(ms):");
sb.append(this.Cspeed);
sb.append("\n");
return sb.toString();
}
}
上面管理类我们需要创建一个对应的属性文件,装载着我们的配置信息:
#不能有分号,否则报错
producer.num = 3
producer.speed = 1500
consumer.num = 5
consumer.speed = 2500
good.maxGoods = 8
最后建立测试类:
public class TestProducerConsumer {
public static void main(String[] args) {
Properties properties = new Properties();
try(InputStream inputStream = TestProducerConsumer
.class.getClassLoader().getResourceAsStream("com/bittech/com/bittech/pc/hello.properties")) {
properties.load(inputStream);
}catch (IOException e) {
e.printStackTrace();
}
Manager manager = new Manager(properties);
//打印属性文件中设置的参数
System.out.println(manager.getCurrentInfo());
manager.start();
}
}
进行测试,结果如下:
– 当前生产者消费者模型中的配置信息如下 –
容量:8
生产者数量:3
生产速率(ms):1500
消费者数量:5
消费速率(ms):2500Producer–0生成商品:Goods{name=‘商品-0’, price=3.298510421377341}
Producer–1生成商品:Goods{name=‘商品-1’, price=4.406656146706069}
Producer–2生成商品:Goods{name=‘商品-2’, price=0.6762076601105449}
Consumer–1获取到商品 :Goods{name=‘商品-0’, price=3.298510421377341}
Consumer–0获取到商品 :Goods{name=‘商品-1’, price=4.406656146706069}
Consumer–3获取到商品 :Goods{name=‘商品-2’, price=0.6762076601105449}
Consumer–2 商品队列已空,通知生产
Consumer–4 商品队列已空,通知生产
Producer–0生成商品:Goods{name=‘商品-3’, price=4.97265347079696}
Consumer–4获取到商品 :Goods{name=‘商品-3’, price=4.97265347079696}
Consumer–2 商品队列已空,通知生产
Producer–1生成商品:Goods{name=‘商品-4’, price=8.870488585259725}
Producer–2生成商品:Goods{name=‘商品-5’, price=2.179215869410979}
Consumer–2获取到商品 :Goods{name=‘商品-4’, price=8.870488585259725}
Producer–2生成商品:Goods{name=‘商品-6’, price=0.7527747030954623}
Producer–0生成商品:Goods{name=‘商品-7’, price=0.1380726602635174}
Producer–1生成商品:Goods{name=‘商品-8’, price=0.739801987555937}
Consumer–1获取到商品 :Goods{name=‘商品-5’, price=2.179215869410979}
Consumer–0获取到商品 :Goods{name=‘商品-6’, price=0.7527747030954623}
Consumer–3获取到商品 :Goods{name=‘商品-7’, price=0.1380726602635174}
Consumer–4获取到商品 :Goods{name=‘商品-8’, price=0.739801987555937}
Consumer–2 商品队列已空,通知生产
Producer–2生成商品:Goods{name=‘商品-9’, price=3.684812679654467}
Consumer–2获取到商品 :Goods{name=‘商品-9’, price=3.684812679654467}
Producer–1生成商品:Goods{name=‘商品-10’, price=7.4808216795036655}
Producer–0生成商品:Goods{name=‘商品-11’, price=9.434210896373118}
Consumer–1获取到商品 :Goods{name=‘商品-10’, price=7.4808216795036655}
Consumer–3获取到商品 :Goods{name=‘商品-11’, price=9.434210896373118}
Consumer–0 商品队列已空,通知生产
Producer–1生成商品:Goods{name=‘商品-12’, price=1.4348959559594199}
Consumer–0获取到商品 :Goods{name=‘商品-12’, price=1.4348959559594199}
Producer–0生成商品:Goods{name=‘商品-13’, price=8.631376618366412}
Producer–2生成商品:Goods{name=‘商品-14’, price=5.5106825887534105}
Consumer–4获取到商品 :Goods{name=‘商品-13’, price=8.631376618366412}Process finished with exit code -1