Java并发编程(多线程)

塞尔达传说荒野之息-1.2-Synchnorized与互斥

本文为《JAVA并发编程与塞尔达传说荒野之息》系列文章。转载请注明来自:www.kakashi01.com。如果您觉得本站帮助到你,可以请卡卡西喝一杯咖啡,让卡卡西更有精神写出更多的好文章。如果觉得哪里写的不足的,也欢迎在评论指出!谢谢您来看我的文章!
本文所有代码可以在我的Git仓库上找到

当有超过1个线程对某个共享变量进行读写时,如果不做任何的处理。那么将可能出现处理结果和单线程处理不一致的情况。这时候,为了保证线程安全,最简单的方法是加锁。而synchnorized是一种实现加锁的方式。(后面会介绍别的方式实现线程安全)。接下来结合例子看看,会有什么“不一致的情况”。

例子1-1 没有任何线程安全保证的战斗对象

假设我们塞尔达游戏可以参与战斗的角色都需要从BaseFighter继承,如代码清单1.1所示。

说明 : 实际真实项目中的结构会比这复杂,而且可能使用的并不是继承结构,而是组合,比如ECS。如果大家有兴趣,可以留言。我后面写完多线程这一系列还会继续写ECS或怎样更好地在真实项目中组织这些类使得提高代码重用和拓展,同时保证代码更清晰优雅。

上述代码可以切换到git分支ch1_1_1_unsafe。

当一个对象被攻击时,我们调用它的beAttacked并传入伤害值。这代码在单线程时没问题。

多线程时,代码的执行顺序可能如下图所示。

非线程安全的beAttacked方法

图1.1 非线程安全的beAttacked方法

图1.1很明显说明了,当两个线程分别在图示的时间轴上执行对应指令的时候。得到的结果将不是我们期望的hp=0。而是hp=3。而且大家也可以自己想一下,如果图示变成Thread#1每个指令都在Thread#0之前执行呢?这是完全有可能的!因为线程的调度是不明确的(就算你设置了线程优先级,假如Thread#1比Thread#0的线程优先级高。也可能出现某个逻辑执行时,Thread#0的指令先执行于Thread#1)。

说明 : 实际线程执行过程中,指令顺序可能比上图还要混乱。并不一定总是一个线程一条指令轮流执行。可能线程0连续先执行了前2条指令,然后线程1连续执行完了所有指令。接着线程0又执行了剩下的2条指令。总之,上图演示的只是一种可能存在的执行顺序。而这种顺序,是不可预知的。

现在你应该已经明白了上述代码存在的问题。这个问题其实通过我们本节要讲的synchnorized可以很方便的到解决。修改后代码如代码清单1.2所示。

上述代码可以切换到git分支ch1_1_2_safe。

修改后的代码执行过程如图1-2所示。

线程安全的beAttacked方法

图1-2 线程安全的beAttacked方法

可以看到:被synchnorized修饰的beAttacked方法变成了原子操作。因此,线程0和线程1不会出现图1-1的交错执行beAttacked方法内指令的情况。此时的代码已经是线程安全的!

All right, all right, all right.

这里即将引出我们上面说的那三个烦人的概念之一:互斥访问。从图1-2中我们可以看到。同一时刻。要么线程0先执行beAttacked方法,要么线程1先执行beAttacked方法。不会出现线程0和线程1同时执行的情况。这正是synchnorized可以使得代码块具备互斥访问的特性。那么,我想再和你探讨一下,synchnorized使什么东西进行互斥,怎样才能互斥,比如我们如果加了另外一个类,还能互斥吗?

要知道上述问题,首先得了解一下JVM关于synchnorized的规则:

至此,我们已经讲完了synchnorized的互斥访问相关内容。

About 旗木卡卡西

爱好编程,喜欢游戏。 完整SLG,RPG,ARPG、SIM网络游戏服务器主程序经验。 曾创业2年,担任CTO兼CEO。
View all posts by 旗木卡卡西 →

发表评论

电子邮件地址不会被公开。 必填项已用*标注

5 × = 35