🔥 Java多线程实战:3种线程创建方式+IO/集合手把手实操(面试高频考点)
作为Java开发者,多线程是面试必问、工作常用的核心技能!很多新手卡在“理论懂了,实操不会”,今天就带大家从0到1掌握3种线程创建方式,结合IO流、集合、异常处理做实战练习,代码可直接复制运行,看完就能上手开发~
前言:为什么要学好线程创建?
多线程是提升程序效率的关键(比如并发读取文件、批量处理数据),而“创建线程”是多线程开发的第一步。Java中3种线程创建方式各有适配场景,面试常问“不同方式的区别”“什么时候用哪种”,本文不仅讲清楚原理,还带大家落地实操,彻底摆脱“理论派”!
关注GZH【咖啡Java研习班】,回复「学习资料」领取完整源码、学习路线图和面试题库,还有技术交流群随时答疑~
一、方式1:继承Thread类(入门必学,最基础实现)
这是最适合新手入门的线程创建方式,核心逻辑:继承Thread类 → 重写run()方法(线程执行体)→ 调用start()方法启动线程。
🌰 实操案例:多线程遍历图书集合
结合ArrayList集合+异常处理,模拟多线程并发遍历图书信息,避免空指针和线程中断异常:
import java.util.ArrayList;
import java.util.List;
// 1. 继承Thread类
class BookThread extends Thread {
// 存储图书数据的集合(衔接集合知识)
private List<String> bookList;
// 构造方法传入集合
public BookThread(List<String> bookList) {
this.bookList = bookList;
}
// 2. 重写run()方法,定义线程执行逻辑
@Override
public void run() {
try {
// 遍历集合(增强for循环简化代码)
for (String book : bookList) {
Thread.sleep(500); // 模拟业务处理延迟(衔接线程阻塞知识)
System.out.println("当前线程:" + Thread.currentThread().getName() + ",遍历图书:" + book);
}
} catch (InterruptedException e) {
// 线程中断异常处理
e.printStackTrace();
} catch (NullPointerException e) {
System.out.println("集合为空,无法遍历");
e.printStackTrace();
}
}
}
public class ThreadCreateDemo1 {
public static void main(String[] args) {
// 初始化图书集合
List<String> bookList = new ArrayList<>();
bookList.add("Java核心技术");
bookList.add("并发编程实战");
bookList.add("SpringBoot实战");
// 3. 创建线程对象(新建状态)
BookThread thread1 = new BookThread(bookList);
BookThread thread2 = new BookThread(bookList);
// 设置线程名称(便于调试)
thread1.setName("图书遍历线程1");
thread2.setName("图书遍历线程2");
// 4. 启动线程(进入就绪状态,等待CPU调度)
thread1.start();
thread2.start();
}
}
✅ 优缺点分析
- 优点:实现简单,一行继承+重写run()即可,适合快速验证逻辑;
- 缺点:Java单继承机制限制,继承Thread后无法再继承其他类;线程与任务耦合,一个线程只能绑定一个任务。
- 适用场景:入门练习、简单单任务场景(比如单独处理一个循环逻辑)。
二、方式2:实现Runnable接口(开发首选,灵活无限制)
为了解决单继承的痛点,Runnable接口应运而生!核心逻辑:实现Runnable接口 → 重写run()方法 → 把任务传入Thread构造器启动线程。线程与任务解耦,一个任务可被多个线程复用,是实际开发的首选方案。
🌰 实操案例:多线程并发读取文件
结合IO流知识,用两个线程同时读取文本文件,模拟实际开发中的并发IO场景,记得处理流关闭和读取异常:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
// 1. 实现Runnable接口
class FileReadRunnable implements Runnable {
// 要读取的文件路径(外部传入,灵活复用)
private String filePath;
public FileReadRunnable(String filePath) {
this.filePath = filePath;
}
// 2. 重写run()方法,定义文件读取逻辑
@Override
public void run() {
BufferedReader br = null; // 缓冲字符流,提升读取效率
try {
br = new BufferedReader(new FileReader(filePath));
String line;
// 逐行读取文件内容
while ((line = br.readLine()) != null) {
System.out.println("线程" + Thread.currentThread().getName() + "读取内容:" + line);
Thread.sleep(300); // 模拟业务处理延迟
}
} catch (IOException e) {
System.out.println("文件读取失败,路径:" + filePath);
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 必须关闭流资源,避免内存泄漏
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class ThreadCreateDemo2 {
public static void main(String[] args) {
// 3. 创建任务对象(线程与任务分离,可复用)
FileReadRunnable readTask = new FileReadRunnable("test.txt");
// 4. 多个线程共享同一个任务
Thread threadA = new Thread(readTask, "文件读取线程A");
Thread threadB = new Thread(readTask, "文件读取线程B");
// 5. 启动线程
threadA.start();
threadB.start();
}
}
✅ 优缺点分析
- 优点:规避单继承限制,可同时实现多个接口;线程与任务分离,一个任务可被多个线程复用,资源利用率高;
- 缺点:run()方法无返回值,无法直接获取线程执行结果;不能向上抛出受检异常,需在方法内部捕获。
- 适用场景:常规业务开发、多线程共享任务(比如并发处理同一个文件/数据库)。
三、方式3:实现Callable接口(进阶方案,带返回值)
如果需要获取线程执行结果(比如多线程计算后汇总数据),前两种方式就不够用了!Callable接口是进阶方案,核心逻辑:实现Callable接口(指定返回值类型)→ 重写call()方法(带返回值+可抛异常)→ 用FutureTask包装任务 → 启动线程并获取结果。
🌰 实操案例:多线程统计集合奇偶个数
用两个线程分别统计集合中的奇数和偶数数量,最后汇总结果,结合异常处理和线程休眠模拟真实场景:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 1. 实现Callable接口,指定返回值类型为Integer
class NumCountCallable implements Callable<Integer> {
private List<Integer> numList; // 待统计的数字集合
private String type; // 统计类型:odd(奇数)、even(偶数)
public NumCountCallable(List<Integer> numList, String type) {
this.numList = numList;
this.type = type;
}
// 2. 重写call()方法,带返回值且可抛异常
@Override
public Integer call() throws Exception {
int count = 0;
for (Integer num : numList) {
if ("odd".equals(type)) {
if (num % 2 != 0) count++;
} else if ("even".equals(type)) {
if (num % 2 == 0) count++;
}
Thread.sleep(100); // 模拟计算延迟
}
return count; // 返回统计结果
}
}
public class ThreadCreateDemo3 {
public static void main(String[] args) {
// 初始化数字集合(1-20)
List<Integer> numList = new ArrayList<>();
for (int i = 1; i <= 20; i++) {
numList.add(i);
}
// 3. 创建Callable任务(分别统计奇数、偶数)
NumCountCallable oddTask = new NumCountCallable(numList, "odd");
NumCountCallable evenTask = new NumCountCallable(numList, "even");
// 4. 用FutureTask包装Callable任务(用于获取返回值)
FutureTask<Integer> oddFuture = new FutureTask<>(oddTask);
FutureTask<Integer> evenFuture = new FutureTask<>(evenTask);
// 5. 创建线程并启动
new Thread(oddFuture, "奇数统计线程").start();
new Thread(evenFuture, "偶数统计线程").start();
// 6. 获取线程执行结果(get()方法会阻塞,直到线程执行完成)
try {
int oddCount = oddFuture.get();
int evenCount = evenFuture.get();
System.out.println("集合中奇数数量:" + oddCount);
System.out.println("集合中偶数数量:" + evenCount);
System.out.println("总数校验:" + (oddCount + evenCount == numList.size() ? "成功" : "失败"));
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
✅ 优缺点分析
- 优点:支持返回值,可直接获取线程执行结果;call()方法可向上抛出异常,便于统一异常处理;
- 缺点:实现步骤稍复杂,需配合FutureTask使用;get()方法会阻塞主线程,需合理控制调用时机(比如用线程池优化)。
- 适用场景:需获取执行结果的复杂任务(比如多线程计算、数据统计、异步回调)。
四、3种方式核心对比(面试必考)
为了方便大家快速选型,整理了面试高频考点对比表,建议收藏备用:
| 创建方式 | 核心特点 | 返回值 | 异常处理 | 继承限制 | 推荐场景 |
|---|---|---|---|---|---|
| 继承Thread | 实现简单,线程与任务耦合 | 无 | 需内部捕获 | 单继承限制 | 入门练习、简单单任务 |
| 实现Runnable | 线程任务解耦,可复用 | 无 | 需内部捕获 | 无限制 | 常规业务开发、多线程共享任务 |
| 实现Callable | 支持返回值,可抛异常 | 有 | 可向上抛出 | 无限制 | 需获取执行结果的复杂任务(异步) |
五、今日作业+明日预告(进阶福利)
📝 课后作业
用Runnable接口实现多线程写入文件功能:将集合中的学生信息(姓名+学号)同时写入两个不同的txt文件,要求处理IO异常、关闭流资源,线程执行时打印“线程X正在写入文件Y”。
👉 作业答案获取:关注GZH【咖啡Java研习班】,回复「学习资料」领取完整代码和优化方案。
📌 明日预告
线程创建后,如何控制执行顺序?比如:
- start()和run()的本质区别(面试必问!)
- 线程“插队”(join())、休眠(sleep())、设置优先级
- 线程中断的正确姿势(避免踩坑)
关注公众号不迷路,明天带大家吃透线程操控核心方法,还会分享多线程面试真题解析~
🎁 福利时间
扫码加入Java技术交流群,领取:
- 多线程完整学习路线图(从入门到精通)
- 线程池+并发工具类实战源码
- 2024最新Java面试高频题(多线程专题)
群内有资深架构师答疑,定期分享技术干货,一起攻克Java并发难点!
最后说两句
多线程是Java开发的核心竞争力,而线程创建是基础中的基础。本文3个实操案例覆盖了集合、IO、异常处理等知识点,代码可直接复制到IDE运行,建议大家动手敲一遍,感受不同方式的差异。
如果觉得文章有用,欢迎点赞+收藏+转发,你的支持是我持续输出的动力~ 评论区留言你的作业完成情况,或者遇到的问题,一起交流进步!
关注【咖啡 Java 研习班】,持续分享Java并发、JVM、SpringBoot等核心技术,助力你从初级工程师进阶到架构师~