(一)多线程开篇

描述:多线程是 Java 并发编程的核心,用于充分利用 CPU 资源、提升程序响应速度。在单线程环境中,程序只能串行执行任务,遇到 IO 操作(如网络请求、文件读写)时会阻塞等待;而多线程可在一个线程阻塞时,让其他线程继续执行,避免 CPU 空闲。

核心应用场景

  • 后台任务(如日志收集、数据备份)
  • 异步处理(如用户注册后发送短信 / 邮件)
  • 高并发服务(如电商秒杀、API 接口)

(二)线程和进程的区别

进程

描述:进程是操作系统资源分配的基本单位。当程序(如 Chrome 浏览器、IDEA)被运行时,系统会从磁盘加载程序代码到内存,为其分配独立的内存空间(代码段、数据段、堆栈段)、CPU 时间片、IO 资源等,此时就形成一个进程。

示例:打开 Windows 任务管理器,能看到 “微信.exe”“Chrome.exe” 等进程,每个进程对应一个独立运行的程序实例。

线程 

描述:线程是 CPU 调度的基本单位,隶属于进程。一个进程内可创建多个线程,所有线程共享进程的内存空间(如静态变量、堆内存),但拥有独立的栈内存(存储局部变量、方法调用栈)。每个线程对应一个 “指令流”,CPU 通过切换线程实现 “并发执行”。

示例:Chrome 浏览器的一个进程内,有 “页面渲染线程”“JS 执行线程”“网络请求线程” 等,多个线程协同完成浏览器功能。

线程与进程的区别
对比维度进程线程
资源分配操作系统独立分配内存 / IO共享所属进程的资源
调度单位操作系统不直接调度进程CPU 直接调度的基本单位
上下文切换成本高(需切换内存空间、IO 资源)低(仅切换栈和寄存器)
独立性独立运行,互不影响共享进程资源,相互依赖

(三)并行与并发的区别

单核 CPU(并发)

描述:单核 CPU 同一时间只能执行一个线程的指令。所谓 “并发”,是 CPU 通过 “时间片轮转” 机制,快速切换多个线程(如 1 秒内切换 1000 次),由于切换速度远超人类感知,造成 “多个线程同时运行” 的假象。示例:单核电脑上同时打开 “微信”“记事本”“浏览器”,CPU 会轮流给三个进程的线程分配时间片,每个线程执行几十毫秒后暂停,切换到下一个线程。

多核 CPU(并行 + 并发)

描述:多核 CPU(如 4 核、8 核)拥有多个独立的运算核心,可同时执行多个线程的指令(并行);同时,每个核心也会通过时间片轮转处理多个线程(并发)。示例:4 核 CPU 上运行 8 个线程,其中 4 个线程可在 4 个核心上 “并行执行”,剩余 4 个线程通过 “并发切换” 在核心上执行,整体效率远高于单核。

并行与并发的区别
概念核心逻辑依赖条件示例
并发时间片切换,“假同时”单核 / 多核 CPU单核电脑多任务运行
并行多核心同时执行,“真同时”必须多核 CPU4 核 CPU 同时处理 4 个请求

(四)创建线程的方式有哪些

创建线程的四种方式

1. 继承 Thread 类

核心逻辑:继承 Thread 类,重写run()方法(定义线程执行的任务),通过start()方法启动线程(底层调用 JVM 的start0()方法,分配 CPU 资源)。

代码示例

public class ThreadDemo extends Thread {
    // 重写run()方法,定义线程任务
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }

    public static void main(String[] args) {
        // 创建线程实例
        ThreadDemo thread1 = new ThreadDemo();
        ThreadDemo thread2 = new ThreadDemo();
        // 启动线程(必须调用start(),而非直接调用run())
        thread1.start(); // 线程1:输出0-4
        thread2.start(); // 线程2:输出0-4(与线程1交替执行)
    }
}

2. 实现 Runnable 接口

核心逻辑:实现Runnable接口的run()方法,将实例传入 Thread 构造器,通过 Thread 的start()启动线程。优点:避免单继承限制,可复用任务逻辑。

代码示例

public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }

    public static void main(String[] args) {
        // 创建任务实例
        RunnableDemo task = new RunnableDemo();
        // 传入Thread并启动
        new Thread(task, "线程A").start(); // 线程A:输出0-4
        new Thread(task, "线程B").start(); // 线程B:输出0-4
    }
}

