🔥 Java多线程实战:3种线程创建方式+IO/集合手把手实操(面试高频考点)

126 阅读8分钟

🔥 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技术交流群,领取:

  1. 多线程完整学习路线图(从入门到精通)
  2. 线程池+并发工具类实战源码
  3. 2024最新Java面试高频题(多线程专题)

群内有资深架构师答疑,定期分享技术干货,一起攻克Java并发难点!

最后说两句

多线程是Java开发的核心竞争力,而线程创建是基础中的基础。本文3个实操案例覆盖了集合、IO、异常处理等知识点,代码可直接复制到IDE运行,建议大家动手敲一遍,感受不同方式的差异。

如果觉得文章有用,欢迎点赞+收藏+转发,你的支持是我持续输出的动力~ 评论区留言你的作业完成情况,或者遇到的问题,一起交流进步!

关注【咖啡 Java 研习班】,持续分享Java并发、JVM、SpringBoot等核心技术,助力你从初级工程师进阶到架构师~