《重学Java高并发》之“摸底考试”:你会使用多线程实现生产者-消费者协作模型吗? 原创

简介: 《重学Java高并发》之“摸底考试”:你会使用多线程实现生产者-消费者协作模型吗?原创

1、消费者/生产者场景


一个非常经典的场景:面包厂生产面包。


在一个面包厂,面包的仓库容积有限,生产工人可以继续生产面包的条件是仓库还有足够的空间,生产的面包是需要派送工人卖给顾客,派送工人要能派送面包的条件是仓库中有剩余的面包。


大概的场景到交付如下图所示:

1142bb4880382433ea13659953c91c3c.png

2、代码实现


有了场景,接下来我们使用java写一个简易的生产者、消费者。


本示例中涉及到类主要如下图所示:

05c9d90667c8822956216c0bd9af88c2.png

其类的职责说明如下:


  • Bakery 面包厂仓库,主要用来存放面包。
  • BreadWork 面包生产工人
  • BreadConsume 面包消费工人
  • Bread 面包


接下来将和大家一一展示代码,同时在介绍代码时将重点阐述线程合作时的一些重点知识,并将提出一个更高难度的思考题供大家挑战。


2.1 Bakery核心实现


Bakery是整个生产者、消费者模型的核心实现类,与多线程编程相关的核心要点也体现在该方法中,其整体代码如下:

538aa54b5ab5b06740bb6ea857c0791b.png

其核心要点解释如下:


  • Object bakeryLock 锁对象,主要是用来保护List< Bread> 数据结构,众所周知,ArrayList是多线程不安全的,也就是说多个线程对其进行访问,必须加锁,这里之所以单独创建一个对象,主要是想突出锁概念,在代码中,其实可以用 synchronized(breads) 来代替。
  • put方法 该方法是被面包生产者调用,向面包厂中添加面包,但是面包厂的容量是有限的,即生产者不能一直往里面添加,即当达到最大容量后,需要阻止生产者继续往里面添加,故这里涉及到条件等待与wait方法,细细说明如下:
  • 访问breads数据结构之前,先使用synchronized进行保护,即加锁。
  • 如果仓库已满,需要调用锁对象的wait方法,调用锁对象的线程,也就是生产者线程会被阻塞,需要等待其他线程的唤醒,唤醒后才能继续执行后续代码。
  • 如果仓库还有空间,则向仓库中添加一个面包,此时另外一个隐含的条件将满足:仓库中已经有面包了,而消费者可能会因为仓库中没有面包而阻塞,故这里需要调用锁对象的notify或nofifyAll方法,唤醒等待的消费者。
  • get方法 该方法主要是被面包消费者调用,从面包中获取面包,但要能获取面包也是有条件的:仓库中存在面包,否则需要阻塞等待消费者创建面包,故这里的要点如下:
  • 如果仓库中没有面包,调用锁对象的wait方法,则消费者线程将进入阻塞状态,其具体实现是消费者线程对象会放在锁对象的条件等待队列,将等待其他线程调用锁的notify或notifyAll。
  • 如果有面包,则从中消费一个面包,此时另外一个隐含的条件将满足:仓库中已经有新的空间存放新的面包,故此时应该调用锁对象的notify或notifyAll,唤醒生产者。


2.2 生产者/消费者代码实现


在该示例中生产者、消费者创建面包,并尝试存储在面包厂中,其代码示例如下:

110d52efda286c1266c108cbb4af0b13.png

消费者、生产者代码比较简单,就不做过多说明。


温馨提示:如果需要整套代码,可以私信我,回复TCODE即可获得。


2.3 运行效果与进阶


该示例的运行效果如下图所示:

1ab0e0f373c951d6a5d37d9ea3ca333e.png

上面的示例其实只是一个入门,重点是了解锁对象,线程之间如何通过notify、wait方法进行协同“作战”,有了上面的示例,我想将难度系数再次提高:


如果做到生产者、消费者交替运行,即生产者生产面包1号,需要等待消费者消费完面包1号后,生产者才能继续生产2号面包。

相关文章
|
4天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
23 9
|
7天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
4天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
6天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
41 1
C++ 多线程之初识多线程
|
22天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
16 3
|
22天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
15 2
|
22天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2
|
22天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
28 1
|
22天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
33 1