(详解版)创建线程的四种方式

news2024/12/25 1:14:17

文章目录

  • Java中创建线程的四种方式
    • 1. 继承`Thread类`并重写 `run` 方法来创建线程
    • 2. 实现`Runnable接口`并实现 `run` 方法来创建线程。
    • 3. 使用`Callable接口`创建线程
    • 4. 使用`Executor框架`创建线程

Java中创建线程的四种方式


接下来我会详细解释这四种方式创建线程如何实现.

我们如果要创建线程实例,就需要先知道 **线程是什么? 在Java中又是以怎样的形式存在的?**只有了解了这些我们才能更好的理解可以通过多种方式来创建线程.

Q: 线程是什么?

A: 线程(Thread)是计算机科学中的一个基本概念,是进程内的一个独立执行单元。一个进程可以包含多个线程,这些线程共享进程的资源,但拥有各自的执行路径。每个线程都是独立运行的,有自己的程序计数器(Program Counter)、寄存器集合和栈。

线程是程序执行的最小单元,它执行进程中的指令序列。相比于进程,线程的创建和销毁的开销较小,线程间的切换成本也相对较低。多线程的优势在于能够更好地利用多核处理器的性能,以及更有效地进行并发编程。

线程通常有两种模型:用户级线程和内核级线程。

  1. 用户级线程: 用户级线程是由用户空间的线程库(Thread Library)管理的,而不需要操作系统内核的支持。用户级线程的切换由线程库在用户空间完成,相对较快。然而,用户级线程的一个缺点是,如果一个线程发生阻塞,整个进程都会被阻塞,因为内核并不知道线程的存在。
  2. 内核级线程: 内核级线程是由操作系统内核管理的,它直接受操作系统的支持。内核级线程的切换涉及到内核的介入,相对较慢。但是,内核级线程的一个优点是如果一个线程发生阻塞,其他线程仍然可以继续执行。

Q:Java中线程又是以怎样的形式存在的?

A: Java中,线程是通过java.lang.Thread类来表示的。Java提供了多线程的支持,通过继承Thread类或实现Runnable接口,可以创建和管理线程。线程的执行通常通过调用线程的start方法来启动,而线程的实际执行逻辑则由run方法定义。


1. 继承Thread类并重写 run 方法来创建线程

2. 实现Runnable接口并实现 run 方法来创建线程。

由于前两种创建线程的方式比较的简单,所以我们就一起讲了,看了之后的解释,你也就会明白我会什么会把这两种方式一起讲了,因为二者的本质其实没有什么非常大的差别.

结构决定性质,我们首先先初步认识下线程的结构,通过查看Java标准库中的 java.lang.Thread类 中的构造方法,我们就可以初步判断出线程Thread对象创建的两种方式,分别是

1.继承Thread类,使用的是Thread类中的缺省构造器(无参构造方法) .

2. 实现Runnable接口,通过观察可以直到在Thread类中其他有参构造方法几乎都有Runnable这个接口 .

如下图:

image-20231219171728045

  • 那么我们继续思考,是否可以继续扩展?

现在我通过观察Java.lang.Thread类中的构造方法,知道了创建线程至少可以有这两种方法,分别是继承Thread类以及实现Runnable接口,那么我们是否可以继续扩展,由于创建线程实例的目的是重写run方法或者实现run方法,定义线程的执行逻辑。那么我是否可以加上匿名内部类或者Lambda的知识呢?这样的话,创建线程的方式又可以细分.

组合搭配之后创建线程的流程主要包括以下步骤:

  1. 继承 Thread 类:

    • 创建一个继承自 Thread 类的新类。
    • 在新类中重写 run 方法,定义线程的执行逻辑。
    • 创建该类的实例。
    • 调用实例的 start 方法,启动线程。
    class MyThread extends Thread {
        public void run() {
            // 线程执行逻辑
        }
    }
    
    // 创建线程的实例
    MyThread myThread = new MyThread();
    // 启动线程
    myThread.start();
    
  2. 实现 Runnable 接口:

    • 创建一个实现 Runnable 接口的类。
    • 在该类中实现 run 方法,定义线程的执行逻辑。
    • 创建 Thread 类的实例,将实现了 Runnable 接口的对象传递给 Thread 构造方法。
    • 调用 start 方法,启动线程。
    class MyRunnable implements Runnable {
        public void run() {
            // 线程执行逻辑
        }
    }
    
    // 创建线程的实例
    Thread thread = new Thread(new MyRunnable());
    // 启动线程
    thread.start();
    
  3. 使用匿名内部类:

    • 使用匿名内部类创建线程,同时实现 run 方法。
    • 创建 Thread 类的实例。
    • 调用 start 方法,启动线程。
    Thread thread = new Thread(new Runnable() {
        public void run() {
            // 线程执行逻辑
        }
    });
    
    // 启动线程
    thread.start();
    
  4. 使用 Lambda 表达式:

  • 使用 Lambda 表达式创建线程,直接在 Runnable 接口的匿名实现中定义 run 方法。
  • 创建 Thread 类的实例。
  • 调用 start 方法,启动线程。
