面试必问!Java线程控制6大核心方法(start/run区别+实操案例,代码可直接跑)
Java多线程开发中,“创建线程”只是入门,“控制线程执行节奏”才是核心!很多新手踩坑在混用start()和run()、线程执行无序、不会优雅中断线程,这些不仅是开发高频问题,更是面试必考点!今天带大家吃透线程控制的6大核心方法,结合IO、集合实战演练,代码可直接复制运行,还会揭秘面试常问的区别与避坑指南~
GZH【咖啡 Java 研习班】 面试题库和多线程学习路线图,技术交流群随时答疑!
前言:为什么线程控制这么重要?
多线程的核心价值是“并发有序执行”——如果不会控制线程的休眠、插队、优先级,程序只会变成“混乱的并发”:比如主线程提前汇总未统计完的数据、线程无法优雅停止导致资源泄漏。本文6大方法+5个实操案例,帮你精准把控线程执行节奏,彻底摆脱“线程失控”的困境!
一、避坑第一站:start()和run()的本质区别(面试必考!)
这是多线程最基础也最易踩的坑!很多人以为两者都能启动线程,实则天差地别,面试必问“为什么不能直接调用run()”。
核心区别(记牢这2点,面试不慌)
- start()方法:真正启动新线程!调用后线程进入就绪状态,等待CPU调度,后续会自动执行run()方法。一个线程只能调用1次start(),重复调用会抛出
IllegalThreadStateException(受检异常,必须处理)。 - run()方法:仅线程执行体!直接调用等同于普通方法调用,不会创建新线程,所有逻辑在当前线程(比如主线程)同步执行,毫无并发效果。
🌰 实操案例:直观对比两者差异
结合集合遍历场景,看执行线程的区别:
import java.util.ArrayList;
import java.util.List;
class TaskRunnable implements Runnable {
private List<String> dataList;
public TaskRunnable(List<String> dataList) {
this.dataList = dataList;
}
@Override
public void run() {
System.out.println("当前执行线程名称:" + Thread.currentThread().getName());
for (String data : dataList) {
System.out.println("处理数据:" + data);
}
}
}
public class StartVsRunDemo {
public static void main(String[] args) {
List<String> dataList = new ArrayList<>();
dataList.add("Java多线程");
dataList.add("IO流");
dataList.add("集合框架");
TaskRunnable task = new TaskRunnable(dataList);
// 1. 直接调用run():无新线程,主线程执行
System.out.println("===== 调用run() =====");
task.run(); // 执行线程:main
// 2. 调用start():创建新线程执行
System.out.println("\n===== 调用start() =====");
new Thread(task, "数据处理线程").start(); // 执行线程:数据处理线程
// 以下代码会抛出异常(注释掉可测试)
// Thread thread = new Thread(task);
// thread.start();
// thread.start(); // 重复调用start(),抛IllegalThreadStateException
}
}
执行结果&结论
===== 调用run() =====
当前执行线程名称:main
处理数据:Java多线程
处理数据:IO流
处理数据:集合框架
===== 调用start() =====
当前执行线程名称:数据处理线程
处理数据:Java多线程
处理数据:IO流
处理数据:集合框架
👉 结论:只有start()能启动新线程实现并发,run()只是普通方法调用,面试遇到“如何启动线程”,直接答start(),并解释两者区别!
二、线程休眠:sleep()方法(控制执行节奏)
Thread.sleep(long millis) 让当前线程休眠指定毫秒数,休眠期间线程进入阻塞状态,释放CPU资源,但不会释放持有的锁。休眠结束后回到就绪状态,等待CPU重新调度。
核心注意点(避坑关键)
- 必须处理
InterruptedException(受检异常),要么捕获要么声明抛出; - 休眠时间是“最小等待时间”:休眠结束后需竞争CPU,实际执行时间可能略长于设定值;
- 不释放锁:如果线程持有synchronized锁,休眠期间其他线程无法获取该锁。
🌰 实操案例:IO分批读取文件
模拟“每读取3行文件休眠500ms”的业务场景,结合IO流实现:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
class FileReadTask implements Runnable {
private String filePath;
public FileReadTask(String filePath) {
this.filePath = filePath;
}
@Override
public void run() {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(filePath));
String line;
int lineNum = 0;
while ((line = br.readLine()) != null) {
lineNum++;
// 每3行休眠一次,模拟分批处理
if (lineNum % 3 == 0) {
System.out.println(Thread.currentThread().getName() + " 分批处理中,休眠500ms");
Thread.sleep(500);
}
System.out.println(Thread.currentThread().getName() + " 读取内容:" + line);
}
} catch (IOException e) {
System.out.println("文件读取异常:" + e.getMessage());
e.printStackTrace();
} catch (InterruptedException e) {
System.out.println("线程休眠被中断:" + e.getMessage());
e.printStackTrace();
} finally {
// 关闭流资源,避免内存泄漏
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class ThreadSleepDemo {
public static void main(String[] args) {
FileReadTask task = new FileReadTask("data.txt");
new Thread(task, "文件读取线程1").start();
new Thread(task, "文件读取线程2").start();
}
}
三、线程插队:join()方法(保证执行顺序)
当需要让某个线程“优先执行完毕”,再继续执行当前线程时,用join()方法。比如主线程需要等待子线程统计完数据再汇总,避免“数据未统计完就计算”的错误。
核心作用
- 调用
thread.join():当前线程进入阻塞状态,直到目标线程thread执行完毕,当前线程才恢复执行; - 支持超时重载:
join(long millis),超过指定时间后,当前线程自动恢复执行,无需等待目标线程结束。
🌰 实操案例:集合数据统计插队
主线程等待子线程统计完1-10的总和后,再进行汇总分析:
import java.util.ArrayList;
import java.util.List;
class DataCountTask implements Runnable {
private List<Integer> numList;
public int sum = 0; // 存储统计结果
public DataCountTask(List<Integer> numList) {
this.numList = numList;
}
@Override
public void run() {
for (Integer num : numList) {
sum += num;
try {
Thread.sleep(100); // 模拟计算延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 统计完成,总和:" + sum);
}
}
public class ThreadJoinDemo {
public static void main(String[] args) throws InterruptedException {
// 初始化1-10的数字集合
List<Integer> numList = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
numList.add(i);
}
DataCountTask task = new DataCountTask(numList);
Thread countThread = new Thread(task, "数据统计线程");
countThread.start();
System.out.println("主线程等待统计线程执行...");
countThread.join(); // 主线程阻塞,等待统计线程完成
System.out.println("主线程获取统计结果:" + task.sum);
System.out.println("主线程开始汇总分析...");
}
}
执行结果
主线程等待统计线程执行...
数据统计线程 统计完成,总和:55
主线程获取统计结果:55
主线程开始汇总分析...
👉 结论:join()是保证线程执行顺序的关键,面试常考“如何让线程A执行完再执行线程B”,答案就是B线程中调用A.join()!
四、线程让步:yield()方法(主动让出CPU)
Thread.yield() 让当前线程主动让出CPU资源,立即回到就绪状态,与其他就绪线程重新竞争CPU调度。
核心特点(与sleep()的区别)
| 方法 | 核心特点 | 锁资源 | 异常处理 |
|---|---|---|---|
| sleep() | 强制休眠指定时间,期间阻塞 | 不释放 | 需处理InterruptedException |
| yield() | 主动让出CPU,立即回到就绪状态 | 不释放 | 无需处理异常 |
注意点
- 让步是“自愿行为”,但无法保证其他线程一定能获得CPU;
- 让步后当前线程可能马上再次被CPU调度,比如只有一个就绪线程时;
- 适用于“不需要优先执行”的线程,比如后台低优先级任务。
五、线程优先级:setPriority()(提示CPU调度)
Java线程优先级是“提示”CPU优先调度的机制,优先级高的线程获取CPU的概率更大,但不保证绝对先执行(CPU调度具有随机性)。
优先级范围(面试常考)
- 最低优先级:
Thread.MIN_PRIORITY(1); - 默认优先级:
Thread.NORM_PRIORITY(5); - 最高优先级:
Thread.MAX_PRIORITY(10)。
🌰 实操案例:设置线程优先级
class PriorityTask implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() +
" 优先级:" + Thread.currentThread().getPriority() +
",执行第" + (i+1) + "次");
}
}
}
public class ThreadPriorityDemo {
public static void main(String[] args) {
PriorityTask task = new PriorityTask();
Thread lowPriorityThread = new Thread(task, "低优先级线程");
Thread highPriorityThread = new Thread(task, "高优先级线程");
// 设置优先级
lowPriorityThread.setPriority(Thread.MIN_PRIORITY); // 1
highPriorityThread.setPriority(Thread.MAX_PRIORITY); // 10
lowPriorityThread.start();
highPriorityThread.start();
}
}
执行说明
多数情况下,高优先级线程会先执行,但偶尔也会出现低优先级线程先执行的情况——这是CPU调度随机性导致的,因此不能依赖优先级控制线程执行顺序,仅能作为辅助优化。
六、线程中断:interrupt()方法(优雅停止线程)
线程中断是Java中“优雅停止线程”的标准方式,它不会强制终止线程,而是给线程设置一个“中断标志”,线程可根据该标志自行决定是否停止。
核心逻辑
- 调用
thread.interrupt():给线程设置中断标志为true; - 线程中通过
Thread.currentThread().isInterrupted()判断中断状态; - 若线程处于sleep()/join()等阻塞状态,调用interrupt()会触发
InterruptedException,同时清除中断标志。
🌰 实操案例:中断休眠中的线程
class InterruptTask implements Runnable {
@Override
public void run() {
try {
int i = 0;
while (true) {
System.out.println("线程执行中,次数:" + (++i));
Thread.sleep(200); // 休眠状态可被中断
// 检查中断标志,决定是否停止
if (Thread.currentThread().isInterrupted()) {
System.out.println("检测到中断标志,准备停止线程");
break;
}
}
} catch (InterruptedException e) {
System.out.println("休眠被中断,线程准备停止");
// 此处可处理中断后的资源释放逻辑
}
System.out.println("线程已停止执行");
}
}
public class ThreadInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread taskThread = new Thread(new InterruptTask(), "测试中断线程");
taskThread.start();
// 主线程3秒后发起中断请求
Thread.sleep(3000);
System.out.println("主线程发起中断请求");
taskThread.interrupt();
}
}
执行结果
线程执行中,次数:1
线程执行中,次数:2
...(省略中间输出)
线程执行中,次数:15
主线程发起中断请求
休眠被中断,线程准备停止
线程已停止执行
👉 避坑:不要用stop()方法停止线程(已过时),会强制终止线程导致资源泄漏,interrupt()是优雅中断的首选!
七、今日作业+明日预告(进阶福利)
📝 课后挑战
用join()和sleep()结合,实现3个线程按“线程A→线程B→线程C”的顺序依次执行,要求每个线程执行完后,下一个线程才启动。
👉 GZH【咖啡 Java 研习班】,回复「学习资料」领取完整代码和多种实现方案。
📌 明日预告
当多个线程同时操作ArrayList、文件等共享资源时,会出现数据错乱、文件写入异常——这就是线程安全问题!明天我们拆解线程安全的本质,用synchronized解决共享资源竞争,结合集合、IO实战演练,还会揭秘面试常问的“ArrayList为什么线程不安全”!
最后说两句
线程控制是多线程开发的核心,也是面试高频考点,今天6大方法+实操案例覆盖了“启动、休眠、插队、让步、优先级、中断”的全场景。建议大家动手敲一遍代码,感受线程执行节奏的变化,尤其是start()和run()、sleep()和yield()的区别,记牢这些面试直接拿分!
如果觉得文章有用,欢迎点赞+收藏+转发,你的支持是我持续输出的动力~ 评论区留言你的作业完成情况,或者遇到的问题,一起交流进步!
GZH【咖啡 Java 研习班】,持续分享Java并发、JVM、SpringBoot等核心技术,助力你从初级工程师进阶到架构师~