第六章 多线程与并发
第二节 Java多线程基础与线程的创建与管理
概述
本节内容聚焦于Java多线程编程的基础知识,重点讲解线程的创建、启动、执行及生命周期管理。掌握本节内容,有助于考生理解多线程的基本原理和Java中线程的具体实现方法,同时为后续深入学习线程同步与并发控制打下坚实基础。
学习目标
- 理解线程的概念及其重要性
- 掌握Java中创建线程的两种主要方式
- 理解线程的生命周期及状态转换
- 学习线程的基本管理操作,如启动、休眠、终止
- 通过实例掌握线程的实际应用
核心概念
线程(Thread)
线程是程序执行的最小单位,是进程中的一个执行流。Java中的多线程允许程序同时执行多个任务,提高程序响应能力和资源利用率。
进程(Process)
进程是操作系统分配资源的基本单位,一个进程中可以包含多个线程。
线程的生命周期
线程从创建到终止经历多个状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、等待(Waiting)、计时等待(Timed Waiting)、终止(Terminated)。
线程调度
线程调度是操作系统或Java虚拟机决定哪个线程获得CPU执行权的过程,Java线程调度基于优先级和时间片轮转。
线程安全
指多线程环境下,多个线程访问共享资源时能够保证数据的一致性和正确性。
原理分析
Java的多线程机制基于Java.lang.Thread类和Runnable接口。
- Thread类代表一个线程对象,直接继承Thread类可以创建线程。
- Runnable接口定义了线程执行任务的规范,适合多个线程共享同一任务。
线程生命周期管理依赖于操作系统线程调度,Java线程状态转移是通过线程API和JVM实现的。线程启动后由操作系统调度分配CPU时间片,线程执行run()方法体内的代码。
线程切换是CPU在不同线程间快速切换执行权,虽然提高了程序响应性,但频繁切换带来上下文切换开销。
详细内容
1. 线程的创建方式
Java中创建线程的两种主要方式:
继承Thread类
- 定义一个继承Thread的子类,重写run()方法。
- 创建该子类的实例。
- 调用start()方法启动线程。
示例代码:
class MyThread extends Thread {
public void run() {
System.out.println("线程运行中:" + Thread.currentThread().getName());
}
}
public class TestThread {
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("线程运行中:" + Thread.currentThread().getName());
}
}
public class TestRunnable {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
区别与选择
| 继承Thread类 | 实现Runnable接口 |
|---|---|
| 单继承限制,不能继承其他类 | 可以继承其他类,灵活性高 |
| 线程对象即任务对象 | 线程对象和任务对象分离,有利资源共享 |
| 适合简单线程任务 | 更适合复杂任务与线程共享 |
2. 线程的生命周期及状态转换
线程状态包括:
- 新建状态(New):线程对象被创建,但尚未启动。
- 就绪状态(Runnable):调用start()后线程处于就绪,等待CPU分配时间。
- 运行状态(Running):线程获得CPU时间片,执行run()方法。
- 阻塞状态(Blocked):线程等待获取监视器锁,无法执行。
- 等待状态(Waiting):线程无限期等待其他线程通知。
- 计时等待(Timed Waiting):线程等待指定时间后自动恢复。
- 终止状态(Terminated):线程执行完毕或异常结束。
线程状态转换图示:
- 新建 -> 就绪:调用start()
- 就绪 -> 运行:线程调度
- 运行 -> 阻塞:等待锁
- 运行 -> 等待:调用wait()
- 运行 -> 计时等待:调用sleep(), join(timeout)
- 阻塞/等待/计时等待 -> 就绪:锁释放或等待结束
- 运行 -> 终止:run()执行完成或异常
3. 线程的基本操作
- start():启动线程,进入就绪状态。
- run():线程执行体,建议不要直接调用run(),否则成为普通方法调用。
- sleep(long millis):使线程暂停指定毫秒,进入计时等待。
- yield():线程让出CPU,进入就绪状态,等待重新调度。
- join():等待线程执行完毕。
- interrupt():中断线程,设置中断标志。
4. 线程优先级
Java线程优先级范围为1(最低)到10(最高),默认优先级是5。
线程调度器优先选择优先级高的线程执行,但优先级不是绝对保证,依赖于操作系统。
5. 线程安全与同步基础(预备知识)
多线程访问共享资源可能导致数据不一致,线程安全问题由此产生。Java提供了synchronized关键字和Lock接口实现线程同步,这将在后续章节详细讲解。
实例分析
实例1:继承Thread类创建线程
背景:实现一个简单线程,输出线程名称。
代码:
class MyThread extends Thread {
public void run() {
for(int i=0; i<5; i++) {
System.out.println(Thread.currentThread().getName() + "运行第" + i + "次");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
}
}
}
public class TestThread {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
分析:
- 创建两个线程对象t1和t2
- 分别调用start()启动线程
- 两个线程交替运行,输出各自名称和计数
- 使用sleep暂停线程,模拟实际工作
结论:继承Thread类简单直接,适合初学者理解线程基本概念。
实例2:实现Runnable接口创建线程
背景:多个线程执行同一个任务,模拟售票场景。
代码:
class TicketWindow implements Runnable {
private int tickets = 5;
public void run() {
while(tickets > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,余票:" + tickets);
tickets--;
try {
Thread.sleep(200);
} catch (InterruptedException e) {}
}
}
}
public class TestRunnable {
public static void main(String[] args) {
TicketWindow window = new TicketWindow();
Thread t1 = new Thread(window, "窗口1");
Thread t2 = new Thread(window, "窗口2");
t1.start();
t2.start();
}
}
分析:
- TicketWindow实现Runnable,定义共享票数变量
- 两个线程共享同一Runnable对象,模拟售票窗口
- 可能出现线程安全问题(票数错误)
结论:Runnable方式适合多个线程共享任务对象,利于资源共享与管理。
实例3:线程状态演示
背景:演示线程的不同状态转换。
代码:
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
Thread.sleep(1000); // TIMED_WAITING
} catch (InterruptedException e) {}
});
System.out.println("新建状态: " + t.getState()); // NEW
t.start();
Thread.sleep(100); // 等待线程启动
System.out.println("运行或就绪状态: " + t.getState()); // RUNNABLE
Thread.sleep(1100);
System.out.println("终止状态: " + t.getState()); // TERMINATED
}
}
分析:
- 观察线程在不同时间点的状态
- 通过sleep模拟计时等待状态
结论:线程状态可通过getState()方法查看,理解状态有助于调试和设计多线程程序。
常见误区与注意事项
直接调用run()方法启动线程
- 错误:直接调用run()方法不会启动新线程,而是当前线程顺序执行。
- 正确:调用start()方法启动线程。
线程启动后重复调用start()
- 错误:同一个线程对象start()只能调用一次,再次调用抛出IllegalThreadStateException。
忽视线程安全问题
- 多线程共享资源时未同步,导致数据不一致。
- 需使用同步机制保证安全。
对线程优先级理解错误
- 优先级高的线程不一定先执行,线程调度受操作系统影响。
忽略异常处理
- sleep()和join()等方法会抛出InterruptedException,必须捕获或声明抛出。
应用场景
- 多任务并行处理:如同时下载多个文件,提高效率。
- 界面响应性:UI线程负责界面显示,后台线程处理耗时操作,避免界面卡死。
- 服务器并发请求处理:每个请求启动独立线程处理,提升并发能力。
- 定时任务执行:使用线程定时执行任务,如日志清理。
- 资源共享协调:多个线程协作完成复杂任务。
知识拓展
- 线程同步与锁机制:synchronized关键字、Lock接口
- 线程通信:wait(), notify(), notifyAll()方法
- 线程池:Executor框架管理线程资源,提高性能
- 并发工具类:CountDownLatch, CyclicBarrier, Semaphore等
- 线程安全集合:ConcurrentHashMap等
总结回顾
本节重点介绍了Java多线程编程的基础知识,重点内容包括:
- 线程的定义与重要性
- 线程的两种创建方式:继承Thread类与实现Runnable接口
- 线程生命周期及状态转换
- 线程的基本管理操作,如启动、休眠、中断
- 线程优先级的基本概念
通过典型实例演示,帮助理解线程创建与状态管理。掌握这些基础,有利于后续学习线程同步、并发控制和高级多线程开发。考生应重点理解start()与run()的区别,线程状态转移,以及线程安全的预备知识。
关键知识点
- 线程是程序执行的最小单位,实现多任务并发
- Java中创建线程的两种方式:继承Thread类和实现Runnable接口
- 线程生命周期包括新建、就绪、运行、阻塞、等待、计时等待、终止七种状态
- 启动线程必须调用start()方法,不能直接调用run()
- 线程优先级影响调度,但不保证绝对执行顺序
- 多线程共享资源时需注意线程安全,避免数据冲突
- 线程状态可通过getState()方法查看,便于调试
- sleep()方法让线程进入计时等待状态
- join()方法用于等待线程完成
- 线程调度由操作系统和JVM共同完成