第五章 多线程与并发编程
第一节 线程的基本概念
概述
随着计算机硬件性能的提升和多核处理器的普及,多线程编程成为现代软件开发中不可或缺的技术。多线程不仅可以提高程序的执行效率,还能提升资源利用率和用户体验。本节将系统介绍线程的基本概念,帮助考生理解线程的定义、特点、生命周期及其与进程的关系,为后续多线程编程技术奠定坚实基础。
学习目标:
- 理解线程的定义及其作用
- 掌握线程与进程的区别和联系
- 熟悉线程的生命周期及状态转换
- 了解线程的创建、启动和终止过程
- 通过实例加深对线程基本概念的理解
核心概念
线程(Thread)
线程是操作系统能够进行运算调度的最小单位,是程序执行的基本顺序单元。一个进程可以包含多个线程,这些线程共享进程的资源,但有独立的执行路径。
进程(Process)
进程是系统进行资源分配和调度的基本单位。每个进程拥有独立的内存空间、系统资源和至少一个线程。
并发(Concurrency)
在单核处理器上,多个线程交替执行,看似同时运行,称为并发;多核处理器上,线程真正同时执行,称为并行。
线程状态
线程在其生命周期中会经历多个状态,常见的有新建、就绪、运行、阻塞和终止。
线程调度
操作系统负责将CPU时间片分配给不同线程,实现线程的切换和执行。
原理分析
线程与进程的关系
线程是运行在进程内部的执行单元,多个线程共享进程的内存空间、文件描述符等资源,但线程有自己的程序计数器、栈和寄存器集合。
| 特性 | 进程 | 线程 |
|---|---|---|
| 资源拥有 | 独立拥有内存空间和系统资源 | 共享进程的资源 |
| 调度单位 | 操作系统调度的基本单位 | 操作系统调度的基本单位 |
| 开销 | 创建销毁开销大 | 创建销毁开销小 |
| 通信方式 | 进程间通信(IPC)复杂 | 线程间通信简单(共享内存) |
线程生命周期
线程生命周期由多个状态构成,状态转换受多种因素影响:
- 新建(New):线程对象被创建,尚未开始执行。
- 就绪(Runnable):线程准备好运行,等待CPU分配时间片。
- 运行(Running):线程获得CPU,开始执行任务。
- 阻塞(Blocked):线程因等待某些条件(如I/O、锁)而暂停执行。
- 终止(Terminated):线程执行完毕或被强制结束。
线程状态转换由线程调度器和同步机制控制。
线程调度原理
线程调度是操作系统分配CPU时间给线程的过程,主要调度方式有:
- 抢占式调度:操作系统强制中断当前线程,切换到其他线程。
- 非抢占式调度:线程主动放弃CPU,调度器切换线程。
调度策略包括先来先服务(FCFS)、时间片轮转(Round Robin)、优先级调度等。
详细内容
1. 线程的定义和作用
线程,俗称“轻量级进程”,是程序执行的最小单位。多线程能够让程序在同一进程内同时执行多条指令流,极大提升程序的响应性和资源利用率。例如,网页浏览器可以同时处理页面渲染、用户交互和网络请求。
线程的主要作用包括:
- 提高程序并发性,多任务并行处理
- 优化资源利用,共享内存减少开销
- 提升程序响应速度,避免界面卡顿
2. 线程与进程的区别
进程和线程的最大区别在于资源拥有和调度单位。进程拥有独立的资源空间,线程共享进程资源。创建进程的开销比线程大很多。
在多线程编程中,多个线程共享进程的地址空间,使得线程间通信更加高效,但也带来同步和安全问题。
3. 线程的生命周期详解
线程的生命周期是理解多线程控制的关键。线程从创建到结束经历五个状态:
- 新建(New):线程对象创建,但尚未调用start()
- 就绪(Runnable):调用start()后线程进入就绪队列,等待CPU时间
- 运行(Running):CPU调度运行线程
- 阻塞(Blocked):等待资源、I/O或同步锁
- 终止(Terminated):线程执行结束或异常终止
线程状态转换示意:
- 新建 -> 就绪(调用start)
- 就绪 -> 运行(CPU调度)
- 运行 -> 阻塞(等待I/O或锁)
- 阻塞 -> 就绪(资源可用)
- 运行 -> 终止(执行完毕)
4. 线程的创建与启动
多线程程序首先需要创建线程对象。常用方式有:
- 继承Thread类:重写run()方法,通过start()启动线程
- 实现Runnable接口:实现run()方法,传入Thread构造函数启动
调用start()方法会启动线程,系统调用底层线程调度机制。
5. 线程的终止方式
线程终止主要有两种:
- 正常结束:run()方法执行完毕
- 强制终止:调用interrupt()请求中断,线程捕获中断信号后结束
强制终止需谨慎,防止资源未释放和数据不一致。
实例分析
案例一:继承Thread类创建线程
背景:编写一个线程类打印数字1-5。
代码示例:
class MyThread extends Thread {
public void run() {
for(int i=1; i<=5; i++) {
System.out.println(getName() + ": " + i);
try {
Thread.sleep(500); // 模拟耗时任务
} catch (InterruptedException e) {
System.out.println(getName() + " 被中断");
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
分析:
- 通过继承Thread类创建线程
- 两个线程交替执行,体现并发效果
- 使用sleep模拟阻塞状态
结论:线程启动后进入就绪,获取CPU时间片后运行,交替打印输出。
案例二:实现Runnable接口创建线程
背景:实现一个线程类打印字母A-E。
代码示例:
class MyRunnable implements Runnable {
public void run() {
for(char c='A'; c<='E'; c++) {
System.out.println(Thread.currentThread().getName() + ": " + c);
try {
Thread.sleep(400);
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
}
}
}
public class RunnableTest {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
Thread t2 = new Thread(new MyRunnable());
t1.start();
t2.start();
}
}
分析:
- 通过实现Runnable接口创建线程,增强了类的灵活性
- 线程共享同一Runnable对象,体现资源共享
结论:Runnable方式更灵活,有利于多线程代码的解耦。
案例三:线程生命周期状态演示
背景:演示线程从新建到终止的状态变化。
代码示例:
public class LifeCycleDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("线程运行中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
System.out.println("线程结束");
});
System.out.println("状态:" + t.getState()); // NEW
t.start();
System.out.println("状态:" + t.getState()); // RUNNABLE
Thread.sleep(500);
System.out.println("状态:" + t.getState()); // TIMED_WAITING
t.join();
System.out.println("状态:" + t.getState()); // TERMINATED
}
}
分析:
- 通过getState()方法查看线程状态
- 观察新建、运行、阻塞(睡眠)、终止状态
结论:线程状态随着执行过程动态变化,理解有助于调试和设计多线程程序。
常见误区
线程直接调用run()方法代替start()
- 错误做法会导致线程在当前线程中顺序执行,无法实现多线程并发。
- 正确做法是调用start()启动线程,使线程进入就绪状态。
忽视线程安全与资源共享问题
- 多线程共享资源时未加同步,导致数据竞争和不一致。
- 应使用同步机制(如synchronized)保护共享资源。
滥用强制终止线程
- 使用stop()等方法强制终止,会造成资源未释放。
- 应使用中断机制或标志变量安全结束线程。
线程生命周期状态理解混淆
- 将线程状态等同于线程是否执行,忽略阻塞和就绪状态。
- 应准确理解状态间转换和线程调度的关系。
忽略线程调度不确定性
- 误以为线程执行顺序固定,忽略调度器分配的随机性。
- 应设计无序执行情况下也能正确工作的程序。
应用场景
图形界面程序
- 主线程负责界面响应,子线程处理耗时操作,避免界面卡顿。
网络服务器
- 通过多线程同时处理多个客户端请求,提高吞吐量。
实时数据处理
- 多线程并发处理数据流,提升处理效率。
并行计算
- 多核心CPU上划分任务线程,实现计算加速。
异步任务执行
- 后台线程执行定时任务或消息监听,主线程继续业务逻辑。
知识拓展
- 线程池技术:管理线程复用,减少创建销毁开销,提高效率。
- 线程同步机制:锁(synchronized、ReentrantLock)、信号量(Semaphore)、条件变量(Condition)等。
- 线程通信:wait()、notify()、notifyAll()实现线程间协作。
- 线程安全设计:不可变对象、原子操作、线程安全集合。
- 并发工具类:Java.util.concurrent包提供丰富并发工具。
总结回顾
本节内容系统介绍了线程的基本概念和原理,重点包括:
- 线程定义和作用,线程是程序执行的最小单位
- 线程与进程的区别,线程共享进程资源但拥有独立执行路径
- 线程生命周期及状态转换,理解新建、就绪、运行、阻塞和终止状态
- 线程的创建、启动和终止方法,掌握Thread类和Runnable接口的使用
- 线程调度原理,理解抢占式和非抢占式调度策略
- 通过实例演示线程创建和生命周期状态,强化理解
- 常见误区警示,避免线程编程中的常见错误
- 多线程应用场景介绍,联系实际提升学习兴趣
深入掌握本节内容,是理解多线程与并发编程的基础,有助于后续学习线程同步、线程安全和并发设计模式。
祝你学习进步,顺利通过全国计算机等级考试四级面向对象程序设计科目!