找回密码
立即注册
搜索
发回帖 发新帖

6328

积分

0

好友

800

主题
发表于 昨天 23:58 | 查看: 8| 回复: 0

字数 1914,阅读大约需 10 分钟

概述

在介绍多线程之前,我们先讲清楚程序、进程和线程这三个概念:

  • 程序(program):正在运行的一段代码,是静态的;
  • 进程(process):程序的一次运行实例,是操作系统分配资源的基本单位。我们启动一个 Java 程序(也就是执行 main 方法)时,操作系统就会分配一个独立的内存空间给它,这个进程内部跑的就是 JVM;
  • 线程(thread):进程内部的执行流,是 CPU 调度和分配的基本单位。

进程和线程的关系:一个进程可以包含多个线程。同一个进程里的所有线程共享进程的堆和方法区,但每个线程都有自己的程序计数器、虚拟机栈和本地方法栈——这也是为什么线程切换的开销远小于进程,线程因此又被称为轻量级进程。多线程的好处,一是让程序能同时处理多项事件,二是充分利用空闲CPU资源。

例如最典型的 Web 服务场景:一个线程处理网络请求时,往往会穿插数据库查询等 IO 操作。IO 等待期间 CPU 其实闲着——单线程模型下,这个线程只能挂起干等 IO 返回,CPU很空闲;多线程模型下,CPU 把执行权让给其他就绪线程继续干活,等到 IO 返回了再切回来。这就把"等待"变成了"干活",并发吞吐自然上去了。

创建和启动线程

Java 创建线程一共有4 种方式,按演进顺序演示,但实际开发中最常用的是最后那种:

  1. 继承 Thread 类:最原始的方式,把任务逻辑写在 run() 里,再 new 出来、start()。缺点是 Java 单继承,一个类继承了 Thread 就不能再继承别的类了。下面的例子能观察到 tA 线程整个生命周期的状态。