3. 实现 Callable 接口(带返回值)

核心逻辑:实现Callable<V>接口的call()方法(可返回泛型结果、抛出异常),通过FutureTask包装任务,传入 Thread 启动;最终通过FutureTask.get()获取返回值(会阻塞直到任务完成)。代码示例

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CallableDemo implements Callable<Integer> {
    // 任务:计算1-100的和,返回结果
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }

    public static void main(String[] args) throws Exception {
        // 1. 包装Callable任务
        FutureTask<Integer> futureTask = new FutureTask<>(new CallableDemo());
        // 2. 启动线程
        new Thread(futureTask, "计算线程").start();
        // 3. 获取返回值(阻塞等待任务完成)
        Integer result = futureTask.get();
        System.out.println("1-100的和:" + result); // 输出5050
    }
}

4. 线程池创建线程(推荐)

核心逻辑:通过ThreadPoolExecutor创建线程池,复用线程资源(避免频繁创建 / 销毁线程的开销),通过execute()(无返回值)或submit()(带返回值)提交任务。

代码示例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 1. 创建线程池(核心参数:核心线程数、最大线程数、空闲时间、队列、拒绝策略)
        ExecutorService threadPool = new ThreadPoolExecutor(
                2, // 核心线程数:长期存活的线程
                4, // 最大线程数:核心线程满+队列满后,最多再创建的线程
                60, // 空闲时间:非核心线程空闲60秒后销毁
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(2), // 任务队列:核心线程满后,任务暂存队列
                new ThreadPoolExecutor.AbortPolicy() // 拒绝策略:队列满+最大线程满时,抛出异常
        );

        // 2. 提交5个任务(核心2 + 队列2 + 非核心2,刚好处理5个任务)
        for (int i = 1; i <= 5; i++) {
            int taskId = i;
            threadPool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + "处理任务:" + taskId);
            });
        }

        // 3. 关闭线程池(不再接受新任务,等待已提交任务完成)
        threadPool.shutdown();
    }
}
Runnable 和 Callable 的区别
对比维度RunnableCallable<V>
返回值无(run () 返回 void)有(call () 返回 V 类型)
异常处理需在 run () 内部捕获,不能抛出可在 call () 声明抛出异常
配合类ThreadFutureTask / 线程池
线程的run()和start()有什么区别
  • start():启动线程的唯一方法,会向 CPU 注册线程,等待时间片分配;一旦获得 CPU,会自动调用run()方法。只能调用一次(多次调用会抛IllegalThreadStateException)。
  • run():仅定义线程的任务逻辑,直接调用run()会以 “主线程串行执行” 的方式运行,不会启动新线程。可调用多次

(五)线程包括哪些状态,状态之间是如何变化的

线程的 6 种状态(Java 官方定义,基于Thread.State枚举)
状态名称含义触发场景
NEW(新建)线程已创建(如new Thread()),但未调用start()Thread thread = new Thread();
RUNNABLE(可运行)线程已启动,正在等待 CPU 时间片(就绪)或正在执行(运行)调用thread.start()
BLOCKED(阻塞)线程因竞争synchronized锁失败,进入阻塞状态线程 A 持有锁,线程 B 尝试获取同一锁时
WAITING(无限等待)线程无时间限制等待,需被其他线程唤醒调用Object.wait()Thread.join()LockSupport.park()(无超时)
TIMED_WAITING(计时等待)线程在指定时间内等待,超时后自动唤醒调用Thread.sleep(1000)Object.wait(1000)LockSupport.parkNanos()
TERMINATED(终止)线程任务执行完毕(run()返回)或因异常终止run()方法执行完成、线程抛出未捕获异常

状态转换流程

public class ThreadStateDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                // 1. RUNNABLE -> TIMED_WAITING(sleep 1秒)
                Thread.sleep(1000);
                // 2. 尝试获取锁(若被主线程持有,进入BLOCKED)
                synchronized (ThreadStateDemo.class) {
                    System.out.println("线程获得锁");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 3. run()执行完毕,进入TERMINATED
        }, "测试线程");

        // 初始状态:NEW
        System.out.println("1. 线程状态:" + thread.getState()); // NEW

        // 启动线程:NEW -> RUNNABLE
        thread.start();
        Thread.sleep(100); // 等待线程进入sleep
        System.out.println("2. 线程状态:" + thread.getState()); // TIMED_WAITING

        // 主线程持有锁,让测试线程进入BLOCKED
        synchronized (ThreadStateDemo.class) {
            Thread.sleep(2000); // 主线程持有锁2秒
        }

        // 等待线程执行完毕
        Thread.sleep(1000);
        System.out.println("3. 线程状态:" + thread.getState()); // TERMINATED
    }
}

