写点什么

并发王者课 - 青铜 1:兵分三路 - 从创建线程开始

发布于: 2021 年 05 月 18 日
并发王者课-青铜1:兵分三路-从创建线程开始

从本文开始,我将基于王者中的段位和场景,从青铜黄金铂金砖石星耀王者,不同的段位对应不同的难易程度,由浅入深逐步介绍 JAVA 中的并发编程,并在每周二、四、六持续更新。


在文章的知识体系方面,主要以实践为主,并在实践中穿插理论知识的讲解,而本文将从最简单的线程创建开始。

一、一个游戏场景

在本局游戏中,将有 3 位玩家出场,他们分别是哪吒、苏烈和安其拉。根据玩家不同的角色定位,在王者峡谷中,他们会有不同的游戏路线:

  • 作为战士的哪吒将走上路的对抗路线;

  • 法师安其拉则去镇守中路;

  • 战坦苏烈则决定去下路。

二、代码实现

显而易见,你已经发现这几个玩家肯定不是单线程。接下来,我们将通过简单的多线程模拟出他们的路线。当然,真实的游戏引擎中绝不会是几个简单的线程,情况会复杂很多。


public static void main(String[] args) {        Thread neZhaPlayer = new Thread() {            public void run() {                System.out.println("我是哪吒,我去上路");            }        };        Thread anQiLaPlayer = new Thread() {            public void run() {                System.out.println("我是安其拉,我去中路");            }        };        Thread suLiePlayer = new Thread() {            public void run() {                System.out.println("我是苏烈,我去下路");            }        };        neZhaPlayer.start();        anQiLaPlayer.start();        suLiePlayer.start();}
复制代码


代码的运行结果:


我是哪吒,我去上路我是苏烈,我去下路我是安其拉,我去中路
Process finished with exit code 0
复制代码


以上,就是游戏中简单的代码片段。我们创建了 3 个线程表示 3 个玩家,并通过run()方法实现他们的路线动作,随后通过start()启动线程。它足够简单,然而这里有 3 个知识点需要你留意。


1. 创建线程


Thread neZhaPlayer = new Thread();
复制代码


2. 执行代码片段


public void run() {    System.out.println("我是哪吒,我去上路");}
复制代码


3. 启动线程


neZhaPlayer.start();
复制代码


对于我们来说,创建线程并不是我们的目标,我们的目标是运行我们期望的代码(比如玩家的游戏路线或某个动作),而线程只是我们实现这一目标的方式。因此,在编写多线程代码时,运行指定的代码片段无疑是极其重要的。在 Java 中,我们有 2 种方式来指定:


  • 继承Thread并重写run方法;

  • 实现Runnable接口并将其传递给Thread的构造器。

三、线程创建的两种方式

1. 继承 Thread 创建线程

在上面的示例代码中,我们所使用的正是这种方式,只不过是匿名实现,你也可以通过显示继承:


public class NeZhaPlayer extends Thread {    public void run() {        System.out.println("我是哪吒,我去上路");    }}
复制代码


此外,在 Java 以及更高的 JDK 版本中,你还可以通过 lambda 表达式简化代码:


Thread anQiLaPlayer = new Thread(() -> System.out.println("我是哪吒,我去上路"));
复制代码

2. 实现 Runnable 接口创建线程

创建线程的第 2 种方法是实现Runnable接口。我们创建了NeZhaRunnable类并实现Runnable接口中的run方法,如下面代码所示。


public class NeZhaRunnable implements Runnable {    public void run() {        System.out.println("我是哪吒,我去上路");    }}
Thread neZhaPlayer = new Thread(new NeZhaRunnable());neZhaPlayer.start();
复制代码


从效果上看,两种方式创建出来的线程效果是一样的。那么,我们应该怎么选择?

建议你使用 Runnable

对于这两种方法,孰优孰劣并没有明确的规定。但是,从面向对象设计的角度来说,推荐你用第二种方式:实现 Runnable 接口


这是因为,在面向对象设计中,有一条约定俗成的规则,组合优于继承(Prefer composition over inheritance),如果没有特别的目的需要重写父类方法,尽量不要使用继承。在 Java 中所有的类都只能是单继承,一旦继承 Thread 之后将不能继承其他类,严重影响类的扩展和灵活性。另外,实现 Runnable 接口也可以与后面的更高级的并发工具结合使用。


所以,相较于继承 Thread,实现 Runnable 接口可以降低代码之间的耦合,保持更好的灵活性。关于这一原则的更多描述,你可以参考《Effective Java》。


当然,如果你对 Thread 情有独钟,当我没说。此外,在 Java 中我们还可以通过ThreadFactory等工具类创建线程,不过本质上仍是对这两种方法的封装。

四、注意,别踩坑!

线程的启动固然简单,然而对于一些新手来说,在启动线程的时候,一不小心就会使用run()而不是start(),就像下面这样:


Thread neZhaPlayer = new Thread(new NeZhaRunnable());neZhaPlayer.run();
复制代码


如果你这么调用的话,你仍然可以看到你期望的输出,然而这正是陷进所在!这是因为,Runnable 中的run()方法并不是你所创建的线程调用的,而是调用你这个线程的线程调用的,也就是主线程。那为什么直接调用run()方法也能看到输出呢?这是因为 Thread 中的run()会直接调用 target 中的run():


public void run() {    if (target != null) {        target.run();    }}
复制代码


所以你看,如果你直接调用run()的话,并不会创建新的线程。关于这两个方法的执行细节,会在后面的线程状态中分析,这里你要记住的就是启动线程调用的是start(),而不是run()


以上就是文本的全部内容,恭喜又上了一颗星!

夫子的试炼

  • 用两种不同的方式,创建出两个线程,交差打印 1~100 之间的奇数和偶数,并断点调试。

关于作者

关注公众号【庸人技术笑谈】,获取及时文章更新。记录平凡人的技术故事,分享有品质(尽量)的技术文章,偶尔也聊聊生活和理想。不贩卖焦虑,不兜售课程。

发布于: 2021 年 05 月 18 日阅读数: 19
用户头像

微信公众号:【技术八点半】 2018.05.13 加入

关注公众号【技术八点半】,及时获取文章更新。传递有品质的技术文章,记录平凡人的成长故事,偶尔也聊聊生活和理想。早晨8:30推送作者品质原创,晚上20:30推送行业深度好文。

评论

发布
暂无评论
并发王者课-青铜1:兵分三路-从创建线程开始