Java并发编程:线程的创建和运行

一、线程基础概念

1.1 什么是进程?

进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位 。简单来说,当我们在Java中启动main函数时,实际上就启动了一个JVM进程。

1.2 什么是线程?

线程是进程中的一个执行路径,线程本身不会独立存在。一个进程中至少有一个线程,进程中的多个线程共享进程的资源。

核心区别 :操作系统分配资源时是把资源分配给进程的,但CPU资源比较特殊,它是分配给线程的。因为真正占用CPU运行的是线程,所以线程也被称为CPU分配的基本单位

1.3 线程的私有与共享资源

资源类型 归属 说明
线程共享 进程中最大的一块内存,存放new创建的对象实例
方法区 线程共享 存放JVM加载的类、常量及静态变量等信息
程序计数器 线程私有 记录线程当前要执行的指令地址
虚拟机栈 线程私有 存储线程的局部变量、调用栈帧

为什么程序计数器是线程私有的?

CPU采用时间片轮转方式让线程轮询占用。当线程的时间片用完后,需要让出CPU,程序计数器就是用来记录线程让出CPU时的执行地址,待下次分配到时间片时,线程可以从私有计数器指定的地址继续执行。

注意 :执行native方法时,程序计数器记录的是undefined地址;只有执行Java代码时,程序计数器记录的才是下一条指令的地址。

1.4 线程的生命周期

线程从创建到销毁会经历以下状态:

  • 新建(New):线程对象被创建

  • 就绪(Runnable) :调用start()后,已获取除CPU外的所有资源

  • 运行(Running):获取CPU资源,正在执行

  • 阻塞(Blocked):等待某些资源或锁

  • 终止(Terminated)run()方法执行完毕


二、线程的三种创建方式

方式一:继承Thread类

java 复制代码
public class ThreadTest {
    
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("I am a child thread");
        }
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();  // 启动线程,注意不是调用run()
    }
}

使用细节

  • 创建Thread对象后,线程并未启动,直到调用start()方法

  • start()后线程处于就绪状态,等待CPU资源

  • run()方法执行完毕,线程进入终止状态

优点

  • run()方法内获取当前线程直接使用this即可,无需Thread.currentThread()

缺点

  • Java不支持多继承,继承了Thread类就不能再继承其他类

  • 任务与代码没有分离,多个线程执行相同任务时需要多份任务代码


方式二:实现Runnable接口

java 复制代码
public class ThreadTest1 {

    public static class RunableTask implements Runnable {
        @Override
        public void run() {
            System.out.println("I am a child thread");
        }
    }

    public static void main(String[] args) {
        RunableTask task = new RunableTask();
        new Thread(task).start();
        new Thread(task).start();  // 多个线程共享同一个task
    }
}

优点

  • 解决单继承的限制,RunnableTask还可以继承其他类

  • 任务与代码分离,多个线程可以共享同一个任务代码

  • 可以通过给RunnableTask添加参数进行任务区分

缺点

  • 任务没有返回值

方式三:实现Callable接口 + FutureTask(有返回值)

java 复制代码
public class ThreadTest2 {

    public static class CallerTask implements Callable<String> {
        @Override
        public String call() throws Exception {
            Thread.sleep(5000);
            return "hello";
        }
    }

    public static void main(String[] args) {
        // 创建异步任务
        FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
        
        // 启动线程
        new Thread(futureTask).start();
        
        try {
            // 等待任务执行完毕,并返回结果(阻塞)
            String result = futureTask.get();
            System.out.println(result);
        } catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

特点

  • 实现Callable接口的call()方法

  • 通过FutureTask包装任务,可获取返回值

  • futureTask.get()阻塞等待任务执行完毕


三、三种方式对比总结

对比维度 继承Thread 实现Runnable 实现Callable
是否支持多继承
任务与代码分离
是否有返回值
获取当前线程 直接使用this Thread.currentThread() Thread.currentThread()
适用场景 简单任务,不需要返回值 需要共享任务代码 需要获取执行结果

四、最佳实践建议

  1. 优先使用实现接口的方式RunnableCallable接口方式更加灵活,避免了单继承的限制

  2. 需要返回值时选择Callable :如果线程执行后需要返回结果,使用FutureTask+Callable

  3. 多个线程共享任务代码 :使用Runnable方式,多个线程可以共用一个任务实例

  4. 线程启动不要直接调用run() :调用run()只是普通方法调用,不会启动新线程;必须调用start()才能启动新线程

相关推荐
狼爷10 小时前
吃透 Java Function 接口,搞定 99% 的 Stream 场景
java·函数式编程
祎雪双十Gy14 小时前
从 DataX 的配置加载说起:我用 FastJson2 做了一个轻量级动态配置管理库
java·后端
小锋java123415 小时前
分享一套锋哥原创的SpringBoot4+Vue3宠物领养网站系统
java
考虑考虑18 小时前
Java实现hmacsha1加密算法
java·后端·java ee
掉鱼的猫19 小时前
Spring Boot → Solon 注解迁移实战指南:一张对照表说清楚
java·spring boot
plainGeekDev19 小时前
广播接收器 → Flow + Lifecycle
android·java·kotlin
plainGeekDev19 小时前
EventBus → SharedFlow
android·java·kotlin
带刺的坐椅19 小时前
Spring Boot → Solon 注解迁移实战指南:一张对照表说清楚
java·springboot·web·solon
用户37215742613519 小时前
Java 将一个 PPT 文档拆分为多个文件
java
人活一口气1 天前
Spring Boot与AIGC的完美结合:从零搭建智能内容生成平台
java·spring boot·aigc