Appearance
18.3 线程启动
线程启动的概念
线程启动是指将线程从新建状态转换到运行状态的过程。在 Java 中,线程启动是通过调用 start() 方法实现的,而不是直接调用 run() 方法。
线程启动的方法
1. 使用 start() 方法
语法:
java
thread.start();作用:
- 启动线程,使其进入就绪状态
- 等待 CPU 时间片,获得时间片后开始执行
run()方法 - 每个线程只能启动一次,多次调用会抛出
IllegalThreadStateException
示例:
java
public class ThreadStartExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 执行完毕");
}, "工作线程");
// 启动线程
thread.start();
System.out.println(Thread.currentThread().getName() + " 继续执行");
}
}2. 直接调用 run() 方法的问题
症状:线程没有并发执行,而是在当前线程中串行执行
原因:直接调用 run() 方法只是普通的方法调用,不会启动新线程
示例:
java
public class DirectRunExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 执行完毕");
}, "工作线程");
// 错误:直接调用 run() 方法
thread.run();
System.out.println(Thread.currentThread().getName() + " 继续执行");
}
}输出:
main 开始执行
main 执行完毕
main 继续执行线程启动的原理
1. 线程状态转换
当调用 start() 方法时,线程会经历以下状态转换:
- 新建(New):线程对象已创建,但尚未启动
- 就绪(Runnable):线程已经启动,等待 CPU 时间片
- 运行(Running):线程获得 CPU 时间片,执行
run()方法 - 终止(Terminated):线程执行完毕或异常终止
2. 启动过程
- 创建线程对象:通过
new Thread()创建线程对象 - 调用 start() 方法:请求系统创建新线程
- 系统分配资源:系统为线程分配栈空间等资源
- 线程进入就绪状态:线程等待 CPU 时间片
- 执行 run() 方法:线程获得 CPU 时间片后,开始执行
run()方法 - 线程终止:
run()方法执行完毕,线程进入终止状态
线程启动的注意事项
1. 只能启动一次
症状:多次调用 start() 方法会抛出 IllegalThreadStateException
解决方案:一个线程只能启动一次,需要创建新的线程对象来执行新的任务
示例:
java
// 错误:多次调用 start() 方法
Thread thread = new Thread(() -> {
System.out.println("线程执行");
});
thread.start();
// thread.start(); // 抛出 IllegalThreadStateException
// 正确:创建新的线程对象
Thread thread1 = new Thread(() -> {
System.out.println("线程1执行");
});
Thread thread2 = new Thread(() -> {
System.out.println("线程2执行");
});
thread1.start();
thread2.start();2. 线程启动的顺序
症状:线程启动的顺序与执行的顺序不一致
原因:线程启动后进入就绪状态,等待 CPU 时间片,执行顺序由操作系统的调度算法决定
示例:
java
public class ThreadOrderExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println("线程1 开始执行");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1 执行完毕");
}, "线程1");
Thread thread2 = new Thread(() -> {
System.out.println("线程2 开始执行");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2 执行完毕");
}, "线程2");
Thread thread3 = new Thread(() -> {
System.out.println("线程3 开始执行");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程3 执行完毕");
}, "线程3");
// 启动线程
thread1.start();
thread2.start();
thread3.start();
System.out.println("主线程执行完毕");
}
}可能的输出:
主线程执行完毕
线程3 开始执行
线程1 开始执行
线程2 开始执行
线程3 执行完毕
线程1 执行完毕
线程2 执行完毕3. 线程启动的开销
症状:频繁创建和启动线程会导致系统开销增加
解决方案:使用线程池来重用线程,减少线程创建和销毁的开销
示例:
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolStartExample {
public static void main(String[] args) {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交多个任务
for (int i = 1; i <= 10; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务 " + taskId);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
}
}线程启动的最佳实践
使用 start() 方法:使用
start()方法启动线程,而不是直接调用run()方法避免多次启动:一个线程只能启动一次,需要创建新的线程对象来执行新的任务
使用线程池:对于大量短期任务,使用线程池可以减少线程创建和销毁的开销
设置线程名称:为线程设置有意义的名称,便于调试和监控
处理异常:在线程的
run()方法中捕获并处理异常,避免线程意外终止合理设置线程优先级:根据任务的重要性设置线程优先级,但不要过度依赖优先级
避免线程安全问题:对于共享资源,使用适当的同步机制
关闭线程池:使用完毕后关闭线程池,避免资源泄漏
示例:线程启动的综合应用
示例 1:多线程下载文件
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FileDownloadExample {
public static void main(String[] args) {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 模拟下载任务
String[] files = {"file1.txt", "file2.txt", "file3.txt", "file4.txt", "file5.txt"};
for (String file : files) {
final String fileName = file;
executor.submit(() -> {
System.out.println(Thread.currentThread().getName() + " 开始下载 " + fileName);
try {
// 模拟下载时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 完成下载 " + fileName);
});
}
// 关闭线程池
executor.shutdown();
}
}示例 2:多线程处理数据
java
public class DataProcessingExample {
public static void main(String[] args) {
// 创建任务
Runnable task1 = () -> {
System.out.println("任务1 开始处理数据");
processData("任务1");
System.out.println("任务1 完成处理数据");
};
Runnable task2 = () -> {
System.out.println("任务2 开始处理数据");
processData("任务2");
System.out.println("任务2 完成处理数据");
};
Runnable task3 = () -> {
System.out.println("任务3 开始处理数据");
processData("任务3");
System.out.println("任务3 完成处理数据");
};
// 创建并启动线程
Thread thread1 = new Thread(task1, "线程1");
Thread thread2 = new Thread(task2, "线程2");
Thread thread3 = new Thread(task3, "线程3");
thread1.start();
thread2.start();
thread3.start();
}
private static void processData(String taskName) {
try {
// 模拟数据处理时间
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}总结
线程启动是 Java 并发编程的重要环节:
线程启动的方法:
- 使用
start()方法启动线程 - 避免直接调用
run()方法
- 使用
线程启动的原理:
- 线程状态从新建转换到就绪
- 等待 CPU 时间片
- 获得时间片后执行
run()方法
线程启动的注意事项:
- 只能启动一次
- 启动顺序与执行顺序不一致
- 频繁启动线程会增加系统开销
线程启动的最佳实践:
- 使用
start()方法 - 避免多次启动
- 使用线程池
- 设置线程名称
- 处理异常
- 避免线程安全问题
- 使用
通过合理启动线程,可以有效地实现并发编程,提高程序的性能和响应速度。