(六)新建T1,T2,T3三个线程,如何保证他们按顺序执行

使用 Thread.join () 方法(核心:让当前线程等待目标线程执行完毕)

原理thread.join()会让调用方线程(如主线程)进入WAITING状态,直到thread执行完毕后,调用方线程才会继续执行。通过链式调用join(),可保证线程顺序。

代码示例

public class ThreadOrderDemo {
    public static void main(String[] args) throws InterruptedException {
        // 定义三个线程,需按 T1 -> T2 -> T3 顺序执行
        Thread t1 = new Thread(() -> System.out.println("T1执行"), "T1");
        Thread t2 = new Thread(() -> System.out.println("T2执行"), "T2");
        Thread t3 = new Thread(() -> System.out.println("T3执行"), "T3");

        // 启动T1,主线程等待T1完成后再启动T2
        t1.start();
        t1.join(); // 主线程等待T1终止

        // 启动T2,主线程等待T2完成后再启动T3
        t2.start();
        t2.join(); // 主线程等待T2终止

        // 启动T3
        t3.start();
        t3.join();

        System.out.println("所有线程执行完毕");
    }
}
// 输出结果(严格顺序):
// T1执行
// T2执行
// T3执行
// 所有线程执行完毕

notify () 和 notifyAll () 的区别(补充)

  • notify():随机唤醒一个处于WAITING状态的线程(等待同一锁的线程),被唤醒的线程需重新竞争锁。
  • notifyAll():唤醒所有处于WAITING状态的线程(等待同一锁的线程),所有被唤醒的线程共同竞争锁,只有一个能获得锁继续执行,其余进入BLOCKED状态。

代码示例(notifyAll ())

public class NotifyDemo {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        // 创建3个等待线程
        for (int i = 1; i <= 3; i++) {
            int threadId = i;
            new Thread(() -> {
                synchronized (lock) {
                    try {
                        System.out.println("线程" + threadId + "进入等待");
                        lock.wait(); // 进入WAITING状态
                        System.out.println("线程" + threadId + "被唤醒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }

        // 主线程等待3秒后,唤醒所有等待线程
        try {
            Thread.sleep(3000);
            synchronized (lock) {
                lock.notifyAll(); // 唤醒所有等待线程
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
// 输出结果:
// 线程1进入等待
// 线程2进入等待
// 线程3进入等待
// (3秒后)
// 线程1被唤醒(随机,实际顺序取决于锁竞争)
// 线程2被唤醒
// 线程3被唤醒

(七)在java中wait和sleep方法的不同

对比维度wait()sleep(long)
所属类Object 类(所有对象都有)Thread 类(静态方法)
锁释放会释放持有对象的锁不会释放任何锁
调用条件必须在synchronized代码块中调用可在任意地方调用
唤醒方式需其他线程调用notify()/notifyAll()超时自动唤醒,或interrupt()中断
状态转换RUNNABLE -> WAITING/TIMED_WAITINGRUNNABLE -> TIMED_WAITING

代码示例(wait 释放锁,sleep 不释放锁)

public class WaitSleepDemo {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        // 线程A:使用wait()
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程A持有锁,调用wait()");
                try {
                    lock.wait(2000); // 释放锁,进入TIMED_WAITING
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程A重新获得锁,执行完毕");
            }
        }, "A").start();

        // 线程B:使用sleep()
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程B持有锁,调用sleep()");
                try {
                    Thread.sleep(2000); // 不释放锁,进入

(八)如何停止一个正在运行的线程

停止线程的三种方式

1.使用退出标志,是线程正常退出,也就是当run方法完成后线程终止

2.使用stop方法强行终止(不推荐,方法已经作废)

3.使用interrupt方法中断线程

(1)打断阻塞的线程(sleep,wait,join)的线程,线程会抛出interruptedException异常

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