难度
初级
学习时间
30分钟
适合人群
零基础
开发语言
Java
开发环境
- JDK v11
- IntelliJ IDEA v2018.3
友情提示
- 本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
- 本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!
1.温故知新
前面在 一章中介绍了 等待唤醒机制 。
在 一章中介绍了 生产者与消费者线程 。
现在我们来讲解 用同步方法实现的生产者与消费者线程例子 。
2.什么是生产者与消费者线程?
如上图所示, 生产者与消费者线程指的是生产者线程和消费者线程,生产者线程负责生产数据,消费者线程负责消费数据。
3.生产者消费者例子
在 一章中最后的完整生产者消费者例子。
演示:
请写一个生产者消费者线程例子。
请观察程序代码及结果。
代码:
Data类:
Producer类:
Consumer类:
Main类:
结果:
从运行结果来看,程序没有问题。
例子一步一步是怎么来的,在上一章 中写得很清楚,感兴趣的小伙伴可以前去阅读。
下面我们要使用同步方法来改写这个例子。
4.同步方法改写生产者消费者线程例子
首先,我们先把数据类Data里面的getMessage()方法和setMessage(String message)方法改为同步方法:
然后,我们把生产者任务类里面的 计数器 属性移到数据类Data里面,先看一下生产者任务类里面的计数器属性长什么样:
把它移到数据类Data里面去:
接着,我们把setMessage(String message)方法里面新的数据信息等于传递进行的数据信息加上计数器属性count++:
计数器移完了之后,生产者任务类里面就没法使用count属性:
于是得将后面“ + count++ ”这行代码移除掉,下面一行代码直接改为 data.setMessage(“你好!”) :
因为数据类Data里面的getMessage()方法和setMessage(String message)方法都已经是同步方法了,所以可以把生产者任务类和消费者任务类中的同步代码块去掉了,请注意:只去掉同步代码块,保留同步代码块里的内容。
这是生产者任务类去掉同步代码块后的样子:
这是消费者任务类去掉同步代码块后的样子:
然后,我们将生产者任务类中的while循环内容移到数据Data类的setMessage(String message)方法中去。
这是生产者任务类中的while循环内容:
移到数据Data类的setMessage(String message)方法中去:
移完后的生产者任务类:
紧接着,我们将消费者任务类中的while循环内容移到数据Data类的getMessage()方法中去。
这是消费者任务类中的while循环内容:
移到数据Data类的getMessage()方法中去:
移完后的消费者任务类:
我们注意到在消费者任务类中有一个操作是“ 将数据对象里面的数据信息设置为null ”,这个操作是我们在消费完数据信息以后将数据信息设置为null:
以前它是在同步代码块中,现在没了同步代码块它还是安全的吗?
它已经是一个不安全的操作啦 ,为什么呢?
这里很有可能造成多次消费 ,怎么多次呢?
假设生产者线程已经生产好数据信息,现在有一个线程1拿到CPU执行权,执行到消费者任务run()方法,run()方法里面有 data.getMessage(),于是执行getMessage()方法 :
此时while判断添加为true,继续往下执行:
getMessage()方法执行完毕后:
释放锁,还没等到线程1消费完此次获取到数据信息,就有另外一个线程抢到同步锁了,于是又去执行getMessage()方法,如此反复,同一个数据信息被消费好几次。
怎么解决这个问题呢?
我们可以把这个操作放到同步代码块/方法中去,这样就安全了。
于是,将“ data.setMessage(null); ”操作移到数据Data类中的getMessage()方法中:
此时还有问题,这个 data对象 在数据Data类中不存在啊,又小伙伴就说,你把“ data. ”去掉就行:
去掉可还行。但是,这样写好像有毛病,你最后是返回message的值,怎么能在返回之前将其设置为null值呢?这不有毛病吗?这样写肯定是错的。
怎么解决这个问题呢?
我们可以通过一个 boolean 类型的变量解决,在数据Data类中定义一个boolean属性empty:
当数据信息已被消费,empty值为true,否则为false。
于是,我们的getMessage()方法和setMessage(String message)方法都得修改。
在setMessage(String message)方法中,我们while语句判断条件是当数据信息未被消费时,线程进行等待,否则继续往下执行,将empty值设置为false,表示数据信息未被消费 :
在getMessage()方法中,我们while语句判断条件是当数据信息被消费时,线程进行等待,否则继续往下执行,将empty值设置为true,表示数据信息被消费 :
最后,还剩 唤醒正在此对象监视器上等待的所有线程 代码没有移到数据Data类中的同步方法去。
即,生产者任务类中的这行代码:
和消费者任务类中的这行代码:
分别移到数据Data类中的setMessage(String message)方法和getMessage()方法里去:
注意notifyAll()方法放的位置 。
有一点,需要注意的是,我们之前想让生产者任务生产数据信息的时候1秒生产一个,于是就写了一段让当前线程睡1秒的代码:
现在要将这段代码也移到数据Data类中的setMessage(String message)方法里去:
好了,整个例子使用同步方法改写完成,整理上述代码之后再运行程序看看。
演示:
请使用同步方法写一个生产者消费者线程的例子。
请观察程序代码及结果。
代码:
Data类:
Producer类:
Consumer类:
Main类:
结果:
从运行结果来看,程序没有任何问题,大功告成!
最后,希望大家可以把这个例子照着写一遍,然后再自己默写一遍,方便以后碰到类似的面试题可以轻松应对。
祝大家编码愉快!
GitHub
本章程序GitHub地址:
总结
- 生产者与消费者线程指的是生产者线程和消费者线程,生产者线程负责生产数据,消费者线程负责消费数据。
- 生产者线程与消费者线程间通过wait()、notify()和notifyAll()方法来协作。
至此,Java中使用同步方法改写生产者消费者线程例子相关内容讲解先告一段落,更多内容请持续关注。
答疑
如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。
上一章
下一章
“全栈2019”Java多线程第二十七章:Lock获取lock/释放unlock锁
学习小组
加入同步学习小组,共同交流与进步。
- 方式一:关注头条号Gorhaf,私信“Java学习小组”。
- 方式二:关注公众号Gorhaf,回复“Java学习小组”。
全栈工程师学习计划
关注我们,加入“全栈工程师学习计划”。
版权声明
原创不易,未经允许不得转载!