第21篇:Java多线程基础:线程创建与生命周期,入门并发编程

专栏:Java基础进阶·并发编程系列

很多Java初学者在学习并发编程时,都会卡在线程创建、生命周期、线程方法这几个基础卡点上。分不清start()和run()的区别、搞不懂线程五种状态如何切换、不会区分三种线程创建方式的适用场景,是新手最常见的问题。

本篇文章从零带大家吃透Java多线程核心基础,涵盖线程概念、三种创建方式、完整生命周期、常用API、状态流转逻辑,附带实战代码+误区解析,看完彻底入门Java并发基础,适配日常开发、面试笔试场景。

文章干货满满,建议收藏反复学习!

一、线程的定义与核心作用

1.1 进程与线程的区别

想要理解线程,首先要分清进程线程

  • 进程:操作系统资源分配的最小单位,独立占用内存、CPU资源,进程之间相互隔离、通信成本高。例如运行的IDEA、浏览器、微信都是独立进程。

  • 线程 :进程内的执行单元,是CPU调度的最小单位,也被称为轻量级进程。一个进程包含多个线程,线程共享进程资源,切换成本极低。

1.2 多线程的核心作用

单线程程序只能串行执行任务,效率极低;多线程可以让程序并行/并发执行多个任务,核心优势如下:

  1. 提升资源利用率:充分利用多核CPU,避免CPU空闲,压榨硬件性能;

  2. 提高程序响应速度:异步执行耗时任务(文件读写、网络请求),避免主线程阻塞卡顿;

  3. 任务拆分解耦:将复杂业务拆分为多个独立子任务,通过多线程并行执行,简化业务逻辑。

核心前提:Java程序默认存在主线程(main线程),所有代码默认由主线程执行,多线程就是手动创建子线程分担任务。

二、Java线程的三种创建方式(实战代码)

Java官方提供三种主流线程创建方式,分别是:继承Thread类、实现Runnable接口、实现Callable接口。三种方式特性、适用场景各不相同,下面逐一拆解并附可直接运行的实战代码。

2.1 方式一:继承Thread类(基础入门)

核心原理

自定义类继承Thread类,重写run()方法(线程执行体),创建对象后调用start()启动线程。

实战代码
java 复制代码
// 1.自定义线程类,继承Thread
public class ThreadDemo extends Thread {
    // 重写run方法:线程执行的核心逻辑
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Thread线程执行:" + i);
        }
    }

    public static void main(String[] args) {
        // 2.创建线程对象
        ThreadDemo threadDemo = new ThreadDemo();
        // 3.启动线程(核心!不是调用run方法)
        threadDemo.start();
    }
}
优缺点

✅ 优点:写法简单、入门友好; ❌ 缺点:Java单继承机制,继承Thread后无法再继承其他类,扩展性差,不推荐项目使用。

2.2 方式二:实现Runnable接口(项目常用)

核心原理

实现Runnable接口,重写run()方法,将任务对象传入Thread构造器,通过Thread对象启动线程。规避了单继承限制,支持线程任务复用。

实战代码
java 复制代码
// 1.自定义任务类,实现Runnable接口
public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Runnable线程执行:" + i);
        }
    }

    public static void main(String[] args) {
        // 2.创建任务对象(可复用)
        RunnableDemo runnableDemo = new RunnableDemo();
        // 3.传入Thread,启动线程
        new Thread(runnableDemo).start();
    }
}
优缺点

✅ 优点:接口实现,不占用继承权限、任务与线程解耦、支持多线程共享任务; ❌ 缺点:无返回值、无法抛出异常

2.3 方式三:实现Callable接口(带返回值)

核心原理

JDK5新增特性,解决Runnable无返回值、不能抛异常的问题。配合FutureTask包装任务,可获取线程执行结果。

实战代码
java 复制代码
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

// 1.实现Callable接口,指定返回值类型
public class CallableDemo implements Callable<Integer> {
    @Override
    // 支持抛出异常、有返回值
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 5; i++) {
            sum += i;
            System.out.println("Callable线程计算:" + i);
        }
        return sum;
    }

    public static void main(String[] args) throws Exception {
        // 2.创建Callable任务
        CallableDemo callableDemo = new CallableDemo();
        // 3.FutureTask包装任务
        FutureTask<Integer> futureTask = new FutureTask<>(callableDemo);
        // 4.启动线程
        new Thread(futureTask).start();
        // 5.获取线程执行返回值(会阻塞主线程,直到子线程执行完毕)
        Integer result = futureTask.get();
        System.out.println("线程执行结果总和:" + result);
    }
}
优缺点

✅ 优点:有返回值、可抛出编译异常、扩展性强; ❌ 缺点:写法复杂,get()方法会阻塞主线程。

2.4 三种创建方式核心对比

创建方式 返回值 异常处理 扩展性 适用场景
继承Thread类 仅运行时异常 差(单继承限制) 简单测试、入门练习
实现Runnable 仅运行时异常 优(接口解耦) 常规异步任务、无返回值场景
实现Callable 可抛编译异常 需要获取线程执行结果的场景

