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 小时前
java编写 agent 入门案例
java·开发语言
环流_1 小时前
redis:持久化rdb
java·数据库·redis
代码中介商1 小时前
C++ STL 容器完全指南(三):deque、list 与 map 深度详解
开发语言·c++
xqqxqxxq2 小时前
Java 线程池(一)
java·开发语言
qxwlcsdn2 小时前
mysql在事务中执行DDL的后果_MySQL 8.0之前的限制
jvm·数据库·python
eggrall2 小时前
Linux进程信号——像收快递一样理解 Linux 信号
linux·开发语言·c++
Full Stack Developme2 小时前
spring-beans 解析
java·后端·spring
foundbug9992 小时前
MATLAB实现:基于图像对比度和波段相关性的高光谱波段选择算法
开发语言·算法·matlab
2401_884454152 小时前
如何防止SQL触发器导致性能下降_通过精简触发器逻辑
jvm·数据库·python