Thread thread = new Thread(() -> {
    // 线程执行逻辑
});

// 启动线程
thread.start();

这些步骤涵盖了主要的线程创建方式。选择哪种方式取决于任务的性质以及对代码的偏好。无论哪种方式,最终的目标是定义线程的执行逻辑并启动线程。


接下来我们来学习剩下的两个创建线程的方式.

3. 使用Callable接口创建线程

认识Callable

Callable 接口是 Java 中用于表示可调用任务(可以返回结果并抛出异常)的接口。与 Runnable 接口不同,Callablecall 方法可以返回执行结果,而 Runnablerun 方法则没有返回值。通常,Callable 接口结合 Future 接口一起使用,Future 代表一个异步计算的结果,可以通过它来获取任务的执行结果,或者等待任务执行完毕.

  • 帮助文档->接口Callable

image-20231219182436261

  • 帮助文档->FutureTask

image-20231219182917253

我们通过对照对比的方式来学习Callable接口哈,这样能够更好的帮助我们理解Callable接口,光看上面的解释和图片你可能不能第一时间消化,现在我们有一个例子,我们需要使用多线程来模拟一个耗时操作

  • 不使用Callable接口和Future
public class RunnableExample {

    public static void main(String[] args) {
        // 使用Runnable创建一个任务
        Runnable runnableTask = () -> {
            System.out.println("Executing Runnable task...");
            // 模拟一个耗时操作
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        // 创建一个线程来执行Runnable任务
        Thread thread = new Thread(runnableTask);
        thread.start();

        // 此时可以执行一些其他的操作
    }
}
  • 使用 CallableFuture 的情况
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableExample {

    public static void main(String[] args) {
        // 使用Callable创建一个任务
        Callable<Integer> callableTask = () -> {
            System.out.println("Executing Callable task...");
            Thread.sleep(2000); // 模拟一个耗时操作
            return 42;
        };

        // 使用FutureTask包装Callable任务
        FutureTask<Integer> futureTask = new FutureTask<>(callableTask);

        // 创建一个线程来执行FutureTask
        Thread thread = new Thread(futureTask);
        thread.start();

        // 此时可以执行一些其他的操作

        try {
            // 获取Callable任务的执行结果,此处会阻塞直到任务执行完毕
            Integer result = futureTask.get();
            System.out.println("Result: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

通过上述的3两个代码块我们来简单的做个总结:

使用 Callable 的优点:

  1. 可以返回结果: Callable 允许任务返回一个结果,而 Runnable 不支持返回结果。这使得在并发编程中更容易获取任务的执行结果。
  2. 支持异常抛出: Callablecall 方法可以抛出受检查的异常,而 Runnablerun 方法不能。这使得在任务执行过程中发生异常时,更容易捕获和处理异常。
  3. 使用 Future 进行异步操作: Future 接口允许异步地获取任务的执行结果,而不需要等待任务完成。这对于并发编程中需要异步操作的场景非常有用。

总体而言,Callable 接口和 Future 接口的结合,提供了更多的灵活性和控制权,特别是在需要获取任务执行结果、处理异常或进行异步操作的情况下。


4. 使用Executor框架创建线程

Java中的Executor框架是一套用于简化多线程编程的工具和框架。它位于java.util.concurrent包下,提供了一种管理和执行线程的方式,使得开发者能够更轻松地编写并发程序。

Executor框架的主要组件包括以下几个:

  1. Executor接口: 是Executor框架的根接口,定义了一个单一的方法 execute(Runnable command),用于执行传入的任务(实现了Runnable接口的对象)。

  2. ExecutorService接口: 继承自Executor接口,提供了更丰富的任务生命周期管理方法,例如提交任务、获取Future对象、关闭ExecutorService等。常见的实现类有ThreadPoolExecutor

  3. ScheduledExecutorService接口: 继承自ExecutorService接口,支持任务的定时执行和周期性执行。常见的实现类有ScheduledThreadPoolExecutor

  4. Executors工厂类: 提供了一些静态方法,用于创建不同类型的ExecutorService实例,例如创建固定大小的线程池、缓存线程池、单线程线程池等。

以下是一个简单的例子,演示了如何使用Executor框架创建线程池并提交任务:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorExample {
    public static void main(String[] args) {
        // 创建固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);//这里必须要看懂,后面内容我有给出解释

        // 提交任务给线程池执行
        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            executorService.execute(() -> {
                System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName());
            });
        }

        // 关闭线程池
        executorService.shutdown();
    }
}

在这个例子中,通过Executors.newFixedThreadPool(2)创建了一个固定大小为2的线程池,然后通过execute方法提交了5个任务给线程池执行。这种方式可以有效地管理线程,使得任务可以并发执行,提高了程序的性能。

其中我们对这行代码进行一个详细的解释:

image-20231219190428504

这段代码创建了一个固定大小为2的线程池,使用了ExecutorService接口,并通过Executors.newFixedThreadPool(2)工厂方法来实现。

  1. ExecutorService接口: ExecutorService是Java Executor框架的一个接口,它扩展了Executor接口,提供更多的方法用于管理线程池和任务执行。

  2. Executors.newFixedThreadPool(2) Executors是一个工具类,提供了一些静态方法用于创建不同类型的ExecutorService实例。newFixedThreadPool(2)是其中一种方法,它创建了一个固定大小为2的线程池。这意味着线程池中最多会同时存在两个线程。

  3. executorService 这是创建的ExecutorService实例的引用,通过该引用可以操作和管理线程池。

综合起来,这行代码的作用是创建了一个固定大小为2的线程池,将其引用赋给executorService。这样,你就可以使用executorService来提交任务,线程池会负责管理这两个线程的生命周期、执行任务和处理任务队列。


结尾

以上的内容就是这篇文章带给大家的内容,我们详细的阐述了Java中创建线程的四种方式,如果有任何的问题或者疑问,非常欢迎大家在评论区评论!!!

  1. executorService 这是创建的ExecutorService实例的引用,通过该引用可以操作和管理线程池。

综合起来,这行代码的作用是创建了一个固定大小为2的线程池,将其引用赋给executorService。这样,你就可以使用executorService来提交任务,线程池会负责管理这两个线程的生命周期、执行任务和处理任务队列。


以上的内容就是这篇文章带给大家的内容,我们详细的阐述了Java中创建线程的四种方式,如果有任何的问题或者疑问,非常欢迎大家在评论区评论!!!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1322510.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32——串口通信应用篇

一、引言 STM32微控制器是一款功能强大的嵌入式系统芯片&#xff0c;广泛应用于各种领域。其中&#xff0c;串口通信是其重要功能之一&#xff0c;可用于与外部设备进行数据交换和控制。本文将介绍STM32串口通信的基本原理、应用场景以及实现方法。 二、STM32串口通信基本原理 …

linux xxd命令(将文件或标准输入转换为hex(十六进制)和ASCII(美国信息交换标准代码)表示,或者从hex dump(十六进制转储)反向到二进制)

文章目录 Linux xxd命令安装xxd基本使用方法创建hex dump从hex dump恢复到二进制 命令选项疑难技术点解析在脚本中使用xxd从hex dump恢复数据 总结 Linux xxd命令 xxd是一个在Linux和UNIX系统中常用的工具&#xff0c;主要用于将文件或标准输入转换为hex&#xff08;十六进制&…

Java中线程状态的描述

多线程-基础方法的认识 截止目前线程的复习 Thread 类 创建Thread类的方法 继承Thread类,重写run方法实现Runnable接口,重写run方法使用匿名内部类继承Thread类,重写run方法使用匿名内部类实现Runnable接口,重写run方法使用Lambda表达式 run方法中的所有的代码是当前线程对…

[Linux] LVS负载均衡群集——DR模式

一、 DR模式的特点 直接路由&#xff1a; 在LVS_DR模式下&#xff0c;负载均衡器不修改数据包的IP地址&#xff0c;只修改目的MAC地址。这使得数据包可以直接路由到后端实际服务器上&#xff0c;而不需要返回到负载均衡器。 高性能&#xff1a; 由于数据包在传输过程中不需要回…

dubbo--03--- dubbo 支持的9种协议

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Dubbo框架特性Dubbo 和 Spring Cloud区别 dubbo 支持的9种协议协议类型1、dubbo 协议 (默认)特性配置常见问题 2、rmi 协议3、hessian 协议4、http 协议特性 5、web…

案例073:基于微信小程序的智慧旅游平台开发

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

【MYSQL】-表的操作

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

functools.partial:Python中灵活函数部分应用的工具

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python编程中&#xff0c;functools.partial是一个强大的工具&#xff0c;它提供了一种部分应用函数的方式&#xff0c;能够在创建新函数时固定部分参数&#xff0c;从而在后续调用中减少需要传递的参数数量。…

python中random.seed()和random.getstate()用法详解

python中random.seed()和random.getstate()用法详解 摘要 python的random包经常被用于模拟实验的重现&#xff0c;数据集的随机划分的确定性重现。然而&#xff0c;我本人之前对random.seed()什么时候调用&#xff0c;调用之后会对之后多少代码起决定性作用这一块感到云里雾里…

压测方案设计..

01 为什么要做压测 1、什么是压力测试&#xff1f; 不断向被测对象施加压力&#xff0c;测试系统在压力情况下的表现。 2、压力测试的目的是什么&#xff1f; 测试得出系统的极限性能指标&#xff0c;从而给出合理的承诺值或者容量告警&#xff1b; 找出系统的性能瓶颈&am…

清华提出ViLa,揭秘 GPT-4V 在机器人视觉规划中的潜力

人类在面对简洁的语言指令时&#xff0c;可以根据上下文进行一连串的操作。对于“拿一罐可乐”的指令&#xff0c;若可乐近在眼前&#xff0c;下意识的反应会是迅速去拿&#xff1b;而当没看到可乐时&#xff0c;人们会主动去冰箱或储物柜中寻找。这种自适应的能力源于对场景的…

51单片机简易出租车计费系统仿真设计

51单片机简易出租车计费系统仿真设计( proteus仿真程序报告讲解视频&#xff09; 仿真图proteus 8.9及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0036 1.主要功能&#xff1a; 出租车计费系统设计内容&#xff1a; 1、…

JDK17 SpringBoot3 整合常见依赖

JDK版本:17 SpringBoot 整合Mybatis Plus 、Redis等 依赖文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xs…

数据链路程协议

目录 数据链路层 介绍 以太网帧格式 目的地址 源地址 类型 CRC 数据 如何封装和解包 如何向上交付 MAC地址与IP地址 MTU 局域网数据转发 局域网数据碰撞 数据包转发 ARP协议 构建ARP请求 ARP请求的处理 ARP响应的构建 ARP欺骗 DNS域名解析 域名解析是什么…

标准IO与文件IO

标准IO通过缓冲机制减少系统调用&#xff0c;实现更高的效率 全缓冲&#xff1a;当流的缓冲区无数据或无空间时才执行实际IO操作 行缓冲&#xff1a;当在输入和输出中遇到换行符&#xff08;\n&#xff09;时&#xff0c;进行IO操作 当流和一个终端关联时&#xff0c;典型的行缓…

【06】GeoScene海图或者电子航道图数据自动化质检

1 S-58错误管理器验证产品 在你编辑数据时进行快速的质量检查可以使用S-58错误管理器&#xff0c;S-58错误管理器工具允许您使用IHO S-58验证标准来验证海事数据库中的产品。你可以验证整个产品&#xff0c;或验证产品的当前范围。 1.1验证产品 使用S-58错误管理器工具完成以…

服务器解析漏洞是什么?攻击检测及修复

服务器解析漏洞&#xff08;Server-side Include Vulnerability&#xff0c;SSI漏洞&#xff09;是一种安全漏洞&#xff0c;通常出现在支持服务器端包含&#xff08;SSI&#xff09;功能的Web服务器上。SSI是一种在Web页面中嵌入动态内容的技术&#xff0c;允许开发人员将外部…

Java 数据结构篇-实现二叉搜索树的核心方法

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 二叉搜索树的概述 2.0 二叉搜索树的成员变量及其构造方法 3.0 实现二叉树的核心接口 3.1 实现二叉搜索树 - 获取值 get(int key) 3.2 实现二叉搜索树 - 获取最小…

监控k8s controller和scheduler,创建serviceMonitor以及Rules

目录 一、修改kube-controller和kube-schduler的yaml文件 二、创建service、endpoint、serviceMonitor 三、Prometheus验证 四、创建PrometheusRule资源 五、Prometheus验证 直接上干货 一、修改kube-controller和kube-schduler的yaml文件 注意&#xff1a;修改时要一个节…

neo4j安装报错:neo4j.bat : 无法将“neo4j.bat”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。

neo4j安装报错&#xff1a; neo4j.bat : 无法将“neo4j.bat”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确 保路径正确&#xff0c;然后再试一次。 解决办法&#xff1a; 在环境变量中的&#xff0c;用户…