三、线程完整生命周期(5大状态)

Java线程从创建到销毁,固定经历5种核心状态 ,JDK源码中由Thread.State枚举定义,所有线程状态切换都遵循固定规则,也是面试高频考点。

3.1 五大状态详解

1. 新建状态(NEW)

线程对象通过new创建完成,但未调用start()方法。此时仅仅是初始化了Java对象,操作系统内核线程未创建,无任何执行资源。

2. 就绪状态(RUNNABLE)

调用start()方法后,线程进入就绪状态。JVM创建内核线程、分配资源,等待CPU调度执行。 注意:就绪状态包含「可运行」和「正在运行」两个细分状态,由操作系统CPU调度切换。

3. 运行状态(RUNNING)

CPU调度选中就绪线程,执行run()/call()方法中的业务逻辑,线程真正执行任务。

4. 阻塞状态(BLOCKED/WAITING/TIMED_WAITING)

线程主动放弃CPU执行权,暂停执行,分为三种细分阻塞状态:

  • BLOCKED:等待获取synchronized同步锁,锁释放后自动进入就绪状态;

  • WAITING:无限等待,调用wait()、join()触发,需其他线程唤醒;

  • TIMED_WAITING:限时等待,调用sleep(time)、wait(time)触发,时间到自动唤醒。

5. 终止状态(TERMINATED)

线程任务执行完毕、正常结束,或异常终止、被终止。线程生命周期结束,终止后无法再次启动

3.2 线程完整状态流转图

NEW → start() → RUNNABLE → CPU调度 → RUNNING RUNNING遇阻塞条件 → BLOCKED/WAITING/TIMED_WAITING 阻塞解除 → RUNNABLE → 执行完毕 → TERMINATED

四、线程常用核心方法详解

掌握线程常用API,是控制线程执行逻辑、实现并发协作的基础,下面详解高频核心方法及使用场景。

4.1 start() 方法

作用:启动线程,由JVM调用底层方法创建内核线程 ,进入就绪状态,等待CPU调度。 核心特点:一个线程只能调用一次start(),重复调用抛出IllegalThreadStateException异常。

4.2 run() 方法

作用:线程的任务执行体 ,仅封装业务逻辑。 核心特点:直接调用run()只是普通方法调用,不会创建新线程,全程由主线程串行执行。

4.3 sleep(long time) 方法

作用:让当前线程限时休眠,进入TIMED_WAITING状态,休眠结束后回归就绪状态。 核心特点:不释放锁资源、仅暂停执行、必须捕获异常。

4.4 join() 方法

作用:线程插队,主线程等待子线程执行完毕后,再继续执行,实现线程执行顺序控制。 场景:需要等待异步任务结果后再执行后续逻辑。

4.5 yield() 方法

作用:线程礼让,当前运行线程主动让出CPU执行权,回到就绪状态。 特点:礼让不保证生效,CPU可能再次调度当前线程。

4.6 wait() / notify() / notifyAll()

作用:线程等待与唤醒,用于线程间通信。 核心特点:必须在同步代码块中使用,会主动释放锁资源,区别于sleep()。

五、新手高频误区深度解析(重点)

误区1:start()和run()方法没有区别

这是新手最容易踩的坑,二者本质完全不同

核心区别
  1. start():真正启动线程,底层调用JVM方法创建操作系统内核线程,线程进入就绪状态,是异步执行;

  2. run():只是普通成员方法,直接调用不会创建新线程,代码由主线程串行执行,无并发效果。

验证代码
java 复制代码
public class StartRunDiffDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println("子线程执行"));
        // 调用run:主线程执行,无新线程
        thread.run();
        // 调用start:启动新线程,异步执行
        // thread.start();
        System.out.println("主线程执行");
    }
}

误区2:线程休眠、等待属于阻塞状态(BLOCKED)

很多新手混淆线程阻塞状态: ✅ BLOCKED :仅针对synchronized锁等待 ; ✅ sleep、wait、join触发的是WAITING/TIMED_WAITING状态,不属于BLOCKED。

误区3:线程执行完毕后,可以重新start()启动

线程进入TERMINATED终止状态后,生命周期彻底结束,再次调用start()会直接抛出异常,线程无法复用重启。

误区4:sleep()会释放锁资源

严格区分: ❌ sleep() :休眠不释放锁,持有锁阻塞,其他线程无法获取锁; ✅ wait():等待会主动释放锁,其他线程可竞争锁执行任务。

六、总结与学习拓展

本文核心总结

  1. 线程是CPU调度最小单位,多线程核心作用是提升程序并发执行效率;

  2. 三种线程创建方式:Thread(简单不灵活)、Runnable(常用无返回值)、Callable(带返回值);

  3. 线程五大生命周期:NEW→RUNNABLE→RUNNING→BLOCKED/WAITING→TERMINATED;

  4. 核心方法牢记:start启线程、run执逻辑、sleep休眠、join插队、wait释放锁;

  5. 重点规避start/run区别、锁释放、线程复用等新手误区。