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

文章目录

  • 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中创建线程的四种方式,如果有任何的问题或者疑问,非常欢迎大家在评论区评论!!!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

7000字详解ERP管理系统!

在当今竞争激烈的商业世界中&#xff0c;中小企业不仅需要保持灵活性&#xff0c;更需要高效管理企业资源。 你可能听说过ERP系统&#xff0c;但它究竟是什么&#xff1f;它为何成为中小企业管理的不二选择&#xff1f;又是如何助力中小企业整合资源、提升效率&#xff0c;并在…

C++ OJ题测试—排序算法效率

目录 OJ链接 一、直接插入排序 二、希尔排序 三、直接选择排序 常规&#xff1a; 第二种&#xff1a; 四、 堆排序 五、冒泡排序 六、快速排序 常规&#xff1a; 三路划分优化效率 七、归并排序 八、计数排序 OJ链接 ​ 一、直接插入排序 class Solution { pub…

Python3.5 中->,即横杠和箭头,用来表示函数的返回值类型

最近在看playwright的源码&#xff0c;在看sync_playwright()方法的源码时发现一个特殊的语法-> 即横杠箭头&#xff0c;跟据如下源码猜测它应该是一个说明函数返回值类型的标识&#xff0c;因为 -> PlaywrightContextManager 与return PlaywrightContextManager() 一致…

算法(2)——滑动窗口

前言&#xff1a; 步骤及算法模板&#xff1a; 确定两个指针变量&#xff0c;left0,right0; 进窗口&#xff1a; 判断&#xff1a; 出窗口 更新结果 接下来我们的所用滑动窗口解决问题都需要以上几个步骤。 一、长度最小的子数组 209. 长度最小的子数组 - 力扣&#xff08;L…

C语言-> 文件操作(函数满屏)

系列文章目录 前言 ✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;橘橙黄又青_C语言,数据结构,函数-CSDN博客 目的&#xff1a;学习文件操作&#xff0c;即…

Halcon深度学习方法

1、异常检测和全局上下文异常检测 图(1)异常检测示例 在图(1)上图中&#xff0c;异常检测示例&#xff1a;为输入图像的每个像素都分配一个分数&#xff0c;表明它显示未知特征(即异常)的可能性&#xff1b;在图(1)下图中&#xff0c;全局上下文异常检测示例&#xff1a;为输入…

Leetcode—859.亲密字符串【简单】

2023每日刷题&#xff08;六十三&#xff09; Leetcode—859.亲密字符串 &#x1f4a9;山实现代码 class Solution { public:bool buddyStrings(string s, string goal) {int len1 s.size(), len2 goal.size();int cnt 0;int flag 0;int flag2 0;int odd -1;int a[26] …

Eclipse_01_如何设置代码文件背景颜色为护眼沙绿色

设置方法 Window --> Preference 参考文档 参考文档 1

软件测试人才稀缺!揭秘为什么你找不到软件测试工作?

最近后台很多粉丝给我留言&#xff1a; 2023年软件测试已经崩盘了吗&#xff0c;为什么都找不到工作了&#xff1f; 确实&#xff0c;今年经济大环境不好&#xff0c;企业也都在降本增效&#xff0c;如果技术能力还在被应届生竞争岗位的阶段&#xff0c;只会越来越难。 找不…

全球首个AI监管法案出炉!

全球首个AI监管法案诞生了&#xff01; 今年来AI领域出现了爆发式的发展&#xff0c;在大模型的浪潮下&#xff0c;全球关于AI监管的讨论也越来越热。近日&#xff0c;经过长时间的讨论&#xff0c;欧洲议会、欧盟成员国和欧盟委员会三方签署了全球首份AI监管法案——《人工智…

关于Android studio新版本和NEW UI显示返回按钮的设置

1.新版Android studio问题 因为在新版本的Android Studio中&#xff0c;默认情况下是没有直接的选项来显示返回上一步按钮在状态栏上的&#xff0c;可以通过以下方法来实现返回上一步的功能&#xff1a; 在Android Studio的顶部菜单栏中&#xff0c;选择"View"。在…

[原创][R语言]股票分析实战[1]:周级别涨幅趋势的相关性

[简介] 常用网名: 猪头三 出生日期: 1981.XX.XX QQ联系: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、D…

spring MVC概述和土门案例(无配置文件开发)

SpringMVC 1&#xff0c;SpringMVC概述2&#xff0c;SpringMVC入门案例2.1 需求分析2.2 案例制作步骤1:创建Maven项目步骤2:补全目录结构步骤3:导入jar包步骤4:创建配置类步骤5:创建Controller类步骤6:使用配置类替换web.xml步骤7:配置Tomcat环境步骤8:启动运行项目步骤9:浏览器…

Spring MVC 原理(四)

Spring MVC 原理 Spring 的模型-视图-控制器&#xff08;MVC&#xff09;框架是围绕一个 DispatcherServlet 来设计的&#xff0c;这个 Servlet会把请求分发给各个处理器&#xff0c;并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染等&#xff0c;甚至还能支持文…

还搞不懂虚短与虚断概念?虚断与虚断通俗讲解,几分钟带你搞定

在模拟电路 中&#xff0c;虚短和虚断是两个重要的概念&#xff0c;它们通常与运放电路 有关。这两个术语描述了运放电路中的一些重要现象&#xff0c;认识它们对 于 电子工程师 和电路设计师来说至关重要。本文将深入探讨虚短和虚断的含义&#xff0c;以及它们在电子电路中的应…

Java中线程状态的描述

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

LeetCode刷题---长度最小的子数组

要点&#xff1a;该题属于滑动窗口类型的题目 解法一&#xff1a;暴力破解法 使用两层for循环&#xff0c;i为起始位置&#xff0c;j为终止位置&#xff0c;每次j都要遍历到数组最后一个下标&#xff0c;并且逐个累加。当sum大于等于target时&#xff0c;比较获取最小的长度&am…

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

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

Burp漏洞扫描指南

burp是web渗透测试中最常用的工具之一。可以通过抓包请求&#xff0c;分析站点漏洞等。是安全爱好者最喜欢的工具。本文让我们一起来学习利用burp进行站点漏洞扫描。 总体而言&#xff0c;burp的扫描方式分为被动扫描和主动扫描两者方式。现对这两种方式进行详细的说明。注意&a…

力扣刷题记录(16)LeetCode:62、63、343、96

目录 62. 不同路径 63. 不同路径 II 343. 整数拆分 96. 不同的二叉搜索树 总结 这题比较简单&#xff0c;直接声明一个二维数组来保存到达该点有几种路径。到达当前点的方法由当前点的左边格子和右边格子决定。 class Solution { public:int uniquePaths(int m, int n)…