01 多线程初阶:解谜多线程世界
在日常开发中,你是否曾遇到这样的情景:应用程序需要执行多个任务,但你希望它们能够同时运行,以提高性能和响应性。这正是多线程编程的核心价值所在。
在本文中,我们将学习 Java 多线程编程的基础知识,包括线程的创建、使用、生命周期以及线程安全产生的原因。
一、线程创建与启动
线程是轻量级的,共享相同的进程内存空间。在 Java 中,线程可以使用 java.lang.Thread 类来创建和管理。
1. 实现 Runnable 接口
public class ThreadRunnableTest {
public static void main(String[] args) {
Thread thread = new Thread(new Task());
thread.setName("测试线程");
thread.start();
}
static class Task implements Runnable {
@Override
public void run() {
System.out.println("线程运行,线程名称为:" + Thread.currentThread().getName());
}
}
}调用 start 方法后,主线程会开启一条子线程去执行任务,主线程继续向下执行,两者并行执行。
2. 实现 Callable 接口(有返回值)
public class ThreadCallableTest {
public static void main(String[] args) throws Exception {
FutureTask<String> stringFutureTask = new FutureTask<>(new TaskReturn());
Thread thread = new Thread(stringFutureTask);
thread.setName("测试线程");
thread.start();
System.out.println(stringFutureTask.get());
}
private static class TaskReturn implements Callable<String> {
@Override
public String call() {
return String.format("我被线程【%s】执行了", Thread.currentThread().getName());
}
}
}Callable 无法直接传递到 Thread 中,需要使用 FutureTask 来包装。FutureTask 的 get 方法可以获取异步任务的执行结果。
主线程与子线程:点击运行后,执行
main方法的线程称为主线程,从main方法中创建的线程称为子线程。
二、线程的主要参数与 API
1. 优先级
线程的优先级是一个整数值(1-10),1 表示最低优先级,10 表示最高优先级。
thread.setPriority(Thread.MAX_PRIORITY); // 设置为最高优先级注意:线程优先级依赖于底层操作系统支持,不保证严格按照优先级顺序执行,且过度依赖可能导致不可预测的结果。
常见问题:
- 优先级反转:低优先级线程持有锁后,高优先级线程等待时被阻塞
- 饥饿:高优先级线程长期占用资源,低优先级线程无法获得执行机会
2. 线程名称
thread.setName("测试线程"); // 设置线程名称
Thread.currentThread().getName() // 获取当前线程名称线程名称在排查问题(如死锁、系统变慢)时非常重要,可以使用 jconsole、jstack 等 JVM 工具监控。
3. 守护线程
守护线程的生命周期随主线程结束而终止,用于执行后台任务(如垃圾回收、定时任务、监控等)。
Thread thread = new Thread(new Task());
thread.setDaemon(true); // 设置为守护线程
thread.start();守护线程 vs 工作线程:
- 守护线程:主线程执行完毕,守护线程立即停止
- 工作线程:JVM 会等待所有工作线程结束后才停止
注意:守护线程可能来不及执行资源回收(如关闭 JDBC 连接),需谨慎使用。
4. 停止线程
不推荐使用 thread.stop(),会导致资源不释放。
推荐使用 interrupt 方法请求线程停止:
public class StopThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Task());
thread.setName("测试线程");
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt(); // 发出停止信号
}
private static class Task implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("我执行了");
}
}
}
}interrupt 方法会在线程等待条件时触发 InterruptedException 异常,需配合循环检测中断信号来终止线程。
三、线程生命周期和状态
Java 线程的生命周期包括以下状态:
| 状态 | 说明 |
|---|---|
| New | 线程对象已创建,但尚未启动 |
| Runnable | 线程已准备好运行,等待 CPU 时间片 |
| Running | 线程获得 CPU 时间片并开始执行 |
| Blocked | 线程被阻塞,等待锁或其他条件 |
| Waiting | 线程等待其他线程通知继续执行 |
| Timed Waiting | 线程等待一段时间后自动唤醒 |
| Terminated | 线程生命周期结束 |
状态转换图:
四、竞态条件和临界区
并发安全问题的产生源于多线程同时访问共享资源,且操作顺序敏感。
小故事:面包店的小明雇佣了两个助手小红和小绿做蛋糕。第三步"添加奶油和装饰"需要按顺序完成,但由于两人同时操作,导致蛋糕做砸了。
解决方案:引入规则——同时只有一人能操作最后一步。这个规则就是锁!
关键概念:
- 临界区:共享资源或代码段,多线程同时访问会产生问题
- 竞态条件:多个线程同时以不同顺序访问临界区
- 锁:保证同一时刻只有一个线程能访问临界区
五、总结
线程是提升服务器资源利用率的关键工具。在本节中,我们学习了:
- 线程的创建方式(
Runnable、Callable) - 线程的参数(优先级、名称、守护线程)
- 线程的停止方法(
interrupt) - 线程的生命周期和状态
- 并发安全问题的产生原因(竞态条件、临界区)
下一节,我们将学习如何合理、高效、安全地使用线程。