线程基础 (Thread Fundamentals)¶
理解线程的概念、创建方式、生命周期,掌握多线程编程的基础知识
目录¶
1. 什么是线程 (What is Thread)¶
1.1 进程与线程¶
进程(Process):操作系统进行资源分配和调度的基本单位,每个进程都有独立的内存空间。
线程(Thread):进程内的执行单元,是CPU调度的基本单位。一个进程可以包含多个线程,这些线程共享进程的内存空间。
1.2 为什么需要多线程?¶
- 提高CPU利用率 - 在IO等待时,CPU可以执行其他线程
- 提高响应速度 - 后台任务不阻塞用户界面
- 充分利用多核CPU - 并行处理提高吞吐量
- 简化编程模型 - 某些场景下多线程更直观
1.3 在算力平台中的应用¶
在算力平台中,多线程的应用场景:
- 任务调度线程 - 定时从Nomad获取任务状态
- 节点监控线程 - 并发采集多个节点的资源信息
- 结算处理线程 - 批量处理用户钱包的扣费和充值
- Web请求处理 - Tomcat使用线程池处理HTTP请求
2. 线程的创建方式 (Thread Creation)¶
2.1 方式一:继承Thread类¶
/**
* 方式一:继承Thread类
* Method 1: Extend Thread Class
*
* 优点:简单直接
* 缺点:Java单继承,不够灵活
*/
public class MyThread extends Thread {
@Override
public void run() {
// 线程执行的代码
System.out.println("线程执行中: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程,调用run()方法
}
}
注意事项:
- 必须重写run()方法
- 调用start()方法启动线程,而不是直接调用run()
- start()会创建新线程,run()只是普通方法调用
2.2 方式二:实现Runnable接口(推荐)¶
/**
* 方式二:实现Runnable接口(推荐)
* Method 2: Implement Runnable Interface (Recommended)
*
* 优点:可以继承其他类,更灵活
* 缺点:无返回值,不能抛出受检异常
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行中: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
// 方式1:创建Thread对象,传入Runnable
Thread thread = new Thread(new MyRunnable());
thread.start();
// 方式2:使用Lambda表达式(Java 8+)
Thread thread2 = new Thread(() -> {
System.out.println("Lambda线程执行");
});
thread2.start();
}
}
为什么推荐Runnable? 1. 可以继承其他类,更灵活 2. 符合面向接口编程的原则 3. 可以复用同一个Runnable对象创建多个线程
2.3 方式三:实现Callable接口¶
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* 方式三:实现Callable接口
* Method 3: Implement Callable Interface
*
* 优点:有返回值,可以抛出异常
* 缺点:使用相对复杂
*/
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
// 执行任务并返回结果
Thread.sleep(1000);
return "任务执行完成";
}
public static void main(String[] args) throws Exception {
// Callable需要包装在FutureTask中
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
// 获取返回值(会阻塞直到任务完成)
String result = futureTask.get();
System.out.println("结果: " + result);
}
}
Callable vs Runnable:
| 特性 | Runnable | Callable |
|---|---|---|
| 返回值 | 无 | 有 |
| 异常 | 不能抛出受检异常 | 可以抛出异常 |
| 使用场景 | 简单任务 | 需要返回值的任务 |
2.4 方式四:使用线程池(生产环境推荐)¶
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 方式四:使用线程池(生产环境推荐)
* Method 4: Use Thread Pool (Recommended for Production)
*
* 优点:资源可控,性能好,便于管理
*/
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("任务 " + taskId + " 执行中");
});
}
// 关闭线程池
executor.shutdown();
}
}
线程池的优势: 1. 资源复用 - 避免频繁创建和销毁线程 2. 资源可控 - 限制线程数量,防止资源耗尽 3. 任务管理 - 支持任务队列、拒绝策略等
详细内容请参考:07-05 - 线程池
3. 线程的生命周期 (Thread Lifecycle)¶
3.1 线程状态(Thread States)¶
Java线程有6种状态,定义在Thread.State枚举中:
public enum State {
NEW, // 新建状态
RUNNABLE, // 可运行状态
BLOCKED, // 阻塞状态
WAITING, // 等待状态
TIMED_WAITING, // 定时等待状态
TERMINATED // 终止状态
}
3.2 状态转换图¶
NEW
|
| start()
↓
RUNNABLE ←────────┐
| |
| 获取CPU时间片 |
| |
| |
BLOCKED (等待锁) |
| |
| 获取到锁 |
| |
WAITING (wait()) |
| |
| notify() |
| |
TIMED_WAITING (sleep)│
| │
| 时间到/中断 │
└────────────┘
|
| run()方法执行完毕
↓
TERMINATED
3.3 各状态详解¶
NEW(新建状态)¶
线程对象已创建,但尚未调用start()方法。
RUNNABLE(可运行状态)¶
线程调用了start()方法,可能正在运行,也可能在等待CPU时间片。
Thread thread = new Thread(() -> {
while (true) {
// 执行任务
}
});
thread.start();
System.out.println(thread.getState()); // RUNNABLE
注意: RUNNABLE状态包括: - Running - 正在CPU上执行 - Ready - 等待CPU时间片
BLOCKED(阻塞状态)¶
线程等待获取监视器锁(monitor lock),进入synchronized代码块时。
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(5000); // 持有锁5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) { // 等待thread1释放锁
System.out.println("获取到锁");
}
});
thread1.start();
Thread.sleep(100); // 确保thread1先获取锁
thread2.start();
Thread.sleep(100);
System.out.println(thread2.getState()); // BLOCKED
WAITING(等待状态)¶
线程无限期等待其他线程的特定操作。
Object lock = new Object();
Thread thread = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(); // 进入WAITING状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
Thread.sleep(100);
System.out.println(thread.getState()); // WAITING
进入WAITING的方法:
- Object.wait() - 等待notify()
- Thread.join() - 等待其他线程结束
- LockSupport.park() - 等待unpark()
TIMED_WAITING(定时等待状态)¶
线程等待指定的时间。
Thread thread = new Thread(() -> {
try {
Thread.sleep(5000); // 睡眠5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
Thread.sleep(100);
System.out.println(thread.getState()); // TIMED_WAITING
进入TIMED_WAITING的方法:
- Thread.sleep(long) - 睡眠指定时间
- Object.wait(long) - 等待指定时间
- Thread.join(long) - 等待其他线程指定时间
- LockSupport.parkNanos() / parkUntil() - 定时等待
TERMINATED(终止状态)¶
线程执行完毕或异常退出。
Thread thread = new Thread(() -> {
System.out.println("执行完毕");
});
thread.start();
thread.join(); // 等待线程结束
System.out.println(thread.getState()); // TERMINATED
4. 线程的基本操作 (Thread Basic Operations)¶
4.1 启动线程 - start()¶
start() vs run():
- start() - 创建新线程,异步执行
- run() - 普通方法调用,同步执行
4.2 等待线程结束 - join()¶
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("任务完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
thread.join(); // 等待thread执行完毕
System.out.println("主线程继续执行");
join()的重载方法:
- join() - 无限期等待
- join(long millis) - 等待指定毫秒数
- join(long millis, int nanos) - 等待指定时间(纳秒精度)
4.3 线程睡眠 - sleep()¶
try {
Thread.sleep(1000); // 睡眠1秒
} catch (InterruptedException e) {
// 处理中断异常
e.printStackTrace();
}
注意事项:
- sleep()不会释放锁
- 可能抛出InterruptedException
- 睡眠时间不精确,可能更长
4.4 线程中断 - interrupt()¶
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 执行任务
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 捕获中断异常,退出循环
System.out.println("线程被中断");
break;
}
}
});
thread.start();
Thread.sleep(5000);
thread.interrupt(); // 中断线程
中断机制:
- interrupt() - 设置中断标志
- isInterrupted() - 检查中断标志(不清除)
- interrupted() - 检查并清除中断标志
4.5 线程让步 - yield()¶
Thread thread = new Thread(() -> {
for (int i = 0; i < 100; i++) {
if (i % 10 == 0) {
Thread.yield(); // 让出CPU时间片
}
System.out.println(i);
}
});
注意: yield()只是建议,不保证一定让出CPU。
5. 守护线程 (Daemon Thread)¶
5.1 什么是守护线程?¶
守护线程(Daemon Thread):为其他线程提供服务的线程,当所有非守护线程结束时,守护线程会自动结束。
5.2 守护线程的特点¶
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("守护线程运行中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
daemonThread.setDaemon(true); // 设置为守护线程
daemonThread.start();
// 主线程执行完毕后,守护线程自动结束
Thread.sleep(3000);
System.out.println("主线程结束");
守护线程的特点:
1. 必须在start()之前设置
2. 所有非守护线程结束时,守护线程自动结束
3. 不能用于执行重要任务(可能被强制结束)
5.3 应用场景¶
- GC线程 - 垃圾回收
- 监控线程 - 系统监控、日志记录
- 定时任务线程 - 定时清理缓存
6. 线程优先级 (Thread Priority)¶
6.1 优先级设置¶
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("Thread 1: " + i);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("Thread 2: " + i);
}
});
thread1.setPriority(Thread.MAX_PRIORITY); // 10
thread2.setPriority(Thread.MIN_PRIORITY); // 1
thread1.start();
thread2.start();
优先级范围:
- MIN_PRIORITY = 1
- NORM_PRIORITY = 5(默认)
- MAX_PRIORITY = 10
6.2 注意事项¶
⚠️ 优先级只是建议,不保证执行顺序
不同操作系统的线程调度策略不同,优先级可能不起作用。生产环境中**不建议依赖优先级**,应该使用更可靠的同步机制。
7. 最佳实践 (Best Practices)¶
7.1 线程命名¶
Thread thread = new Thread(() -> {
// 任务逻辑
}, "Task-Thread-1"); // 给线程命名
// 或者在run()方法中
Thread.currentThread().setName("Custom-Name");
好处: 便于调试和日志追踪
7.2 异常处理¶
Thread thread = new Thread(() -> {
try {
// 可能抛出异常的任务
riskyTask();
} catch (Exception e) {
// 记录日志
logger.error("线程执行异常", e);
// 不要吞掉异常
}
});
// 或者使用UncaughtExceptionHandler
thread.setUncaughtExceptionHandler((t, e) -> {
logger.error("线程 " + t.getName() + " 未捕获异常", e);
});
7.3 避免直接继承Thread¶
// ❌ 不推荐
class MyThread extends Thread {
// ...
}
// ✅ 推荐
class MyTask implements Runnable {
// ...
}
Thread thread = new Thread(new MyTask());
7.4 使用线程池¶
// ❌ 不推荐:直接创建线程
for (int i = 0; i < 100; i++) {
new Thread(() -> {
// 任务
}).start();
}
// ✅ 推荐:使用线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
// 任务
});
}
8. 面试高频问题 (Interview Questions)¶
Q1: start()和run()的区别?¶
答案:
- start() - 创建新线程,异步执行run()方法
- run() - 普通方法调用,同步执行,不会创建新线程
Thread thread = new Thread(() -> System.out.println("执行"));
thread.start(); // 创建新线程执行
thread.run(); // 在当前线程执行
Q2: 线程的生命周期有哪些状态?¶
答案: NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
Q3: 如何创建线程?推荐哪种方式?¶
答案: 1. 继承Thread类 2. 实现Runnable接口(推荐) 3. 实现Callable接口(需要返回值时) 4. 使用线程池(生产环境推荐)
Q4: 守护线程和普通线程的区别?¶
答案: - 守护线程:为其他线程服务,所有非守护线程结束时自动结束 - 普通线程:执行主要业务逻辑,必须执行完毕
Q5: sleep()和wait()的区别?¶
答案:
| 特性 | sleep() | wait() |
|---|---|---|
| 所属类 | Thread | Object |
| 释放锁 | 否 | 是 |
| 唤醒方式 | 时间到 | notify()/notifyAll() |
| 使用场景 | 暂停执行 | 线程间通信 |
📖 扩展阅读¶
返回: 07-Java并发编程
下一章: 07-02 - 线程同步与内存模型 →
上一章: ← 06 - Java集合框架