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()才能启动新线程

相关推荐
写代码写到手抽筋1 小时前
5G上行DCI字段判定:端口 流数 PMI选择详解
java·算法·5g
xieliyu.1 小时前
Java算法精讲:双指针(二)
java·开发语言·算法
jeffer_liu2 小时前
Spring AI 生产级实战:裁判员
java·人工智能·后端·spring·大模型
何以解忧,唯有..2 小时前
Python包管理工具pip:从入门到精通
开发语言·python·pip
雪的季节2 小时前
RabbitMQ详解
开发语言
小bo波3 小时前
枚举实战
java·设计模式·枚举·后端开发·代码重构
ice8130331813 小时前
【Python】Matplotlib折线图绘制
开发语言·python·matplotlib
夜微凉43 小时前
三、Spring
java·后端·spring
三品吉他手会点灯3 小时前
C语言学习笔记 - 44.运算符和表达式 - 运算符2 - 除法与取余运算符
c语言·开发语言·笔记·算法
kkeeper~3 小时前
0基础C语言积跬步之动态内存管理
c语言·开发语言