赞
踩
最近大热的《哪吒之魔童降世》,唯美的中国风、催泪的剧情、打破固有的成见、解开命运的枷锁......饺子导演带领团队十一年磨一剑的幕后制作故事让人深受感动,实乃国漫良心之作。
在生活中,作为程序员要保持将身边发生的事情代入计算机思考的习惯。其中,在江山社稷图中的一段剧情让人印象深刻:哪吒、太乙真人、敖丙、申公豹四个人为了打开江山社稷图,共同争夺同一支毛笔,由此引发大混乱。
这熟悉的资源争夺,不正像是多线程并发执行引发的竞争状态吗?
多线程编程早已不是一个新鲜概念,在现代计算机的硬件支持下,开启多个线程分段执行耗时任务已成为一种常见的性能优化手段,并且使用多线程还可以充分利用 CPU 的资源。
要理解多线程,需要先搞懂几个概念:
线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文。多线程主要是为了节约 CPU 时间,线程的运行中需要使用计算机的内存资源和 CPU。
下面我们就使用多线程来模拟一下四人抢笔的这段剧情。
先新建一个江山社稷图类:
- object Painting {
- var pen = true
- }
哪吒、太乙真人、敖丙、申公豹都有抢笔动作和打开社稷图动作,我们新建一个接口类来表示这两个动作:
- interface ITask {
-
- fun getPen():Boolean
-
- fun openPainting()
- }
接下来创建哪吒、太乙真人、敖丙、申公豹四个对象,实现此接口:
哪吒:
- class NeZha : ITask {
-
- override fun getPen():Boolean {
- return Painting.pen
- }
-
- override fun openPainting() {
- // 花费一秒钟的时间使用钥匙打开江山社稷图
- Thread.sleep(1000)
- println("哪吒打开了江山社稷图")
- Painting.pen = false
- }
- }
可以看到,我们在代码中通过 getPen() 方法获取江山社稷图中的 pen 字段,在 openPainting() 方法中,花费一秒钟的时间打开江山社稷图,然后将 pen 字段置为 false ,表示笔已被使用。太乙真人、敖丙和申公豹类的代码是类似的。
太乙真人:
- class TaiYiZhenRen : ITask {
-
- override fun getPen():Boolean {
- return Painting.pen
- }
-
- override fun openPainting() {
- Thread.sleep(1000)
- println("太乙真人打开了江山社稷图")
- Painting.pen = false
- }
- }
敖丙:
- class AoBing : ITask {
-
- override fun getPen():Boolean {
- return Painting.pen
- }
-
- override fun openPainting() {
- Thread.sleep(1000)
- println("敖丙打开了江山社稷图")
- Painting.pen = false
- }
- }
申公豹:
- class ShenGongBao : ITask {
-
- override fun getPen():Boolean {
- return Painting.pen
- }
-
- override fun openPainting() {
- Thread.sleep(1000)
- println("申公豹打开了江山社稷图")
- Painting.pen = false
- }
- }
然后我们新建一个 Fight 类,模拟四人的抢笔场景,程序流程如下:
- class Fight {
- @Test
- fun start() {
- val neZha = NeZha()
- val taiYiZhenRen = TaiYiZhenRen()
- val aoBing = AoBing()
- val shenGongBao = ShenGongBao()
- Thread {
- if (neZha.getPen()) {
- neZha.openPainting()
- } else {
- println("哪吒没有抢到笔。")
- }
- }.start()
- Thread {
- if (taiYiZhenRen.getPen()) {
- taiYiZhenRen.openPainting()
- } else {
- println("太乙真人没有抢到笔。")
- }
- }.start()
- Thread {
- if (aoBing.getPen()) {
- aoBing.openPainting()
- } else {
- println("敖丙没有抢到笔。")
- }
- }.start()
- Thread {
- if (shenGongBao.getPen()) {
- shenGongBao.openPainting()
- } else {
- println("申公豹没有抢到笔。")
- }
- }.start()
-
- Thread.sleep(2000)
- }
- }

大功告成!运行程序,控制台显示如下:
- 哪吒打开了江山社稷图
- 申公豹打开了江山社稷图
- 太乙真人打开了江山社稷图
- 敖丙打开了江山社稷图
Excuse me ? 明明每个人都在打开社稷图后将 pen 置为了 false ,为什么四个人都拿到了笔打开了江山社稷图?
这就是多线程编程中常出现的并发问题,当多个线程访问同一个对象时,会引发竞争状态,导致结果不正确。
要解决此问题,需要使用 synchronized 关键字。synchronize 直译为"同步化",在《Head First Java》一书中,对 synchronize 关键字是这样介绍的:
每个 Java 对象都有锁,每个锁只有一把钥匙,但大多数情况下对象都没有上锁。但如果对象有同步化的方法,则线程只能在取得钥匙的情况下进入线程。也就是说只能在没有其他线程已经进入的情况下才能进入。
本例中,synchronize 的作用是当哪吒、太乙真人、敖丙、申公豹中的任何一个人抢到笔后,将笔"锁住",直至使用完后才释放。将程序修改如下:
- class Fight {
- @Test
- fun start() {
- val neZha = NeZha()
- val taiYiZhenRen = TaiYiZhenRen()
- val aoBing = AoBing()
- val shenGongBao = ShenGongBao()
- Thread {
- synchronized(Painting.pen) {
- if (neZha.getPen()) {
- neZha.openPainting()
- } else {
- println("哪吒没有抢到笔。")
- }
- }
- }.start()
- Thread {
- synchronized(Painting.pen) {
- if (taiYiZhenRen.getPen()) {
- taiYiZhenRen.openPainting()
- } else {
- println("太乙真人没有抢到笔。")
- }
- }
- }.start()
- Thread {
- synchronized(Painting.pen) {
- if (aoBing.getPen()) {
- aoBing.openPainting()
- } else {
- println("敖丙没有抢到笔。")
- }
- }
- }.start()
- Thread {
- synchronized(Painting.pen) {
- if (shenGongBao.getPen()) {
- shenGongBao.openPainting()
- } else {
- println("申公豹没有抢到笔。")
- }
- }
- }.start()
-
- Thread.sleep(2000)
- }
- }

然后,运行程序,输出如下:
- 哪吒打开了江山社稷图
- 申公豹没有抢到笔。
- 敖丙没有抢到笔。
- 太乙真人没有抢到笔。
这样就实现了多线程的同步化,以上就是一个多线程编程的简单应用场景。
近年来随着中国程序员水平的整体提高,多线程问题也成为了大厂面试官的新宠。实际工作中遇到的多线程问题往往复杂得多,常见考察点有:
考点虽多,但个中原理其实并不复杂,细心的小伙伴可能已经发现了,力扣题库中最新上线了多线程编程题,帮助你把多线程问题抽丝剥茧、各个击破。在题库页面多线程编程题就可以看到:
力扣的多线程题库还在不断更新中,想要练手多线程题目的同学们赶紧先练习起来吧~
本文作者:Alpinist Wang
声明:本文归 “力扣” 版权所有,如需转载请联系。
文中部分图片来源于网络,为非商业用途使用,如有侵权联系删除。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。