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. 优先使用实现接口的方式 :Runnable和Callable接口方式更加灵活,避免了单继承的限制

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

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

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

相关推荐
daidaidaiyu8 小时前
ThingsBoard 集群的核心逻辑源码分析
java
2601_962885728 小时前
如何用 Python 自动识别股票的支撑位与压力位?
开发语言·python
爱吃奥利奥_wen9 小时前
面向对象三板斧:封装、继承、多态,到底在“装“什么、“承“什么、“变“什么?
开发语言·经验分享·c#
我可能是个假开发9 小时前
FTP Unicode 文件名导致 `550` 的问题分析与解决方案
java·开发语言·spring
caoerzhong9 小时前
中小企业上 WMS 该先上哪几块:JeeWMS 开源 Java 仓库管理系统的分批上线清单
java·python·开源
WL学习笔记10 小时前
日期类(C++)
开发语言·c++·日期类实现
【JAVA】玩家10 小时前
Spring核心原理全解析:从零到生产实战
java·后端·spring
Wang's Blog11 小时前
Java框架 SpringCloud 快速入门: 实现 Feign 最佳实践(抽取方式)
java·spring cloud
谢亮_vipxieliang11 小时前
Go map与结构体的正确使用
开发语言·golang·哈希算法
H.莓飛11 小时前
【C++】命名空间、缺省参数、函数重载与引用
linux·开发语言·c++·visual studio