Java多线程:生产者与消费者案例
作者:鱼小洲 发布时间:2021-06-30 05:47:08
前言
想象一下生活中哪些是和线程沾边的?饭店炒菜就是一个很好的例子
首先客人要吃菜,前提是厨师要炒好,也就是说,厨师不炒好的话客人是没有饭菜的。这时候,厨师就是一个线程,客人拿菜就是另一个线程。
工具
jdk13,IDEA2019.1.4
知识点
Thread、Runnable、synchronized、面向对象知识(继承、封装、接口、方法重写)、条件判断以及线程的一些其他知识点
设计思路
首先要有两个线程,也就是说要两个类,分别是Producer(生产者)和Consumer(消费者)。
由于我们是模拟厨师与客人之间的互动,也就是说还需要一个类来封装信息:Message(类)。
然后,避免线程之间发生数据混乱的情况,肯定还需要使用synchronized来进行线程同步。
具体步骤
首先我们来一份没有用synchronized的代码,先看看效果:
public class Message {
private String title;
private String content;
Message(){
};
public Message(String title, String content) {
this.title = title;
this.content = content;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
/*
* 定义生产者类Producer
* */
class Producer implements Runnable{
private Message msg=null;
public Producer(Message msg) {
this.msg = msg;
}
@Override
public void run() {
for (int i=0;i<=50;i++){
if (i%2==0){
this.msg.setTitle("喜欢夜雨吗?");
try {
Thread.sleep(100);
}catch (InterruptedException e){
System.out.println(e);
}
this.msg.setContent("是的呢!");
}else {
this.msg.setTitle("还不关注我吗?");
try {
Thread.sleep(100);
}catch (InterruptedException e){
System.out.println(e);
}
this.msg.setContent("好的呢!");
}
}
}
}
/*
* 定义消费者类Consumer
* */
class Consumer implements Runnable{
private Message msg=null;
public Consumer(Message msg) {
this.msg = msg;
}
@Override
public void run() {
for (int i=0;i<=50;i++){
try {
Thread.sleep(100);
}catch (InterruptedException e){
System.out.println(e);
}
System.out.println(this.msg.getTitle()+"--->"+this.msg.getContent());
}
}
}
class TestDemo{
public static void main(String[] args) {
Message msg=new Message();
new Thread(new Producer(msg)).start();
new Thread(new Consumer(msg)).start();
}
}
看看运行结果:
看仔细咯,发生了数据错乱啊,Title与Content没有一一对应欸。咋办?
能咋办,改代码呗。
发生数据混乱的原因完全是因为,生产者线程还没生产的时候,消费者就已经消费了(至于消费的啥我也不知道,我也不敢问啊)。所以造成了数据混乱,不过我们上面说了啊,要使用synchronized来让线程同步一下。但是又会出问题,我们接着往下看
class TestDemo{
public static void main(String[] args) {
Message msg=new Message();
new Thread(new Producer(msg)).start();
new Thread(new Consumer(msg)).start();
}
}
class Message{
private String title,content;
public synchronized void set(String title,String content){
this.title=title;
this.content=content;
}
public synchronized void get(){
try {
Thread.sleep(1000);
}catch (InterruptedException e){
System.out.println(e);
}
System.out.println(this.title+"-->"+this.content);
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
class Producer implements Runnable{
private Message msg=null;
Producer(Message msg){
this.msg=msg;
}
@Override
public void run() {
for (int i=0;i<=50;i++){
if (i%2==0){
this.msg.set("喜欢夜雨吗?","是的呢!");
}else {
this.msg.set("还不关注吗?","好的呢!");
}
}
}
}
class Consumer implements Runnable{
private Message msg=null;
Consumer(Message msg){
this.msg=msg;
}
@Override
public void run() {
for (int i=0;i<=50;i++){
this.msg.get();
}
}
}
我又重新封装了一些方法,然后运行的时候,wc,数据倒是不混乱了,但是呢,数据重复了一大堆。
为啥会出现这个问题呢?最后想了一下,会出现这种问题的,就是因为线程的执行顺序的问题。我们想要实现的效果是生产者线程执行了之后,让生产者线程等待,而后让消费者线程执行,等待消费者线程执行完成之后就又让生产者线程继续执行。后来我又查了查官方文档,发现Object类中专门有三个方法是与线程相关的:
方法 | 描述 |
---|---|
public final void wait() throws InterruptedException | 线程的等待 |
public final void notify() | 唤醒第一个等待线程 |
public final void notifyAll() |
当我看到这些方法的时候,心里愣了一下,这不就是我们想要的方法吗,用wait()方法来让生产者线程等待,然后运行消费者线程,等消费者线程执行完了之后又让生产者线程去执行。这其中我们用true和false来表示运行开始和运行暂停。
最后我们来看看完成品的代码:
class TestDemo{
public static void main(String[] args) {
Message msg=new Message();
new Thread(new Producer(msg)).start();
new Thread(new Consumer(msg)).start();
}
}
class Message extends Exception{
private String title,content;
private boolean flag=true;
// true表示正在生产,不要来取走,因为没由让消费者区走的
// false表示可以取走,但是不能生产
public synchronized void set(String title,String content){
if (this.flag==false){
try {
super.wait();
}catch (InterruptedException e){
System.out.println(e);
}
}
this.title=title;
try {
Thread.sleep(60);
}catch (InterruptedException e){
System.out.println(e);
}
this.content=content;
this.flag=true; // 生产完成,修改标志位
super.notify(); // 唤醒等待线程
}
public synchronized void get(){
if (flag==true) {// 已经生产好了,等待取走
try {
super.wait();
}catch (InterruptedException e){
System.out.println(e);
}
}
try {
Thread.sleep(60);
}catch (InterruptedException e){
System.out.println(e);
}
System.out.println(this.title+"-->"+this.content);
this.flag=true;
super.notify();
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
class Producer implements Runnable{
private Message msg=null;
Producer(Message msg){
this.msg=msg;
}
@Override
public void run() {
for (int i=0;i<=50;i++){
if (i%2==0){
this.msg.set("喜欢夜雨吗?","是的呢!");
}else {
this.msg.set("还不关注吗?","好的呢!");
}
}
}
}
class Consumer implements Runnable{
private Message msg=null;
Consumer(Message msg){
this.msg=msg;
}
@Override
public void run() {
for (int i=0;i<=50;i++){
this.msg.get();
}
}
}
运行结果我就不贴了,你们自己去测试一下吧…
来源:https://blog.csdn.net/weixin_43581288/article/details/104784227


猜你喜欢
- Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念。先来看名词解释。工作区(Working Directory)就是你在电脑里
- 本文实例讲述了C#实现的简单随机数产生器功能。分享给大家供大家参考,具体如下:运行效果如下:具体代码如下:using System;usin
- Mavan pom文件引用依赖 <!-- hutool工具类--><dependency><gro
- 一. SpringBoot中实现Session共享1. 创建web项目我们按照之前的经验,创建一个web程序,并将之改造成Spring Bo
- 今天讲一下目前移动领域很常用的技术——二维码。现在大街小巷、各大网站都有二维码的踪迹,不管是IOS、Android、WP都有相关支持的软件。
- Java处理JSON数据有三个比较流行的类库FastJSON、Gson和Jackson。JacksonJackson是由其社区进行维护,简单
- java String的深入理解一、Java内存模型 按照官方的说法:Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和
- 无论是用Eclipse还是用Android Studio做android开发,都会接触到jar包,全称应该是:Java Archive,即j
- 安装java的运行环境IDEA一 找到并下载IDEA百度IDEA找到官网,往下翻找到Java并点击找到 并下载IDEA右边为开源的免费版本,
- JDK 中提供了一些对无状态协议请求(HTTP )的支持,下面我就将我所写的一个小例子(组件)进行描述:首先让我们先构建一个请求类(Http
- 本文实例讲述了Android开发使用Messenger及Handler进行通信的方法。分享给大家供大家参考,具体如下:1. 客户端servi
- Vector简介ArrayList 和 Vector 其实大同小异,基本结构都差不多,但是一些细节上有区别:比如线程安全与否,扩容的大小等,
- String 不是简单类型,而是一个类,它被用来表示字符序列。字符本身符合 Unicode 标准,其初始化方式有两种。如:String gr
- 题目:编写一个程序,在面板上移动小球。应该定义一个面板类来显示小球,并提供向上下左右移动小球的方法。请进行边界检查以防止小球移动到视线之外。
- 前言我们在 页面切换转场动画,英雄救场更有趣!介绍了 Hero 动画效果,使用 Hero 用于转场能够提供非常不错的体验。既然称之
- 找不同给定两个字符串 s 和 t ,它们只包含小写字母。字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。
- 前言之前写过一篇介绍flutter集成到Android工程的文章,这次总结记录一下自己把flutter集成到iOS的流程,以及遇到的问题以及
- 在一个项目中我们可能会需要用到相同的布局设计,如果都写在一个xml文件中,代码显得很冗余,并且可读性也很差,所以我们可以把相同布局的代码单独
- 在Servlet2.5中,我们要实现文件上传功能时,一般都需要借助第三方开源组件,例如Apache的commons-fileupload组件
- 本文实例讲述了C#实现的海盗分金算法。分享给大家供大家参考,具体如下:海盗分金的故事5个海盗抢到了100颗宝石,每一颗都一样的大小和价值连城