第五章 多线程与并发编程
第二节 线程的创建与管理
概述
多线程技术是现代计算机程序设计中提高程序效率和响应能力的重要手段。线程作为操作系统调度的基本单位,能够实现程序的并发执行和资源共享。本节主要介绍线程的基本概念、创建方法及管理技巧,帮助考生系统掌握线程的生命周期、状态转换以及线程的启动、控制与销毁方法。通过本节学习,考生将能够熟练掌握多线程程序设计的基础,为后续复杂的并发编程打下坚实基础。
核心概念
线程(Thread):线程是进程中的一个执行流,是CPU调度和分派的基本单位。一个进程可以包含多个线程,共享进程的资源。
进程(Process):操作系统分配资源的基本单位,每个进程拥有独立的地址空间。
线程生命周期:线程从创建、就绪、运行、阻塞到终止的状态变化过程。
线程调度:操作系统根据一定的策略决定哪个线程获得CPU执行权。
多线程并发:多个线程交替执行,达到同时处理多个任务的效果。
线程安全:在多线程环境下,保证共享数据的一致性和正确性。
原理分析
线程的工作原理
线程是轻量级的进程,多个线程共享同一进程的内存空间和资源。线程通过CPU时间片轮转或优先级调度实现并发执行。线程的创建和管理依赖于操作系统提供的线程库或语言的线程API。
线程的生命周期包括五个主要状态:
- 新建(New):线程对象被创建,但还未启动。
- 就绪(Runnable):线程已经准备好运行,但等待CPU分配时间。
- 运行(Running):线程获得CPU时间片,执行任务。
- 阻塞(Blocked/Waiting):线程因等待资源或条件暂时停止运行。
- 终止(Terminated):线程执行结束。
线程状态的转换受到线程控制方法和操作系统调度的影响。
线程的创建方式
常见的线程创建方式包括继承Thread类和实现Runnable接口。两者的区别在于Java语言层面,前者是单继承且直接拥有线程方法,后者是灵活的接口实现,适合多继承设计。
线程调度与管理
线程调度涉及线程优先级和调度策略。线程优先级决定线程获取CPU的概率,但不是绝对保障。线程管理还包括线程的暂停、恢复、终止等操作,合理管理线程可以避免资源浪费和程序死锁。
详细内容
1. 线程的创建方法
多线程程序设计的第一步是创建线程。常见方法有:
继承Thread类
- 创建一个子类继承Thread类
- 重写run()方法,定义线程执行的任务
- 创建子类实例,调用start()启动线程
class MyThread extends Thread { public void run() { System.out.println("线程执行任务"); } } public class Test { public static void main(String[] args) { MyThread t = new MyThread(); t.start(); } }实现Runnable接口
- 实现Runnable接口,重写run()方法
- 使用Thread类构造方法传入Runnable对象
- 调用start()启动线程
class MyRunnable implements Runnable { public void run() { System.out.println("线程运行中"); } } public class Test { public static void main(String[] args) { Thread t = new Thread(new MyRunnable()); t.start(); } }匿名内部类和Lambda表达式(Java 8及以后)
- 通过匿名内部类简化代码
- 使用Lambda表达式更简洁
Thread t = new Thread(() -> { System.out.println("线程执行Lambda任务"); }); t.start();
注意:调用start()方法才会启动新线程并执行run()方法,直接调用run()方法只是普通方法调用,不会新建线程。
2. 线程生命周期管理
线程的生命周期管理是多线程编程的核心。主要涉及:
- 启动线程:调用
start()方法后,线程进入就绪状态等待CPU调度。 - 线程休眠:调用
Thread.sleep(milliseconds)使线程暂停指定时间,进入阻塞状态。 - 线程等待与通知:通过
wait()、notify()、notifyAll()方法实现线程间通信与协作。 - 线程中断:调用
interrupt()请求线程中断,线程可通过isInterrupted()检测中断状态。 - 线程终止:线程运行完
run()方法自动终止,或通过标志位控制线程安全退出。
合理管理线程生命周期,避免线程死锁、资源竞争、线程泄漏等问题。
3. 线程状态详解
线程状态转换图如下(文字描述):
- 新建 -> 调用start() -> 就绪
- 就绪 -> CPU调度 -> 运行
- 运行 -> 调用sleep()或等待资源 -> 阻塞
- 阻塞 -> 资源可用 -> 就绪
- 运行 -> run()结束 -> 终止
程序员应熟悉这些状态及其转换,合理设计线程控制逻辑。
4. 线程优先级与调度
Java线程优先级范围为1(最低)到10(最高),默认优先级为5。优先级高的线程优先获得CPU时间,但不保证绝对先执行。线程调度有:
- 抢占式调度:高优先级线程抢占低优先级线程CPU
- 时间片轮转:线程轮流分配CPU时间片
合理设置线程优先级可优化程序性能,但应避免因优先级反转导致资源饥饿。
5. 线程安全与同步基础
线程共享数据时,可能会出现数据竞争和不一致问题。使用同步机制(如synchronized关键字)保护临界区,保证线程安全。
实例分析
案例一:创建并启动多个线程打印序列
背景:需要同时执行三个线程,每个线程打印1到5的数字。
分析:
- 每个线程独立打印数字,互不干扰
- 通过继承Thread类创建线程
代码示例:
class NumberThread extends Thread {
private String threadName;
public NumberThread(String name) {
this.threadName = name;
}
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + "打印: " + i);
try {
Thread.sleep(100); // 模拟延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class MultiThreadDemo {
public static void main(String[] args) {
NumberThread t1 = new NumberThread("线程1");
NumberThread t2 = new NumberThread("线程2");
NumberThread t3 = new NumberThread("线程3");
t1.start();
t2.start();
t3.start();
}
}
结论:通过继承Thread类创建线程,调用start()方法启动,实现了三个线程并发打印。
案例二:实现Runnable接口的线程
背景:改进案例一,使用Runnable接口实现多线程,体现接口的灵活性。
分析:
- 实现Runnable接口定义任务
- 创建Thread对象传入Runnable实例
- 启动线程
代码示例:
class PrintTask implements Runnable {
private String name;
public PrintTask(String name) {
this.name = name;
}
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(name + "打印: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
Thread t1 = new Thread(new PrintTask("线程A"));
Thread t2 = new Thread(new PrintTask("线程B"));
t1.start();
t2.start();
}
}
结论:实现Runnable接口可以灵活复用任务类,同时避免了单继承限制。
案例三:线程优先级影响演示
背景:演示不同线程优先级对执行顺序的影响。
分析:
- 创建两个线程,分别设置不同优先级
- 观察CPU调度执行情况
代码示例:
class PriorityThread extends Thread {
public PriorityThread(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + "正在运行");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class PriorityDemo {
public static void main(String[] args) {
PriorityThread t1 = new PriorityThread("高优先级线程");
PriorityThread t2 = new PriorityThread("低优先级线程");
t1.setPriority(Thread.MAX_PRIORITY); // 10
t2.setPriority(Thread.MIN_PRIORITY); // 1
t1.start();
t2.start();
}
}
结论:高优先级线程倾向于先执行,但具体执行顺序仍由操作系统调度决定,不能完全依赖优先级。
常见误区与注意事项
错误调用run()方法
- 误区:直接调用线程对象的run()方法,线程并未真正启动。
- 正确做法:调用start()方法,启动新线程执行run()。
忽视线程安全
- 误区:多个线程访问共享数据无同步,导致数据不一致。
- 正确做法:使用同步机制(synchronized、锁等)保证线程安全。
线程未及时终止
- 误区:线程执行完毕后未正确结束,造成资源浪费。
- 正确做法:通过合理设计线程任务和中断机制,确保线程安全结束。
优先级误用
- 误区:认为设置高优先级线程一定先执行。
- 正确做法:合理设置优先级,理解调度机制,不依赖优先级保证执行顺序。
忽略异常处理
- 误区:线程中的异常未捕获,导致线程意外终止。
- 正确做法:在线程任务中添加异常捕获,保证线程稳定运行。
应用场景
- 图形界面程序:主线程负责界面响应,子线程处理耗时任务,避免界面卡顿。
- 服务器端多连接处理:每个客户端请求由独立线程处理,提高并发性能。
- 实时数据处理:多个线程并发处理传感器数据,实现高效实时响应。
- 后台任务调度:定时执行任务,采用多个线程协同工作。
- 游戏开发:分离渲染、物理计算、网络通信线程,优化性能。
知识拓展
- 线程池技术:通过线程池管理大量线程,避免频繁创建销毁带来的开销。
- 并发包(java.util.concurrent):提供高级线程管理工具,如Executor、Future、Semaphore等。
- 线程间通信:深入学习wait()、notify()机制,掌握线程协作。
- 死锁与避免策略:理解死锁产生原因,掌握避免方法。
- 线程本地存储(ThreadLocal):为每个线程提供独立变量副本,避免共享冲突。
总结回顾
本节重点围绕线程的创建与管理展开,主要内容包括:
- 线程基本概念及生命周期状态详解
- 线程创建的两种主要方式:继承Thread类和实现Runnable接口
- 线程启动、休眠、中断及终止的管理方法
- 线程优先级与调度机制的基本原理
- 通过典型实例演示线程创建和优先级设置
- 常见误区剖析,避免线程使用中的陷阱
- 多线程实际应用场景介绍,拓展学习方向
掌握本节内容,考生能够熟练实现线程创建及基本管理,理解多线程并发执行的原理和特点,为后续并发控制与线程安全技术打下坚实基础。