一、概述
记录时间 [20240807] 立秋 & 128 创作纪念日
本文是关于 Java 多线程学习的导航篇,总览了有关多线程学习的几个模块。简单介绍了一些与线程相关的基本概念,便于后续展开多线程的学习。
前置知识:Java 基础篇;Java 面向对象
Java 多线程学习主要模块包括:线程简介;线程实现;线程状态;线程同步;线程通信问题;拓展高级主题。
关于创作纪念日
里程碑专区
是生命中平常的午后,也是留给未来的礼物。
一切尽在不言中,那就写文章纪念一下吧。
愿所见者心想事成。
二、多任务 / 多线程
多任务(Multitasking)是指操作系统能够同时处理多个任务的能力。
- 在计算机科学中,多任务可以指不同的层面;
- 但在日常使用中,它通常指的是操作系统能够使用户感觉多个应用程序同时运行的能力。
多线程(Multithreading)是指在一个进程中同时运行多个线程的能力。
1. 多任务的类型
单处理器多任务
- 在单个处理器上模拟多个任务同时运行。
- 实际上,处理器在各个任务间快速切换,使得每个任务都获得一定的时间片来执行。
- 用户感觉好像多个应用程序都在同时运行。
多处理器多任务
- 当系统有多个处理器时,可以真正实现多个任务同时运行在不同的处理器上。
- 这种方式下,真正的并行处理成为可能,从而提高了系统的整体性能。
2. 多任务的示例
假设一个用户正在使用电脑编辑文档,同时下载文件,并且还有一个音乐播放器在后台播放音乐。
在这种情况下,操作系统会为每个应用程序分配一定的处理器时间,并确保它们都能正常运行。
虽然实际上每次只有一个应用程序直接使用处理器,但由于时间分片技术,用户会感觉到所有应用程序都在同时运行。
在现代操作系统中,多任务几乎是标配,无论是桌面操作系统还是移动操作系统,都支持多任务处理。这使得用户能够高效地执行多个任务,提高了生产力和娱乐体验。
3. 线程与多线程
线程
- 线程是进程中的一个执行单元,是进程内部的一个轻量级进程。
- 线程是操作系统能够进行运算调度的最小单位。
- 线程共享所属进程的资源,如内存空间、文件句柄等。
多线程
-
多线程允许在一个进程中同时执行多个线程。
-
这些线程可以并行执行,提高程序的执行效率和响应速度。
三、调用方式
如图,普通方法调用和多线程是两种不同的执行模式。
- 在普通方法调用中,程序按照顺序执行;
- 在多线程环境中,程序可以在同一时间内并发执行多个任务。
下面是这两种执行模式的详细对比:
1. 普通方法调用
- 顺序执行
- 普通方法调用意味着程序中的方法按顺序执行,一个方法调用完成后才会执行下一个方法。
- 这种执行模式适用于不需要并发执行的任务。
- 单线程执行
- 在单线程环境中,整个程序在同一时间只能执行一个任务。
- 即使程序中有多个方法,它们也是依次执行的。
- 资源独占
- 当一个方法正在执行时,其他方法无法访问正在使用的资源,直到当前方法执行完毕。
- 这有助于简化资源管理和避免并发问题。
- 调用栈
- 每当一个方法被调用时,都会在调用栈上创建一个新的栈帧。
- 当方法返回时,对应的栈帧被弹出,控制权回到调用者。
2. 多线程
- 并发执行
- 多线程允许一个程序在同一时间内执行多个任务。
- 每个任务(线程)可以独立于其他线程执行。
- 资源共享
- 线程共享同一进程的资源,如内存、文件句柄等。
- 因此,线程间可以很容易地共享数据和资源。
- 线程调度
- 操作系统负责调度线程,决定哪个线程在给定时刻获得 CPU 时间。
- 线程调度可以基于时间片轮转、优先级等策略。
- 线程同步
- 为了防止多个线程同时访问同一资源导致的问题,需要使用同步机制。
- 常见的同步机制包括锁(Locks)、信号量(Semaphores)、条件变量(Condition Variables)等。
- 线程间通信 (IPC)
- 线程间通信允许线程间共享数据和同步状态。
- 通常使用共享内存区域、消息队列等方式实现。
3. 示例对比
对于普通方法
假设有一个简单的程序,它按顺序调用两个方法 methodA
和 methodB
:
public class SequentialExample {public static void methodA() {System.out.println("Method A started");try {Thread.sleep(1000); // 模拟耗时操作} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Method A finished");}public static void methodB() {System.out.println("Method B started");try {Thread.sleep(1000); // 模拟耗时操作} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Method B finished");}// 主程序public static void main(String[] args) {methodA();methodB();}
}
从主程序中可以看出,methodA
方法先于 methodB
方法被调用。
得到结果为:
Method A started
Method A finished
Method B started
Method B finished
对于多线程
同样的方法 methodA
和 methodB
,如果采用多线程的方式运行。
- 先创建线程
threadA
调用methodA
;再创建线程threadB
调用methodB
。 - 两个线程同时运行,输出结果不确定且不唯一。
public class MultithreadingExample {public static void methodA() {System.out.println("Method A started");try {Thread.sleep(1000); // 模拟耗时操作} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Method A finished");}public static void methodB() {System.out.println("Method B started");try {Thread.sleep(1000); // 模拟耗时操作} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Method B finished");}public static void main(String[] args) {Thread threadA = new Thread(MultithreadingExample::methodA);Thread threadB = new Thread(MultithreadingExample::methodB);threadA.start();threadB.start();}
}
四、程序 / 进程 / 线程
程序、进程和线程是计算机科学中的三个基本概念,它们之间有着紧密的关系,构成了现代操作系统中程序执行的基础结构。
程序是静态的指令集合,进程是程序的一次执行实例,而线程是进程中的执行单元。
1. 程序 (Program)
- 定义
- 程序是一组有序的指令集合,用来描述计算机执行特定任务的步骤。
- 程序通常是指源代码经过编译后生成的可执行文件或脚本。
- 特点
- 程序本身不能直接运行,需要加载到内存中并由操作系统启动。
- 一个程序可以包含多个文件和资源,如库文件、配置文件等。
- 示例
- 编译后的
.exe
文件(Windows)或.sh
脚本(Unix/Linux)。
- 编译后的
2. 进程 (Process)
- 定义
- 进程是一个程序的一次执行实例。
- 进程是操作系统进行资源分配和调度的基本单位。
- 特点
- 每个进程都有自己的独立内存空间、文件描述符表和环境变量。
- 进程之间通过进程间通信 (IPC) 进行通信和同步。
- 状态
- 进程在其生命周期中有多种状态,包括新建、就绪、运行、阻塞和终止。
- 示例
- 打开一个浏览器,启动一个文本编辑器,运行一个游戏等。
3. 线程 (Thread)
- 定义
- 线程是进程中的一个执行单元,是操作系统能够进行运算调度的最小单位。
- 线程是 CPU 调度和执行的单位。
- 线程共享所属进程的资源,如内存空间、文件句柄等。
- 特点
- 线程之间的通信简单,因为它们共享同一进程的地址空间。
- 创建和销毁线程的成本低于进程。
- 线程间切换的开销较小。
- 状态
- 线程也有多种状态,包括新建、就绪、运行、阻塞和终止。
- 示例
- 在一个浏览器进程中,可能有多个线程,一个用于处理用户界面,另一个用于处理网络请求。
4. 关系和区别
程序与进程
- 程序是静态的,而进程是动态的。
- 一个程序可以对应多个进程,每个进程都是程序的一个执行实例。
进程与线程
- 进程是资源分配的基本单位,而线程是调度的基本单位。
- 同一进程中的线程共享进程的资源,而不同进程之间的资源是隔离的。
- 创建和销毁进程的开销大于线程。
- 一个进程可以有多个线程,如观看视频时,同时听声音,看图像,看弹幕,等等。
五、核心概念
- 线程是独立的执行路径。
- 在程序运行时,即使没有显式创建线程,后台也会存在多个线程,例如主线程和垃圾回收 (
gc
) 线程。 main()
为主线程,是系统的入口,用于执行整个程序。- 在一个进程中,如果创建了多个线程,那么这些线程的运行是由调度器负责安排的。调度器与操作系统紧密相关,并且线程的执行顺序通常是不可人为干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
- 线程会带来额外的开销,如 CPU 调度时间,以及并发控制开销。
- 每个线程都在自己的工作内存中进行交互,如果内存控制不当,可能会导致数据不一致的问题。
六、参考资料
狂神说 Java 多线程:https://www.bilibili.com/video/BV1V4411p7EF
TIOBE 编程语言走势: https://www.tiobe.com/tiobe-index/
Typora 官网:https://www.typoraio.cn/
Oracle 官网:https://www.oracle.com/
Notepad++ 下载地址:https://notepad-plus.en.softonic.com/
IDEA 官网:https://www.jetbrains.com.cn/idea/
Java 开发手册:https://developer.aliyun.com/ebook/394
Java 8 帮助文档:https://docs.oracle.com/javase/8/docs/api/