第1章 Java多线程基础
1.1 初识线程
首先介绍线程的概念。我们可以把线程看作有活力、有生命力的,它是让看似安静的一段段代码活动起来的一种形式。在学习本章前,读者如果已经进行过Java简易入门基础的学习,那么理解线程会是一件非常容易的事情。理解了线程的概念后,会由单线程向多线程进军。
1.1.1 线程是什么?
如今,智能手机与我们的生活密不可分。智能手机之所以这样吸引我们,与其能提供丰富多彩的应用程序有密切的关系。在使用这些应用程序,如查阅资讯、单击图标、拉取列表、播放视频和音乐等时,会给人们以视觉和听觉上的享受。同时,智能手机能及时地对我们的操作进行反馈,非常友好。这里的每一次反馈,都可能是有一个线程在专心致志地为我们服务。所以,看似陌生的线程实际上已经默默服务人们多时。
每一个刚接触程序设计的初级人员,在学习了某种编程语言后都会开始尝试编写一些基本的短小的代码段。在Java中,这些短小的代码段一般会被放入一个class,然后保存到一个扩展名
为.java的文件中;之后通过命令行或集成开发环境工具的编译,生成.class文件并让这个.class文件运行起来,得到我们想要的结果。
例如,有一个简单的模仿游戏打开宝箱得到礼品的程序代码,参考如下:
1
public class OpenBox {
2
public static void main(String[] args) {
3
//设置宝箱中可能包含的水果
4
List fruits = new ArrayList();
5
fruits.add("green apple");
6
fruits.add("red apple");
7
fruits.add("banana");
8
fruits.add("cherry");
9
fruits.add("watermelon");
10
//获取随机的下标,用于生成随机的水果,范围为0至最大水果链表的下标
11
Random randomUtil = new Random();
12
int randomInt = randomUtil.nextInt(fruits.size());
13
System.out.println("打开宝箱,得到了" +
14
fruits.get(randomInt) + "!");
15
}
16
}
我们将其以文件形式保存到系统中,如图1.1所示。
图片包含 屏幕截图
描述已自动生成
图1.1 已经保存到系统的Java的类文件
这样,该文件中就包含了我们想要运行的一小段程序。当使用Java的命令或单击集成开发环境的run按钮时,程序就会运行起来,并且按照编写好的逻辑反馈相关信息。OpenBox的运行结果如图1.2所示。
图1.2 OpenBox的运行结果
以上这些看似简单的操作过程,可以让我们更好地理解以下几个概念:程序、进程、线程。
程序可以理解为个人的思维整合所设计和编写的一种有特殊意义的文本作品,其包含一些有特殊含义的词汇、符号、数据及短语缩写,俗称代码。程序本身是一种静态的文本作品,但通过特殊的环境,能让其产生动态的逻辑和具备运算能力。
上文中的OpenBox.java文件中的文本内容就是程序。
进程则是对某程序的运行过程。一般地,一份程序的一次运行能产生一个进程,进程是一个动态的概念。进程的运行是需要用到程序的内容的,更确切地说,进程的运行离不开程序,离不开程序中有特殊含义的文本。实际上,进程运行中有专门存放这些文本的区域,该区域称为代码文本区域。程序与进程是一对多的关系,即一个程序可以同时运行一个或多个进程。单击集成开发环境的run按钮时,OpenBox.java对应的一个进程就立刻产生了。
理解好程序和进程的关系,就可以对线程加以描述和解释。线程是比进程更细小的一级划分,线程可以利用进程所拥有的资源,并且能独立完成一项任务,如计算、输出显示信息等。在引入线程的操作系统中,通常是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。进程与线程也是一对多的关系,即一个进程中至少有一个线程与之对应。如果一个进程中有多个线程同时存在,那么就是多线程的进程。上面的OpenBox.java程序运行时,其在产生一个进程的同时,也产生了一个单线程与之对应。也就是说,当运行OpenBox.java程序时,该行为所产生的进程是一个单线程进程。
程序、进程、线程的关系如图1.3所示。
图1.3 程序、进程、线程的关系
知识拓展
近年来,随着大数据的兴起,对于大数据的处理要求比传统的普通数据处理要求有了更高的标准,Java在大数据的处理方面也在不断地优化,特别是在开源社区中,许多开发贡献者提供了许多大数据处理相关的组件和中间件。其中一个称为quasar的组件实现了Java的纤程。纤程是比线程更小的一级划分,它所占用的系统资源更少,可以理解为更轻量级的一种特殊线程。一般地,从占用系统资源的大小方面来说,可以这样排序:进程 > 线程>纤程。本小节不展开对纤程的介绍,有兴趣的读者可以通过quasar的开源地址(https://github.com/puniverse/quasar)了解相关内容。
1.1.2 单线程与多线程
在1.1.1小节的讲解中,我们知道了一个进程至少包含一个线程。如果该进程只有一个线程,则将其称为单线程的进程。对于简单的应用程序,的确只要单线程就可以了;但对于一些复杂的应用程序,如与人的交互和反馈比较多时,就需要多线程来完成这些任务。一些大型的软件和游戏等应用程序,一般都包含了多线程来丰富整个系统的功能,以获得强大的处理性能,或者通过多状态的即时反馈来增加趣味性。
例如,图1.4所示的经典任天堂FC游戏,虽然是简单的游戏,但却给许多青少年带来了快乐。
图1.4 经典任天堂FC游戏
这些游戏中都加入了多线程,玩家可以获得更多的快乐体验和反馈。多线程能让一个游戏充满活力,让游戏中的物体活动起来。下面我们来看看“超级马里奥”系列某款游戏的一个画面中的一些多线程,如图1.5所示。
图1.5 “超级马里奥”系列某款游戏中的线程
图1.5中,玩家手柄控制的主角马里奥是一个独立的线程,能吃的、会走动的蘑菇是一个独立的线程,而行走中的敌人香菇君也是一个独立的线程,另外背景音乐也是一个线程。当然,这幅游戏画面中还有许多其他的线程在工作,这些众多的线程共同组成了这一生动活泼的经典游戏。读者可以思考这款游戏中还有哪些线程在默默地为游戏玩家提供服务。
这样的一个游戏实例,实际上也是一个进程。我们可以看到,一个进程可以包含一个或多个线程。单线程的进程虽然也存在,但实际上我们更多的会使用多线程的进程。
1.1.3 多线程的优势
多线程的优势在前面的小节中已经有粗略介绍,本小节将再次从系统的外在表现和内在表现两方面来讲解多线程的优势,如图1.6所示。
图片包含 地图, 文字
描述已自动生成
图1.6 多线程的优势
(1)由于有多个线程分工合作,所以有时候用户使用一个线程提供的功能时,另一个线程已经准备着为用户提供下一步的功能,能节省用户的等待时间。
(2)如果能合理地结合硬件资源来设计好多线程的并发,让系统处于合理的多线程调度模式,就能提高系统吞吐率,让系统具备高效的处理能力。
从系统的外在表现来看,多线程能快速反馈用户的多种操作。如果只有单一的线程,那么其处理简单的逻辑可以做到又快又好,但有时,某些需求可能需要处理大量的数据,这时,如果还是分派给单一的线程来完成,就可能需要花费比较长的时间,这样对于该软件或应用程序的使用者来说,体验感会大打折扣。
我们可以把一个线程处理数据的逻辑比喻成将城区南岸的车辆通过桥梁开至北岸的过程,每一辆车都是需要处理的一个小数据,如图1.7所示。
图1.7 普通流量情况下的车辆过桥
当车辆少时,车的通行的确非常畅顺,一座桥梁就足以应对。但当车辆非常多时,如果只有一座桥梁,可能就不能满足快速通行的需求,如图1.8所示。
图1.8 拥挤的大车流过桥
看到这种情形,许多读者可能会想到,如果南北两岸足够广阔,那么只需要多建几座桥梁就可以让大量的车辆分流,从而达到快速通行的目的。当建了几座桥梁后,这些拥堵的车辆就可以就近选择合适的桥梁分流通行,如图1.9所示。
图1.9 多座桥梁的情况下,多通道解决大车流过桥
其实,我们在进行数据处理时,如果发现一个线程处理能力不足而不能快速响应时,就可以通过类似“多建几座桥梁”的方式来帮助我们处理数据,即可以引入多线程来处理这样的大数据。
同时,使用多线程的软件或应用程序一般能在同一时间给予我们更多感知上面的满足。例如,我们在使用虚拟线程娱乐设备时,除了眼睛能观察到的动画外,一般会伴有当前情景下的背景音乐,甚至有些输入设备如震动手柄等会按情景震动起来,让用户沉浸式地进入一个虚拟场景。
从系统的内在表现来看,多线程能更有效地利用系统资源,特别是CPU的计算和磁盘的存储I/O资源等。另外,使用多线程能让系统更好地分工,可以把一个大任务拆分成多个关联的小任务。例如,一个大数据文件集合的内容修改功能,拆分成读文本内容、修改文本内容、保存文本内容三个线程来独立处理,只要控制好先后顺序,就能达到多线程并发处理的目的。
展开