...
public static void main(String[] args) {
        System.out.println("main开始运行");
        TaskThread tA = new TaskThread("任务A");
        TaskThread tB = new TaskThread("任务B");

        System.out.println("任务A状态:" + tA.getState());

        tA.start();
        tB.start();

        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(300);
                System.out.println("任务A状态" + tA.getState());
                System.out.println("任务B状态" + tB.getState());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        try {
            tA.join();
            tB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main所有子线程执行完毕,tA状态:" + tA.getState()); 
              // 输出TERMINATED
        System.out.println("main程序退出");
    }

...
class TaskThread extends Thread {
    public TaskThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        Thread current = Thread.currentThread();
        System.out.println(current.getName() + "开始运行,状态:" + current.getState());

        for (int i = 1; i <= 5; i++) {
            System.out.println(current.getName() + "进度:" + (i * 20) + "%");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(current.getName() + "执行完毕");
    }
}

/*
任务A状态:NEW
任务B开始运行,状态:RUNNABLE
任务A开始运行,状态:RUNNABLE
任务A进度:20%
任务B进度:20%
任务A状态TIMED_WAITING
任务B状态TIMED_WAITING
任务B进度:40%
任务A进度:40%
任务A状态TIMED_WAITING
任务B状态TIMED_WAITING
任务A状态TIMED_WAITING
任务B状态TIMED_WAITING
任务A进度:60%
任务B进度:60%
任务A进度:80%
任务B进度:80%
任务B进度:100%
任务A进度:100%
任务B执行完毕
任务A执行完毕
main所有子线程执行完毕,tA状态:TERMINATED
main程序退出
*/
  1. 实现 Runnable 接口:把任务逻辑放进 run(),再交给 new Thread(task) 包装一下。因为接口不受"单继承"限制、逻辑和线程分离可以复用。
...   
public static void main(String[] args) {
        System.out.println("main开始运行");
        var taskA = new Task("TaskA");
        var taskB = new Task("TaskB");

        var tA = new Thread(taskA, "ThreadA");
        var tB = new Thread(taskB, "ThreadB");

        System.out.println("tA状态:" + tA.getState());

        tA.start();
        tB.start();

        for (int i = 0; i < 4; i++) {
            try {
                Thread.sleep(300);
                System.out.println("threadA状态:" + tA.getState());
                System.out.println("threadB状态:" + tB.getState());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        try {
            tA.join();
            tB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("全部完成,tA:状态:" + tA.getState());

    }
...

class Task implements Runnable {
    private final String taskName;

    public Task(String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        Thread currentThread = Thread.currentThread();
        System.out.println("开始执行:" + currentThread.getName() + "状态" + currentThread.getState());
        for (int i = 1; i <= 5; i++) {
            System.out.println(currentThread.getName() + "任务" + taskName + " 进度:" + (i * 20) + "%");

            try {
                Thread.sleep(400);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(currentThread.getName() + "执行完毕");
    }
}

/*
main开始运行
tA状态:NEW
开始执行:ThreadB状态RUNNABLE
开始执行:ThreadA状态RUNNABLE
ThreadA任务TaskA 进度:20%
ThreadB任务TaskB 进度:20%
tA状态:TIMED_WAITING
tB状态:TIMED_WAITING
ThreadA任务TaskA 进度:40%
ThreadB任务TaskB 进度:40%
tA状态:TIMED_WAITING
tB状态:TIMED_WAITING
ThreadA任务TaskA 进度:60%
ThreadB任务TaskB 进度:60%
tA状态:TIMED_WAITING
tB状态:TIMED_WAITING
ThreadB任务TaskB 进度:80%
ThreadA任务TaskA 进度:80%
tA状态:RUNNABLE
tB状态:TIMED_WAITING
ThreadB任务TaskB 进度:100%
ThreadA任务TaskA 进度:100%
ThreadB执行完毕
ThreadA执行完毕
全部完成,tA:状态:TERMINATED
*/
  1. 实现 Callable 和 Future:前两种的 run() 没有返回值、也不能抛受检异常,Callable 正好补上了这两个短板——call() 有返回值且可以抛异常,配合 Future 就能拿到执行结果,也是带返回结果的方式。用的时候要把 Callable 包装成 FutureTask 再交给线程,最后调 futureTask.get() 阻塞取结果。
...   
public static void main(String[] args) {
        System.out.println("main开始运行");
        SumTask task = new SumTask(1, 100);

        FutureTask<Integer> futureTask = new FutureTask<>(task);
        Thread worker = new Thread(futureTask, "计算器线程");
        worker.start();

...

class SumTask implements Callable<Integer> {

    private final int start;
    private final int end;

    public SumTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public Integer call() throws Exception {
        Thread current = Thread.currentThread();
        System.out.println(current.getName());

        int sum = 0;
        for (int i = start; i <= end; i++) {
            sum += i;
            Thread.sleep(10);
        }

        System.out.println(current.getName() + "完成,结果为:" + sum);
        return sum;
    }
}
  1. 线程池(推荐):生产环境中主流的线程创建方式。前面三种都是"一个任务 new 一个线程",任务一多线程就会无限膨胀;而线程池通过复用已有线程来控制并发数量,顺带还提供了任务队列、拒绝策略、超时控制这些生产需要的功能。实际项目中直接用 JDK 自带的 Executors 工具类创建即可。
    public static void main(String[] args) {
        // 创建自定义线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2, //corePoolSize:核心线程数
                4, // maximumPoolSize:最大线程数
                60L, // keepAliveTime:空闲存活时间
                TimeUnit.SECONDS,                 // 时间单位
                new LinkedBlockingQueue<>(10),   // 有界队列,容量10
                Executors.defaultThreadFactory(),// 线程工厂(可用默认)
                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略(让调用者执行)
        );

        for (int i = 1; i <= 12; i++) {
            int taskId = i;
            pool.execute(() -> {
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在执行任务" + taskId);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println(name + "完成" + taskId);
            });

        }
        //关闭线程池
        pool.shutdown();
        System.out.println("main结束");
}
/*

pool-1-thread-1正在执行任务1
pool-1-thread-2正在执行任务2
pool-1-thread-1完成1
pool-1-thread-2完成2
pool-1-thread-1正在执行任务3
pool-1-thread-2正在执行任务4
pool-1-thread-2完成4
pool-1-thread-1完成3
pool-1-thread-1正在执行任务6
pool-1-thread-2正在执行任务5
pool-1-thread-2完成5
pool-1-thread-2正在执行任务7
pool-1-thread-1完成6
pool-1-thread-1正在执行任务8
pool-1-thread-2完成7
pool-1-thread-1完成8
pool-1-thread-2正在执行任务9
pool-1-thread-1正在执行任务10
pool-1-thread-1完成10
pool-1-thread-1正在执行任务11
pool-1-thread-2完成9
pool-1-thread-2正在执行任务12
pool-1-thread-1完成11
pool-1-thread-2完成12

*/

最后四种方式对比如下:

方式 有返回值 可抛受检异常 复用性 评价
继承 Thread ❌ ❌ ❌ 受单继承限制,不推荐
实现 Runnable ❌ ❌ ✅ 任务可复用 简单场景够用
实现 Callable+Future ✅ ✅ ✅ 能拿结果,注意 get() 会阻塞
线程池 ✅ ✅ ✅ 生产环境使用



上一篇:Spring Boot 自动配置原理:从条件装配到 Bean 退让机制
下一篇:树莓派4/5 2GB版官宣涨价12.5美元,内存短缺或持续到2027
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-3 08:19 , Processed in 0.069424 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表