Java并发编程实战 02 | 为什么创建线程只有一种方法?

在 Java 中,我们如何创建和使用线程?为什么说线程的创建方式本质上只有一种呢?本文将从并发编程的基础——如何创建线程开始,希望大家能够打好基础。虽然线程的创建看起来很简单,但其中还是有很多细节值得深入探讨。最后,我们将揭开线程实现的面纱,看清它的本质。

首先,大家可以思考一个问题:线程的实现方式有几种?大多数人会回答 2 种、3 种,甚至 4 种,但很少有人会说只有 1 种。我们先来看看常见的两种线程实现方式,以及它们背后的本质。

Thread 类和 Runnable 接口

继承Thread类

创建线程的第一种方式是继承类Thread`类,重写其中的run()方法:

class SayHelloThread extends Thread {public void run() {System.out.println("hello world");}
}public class ThreadJavaApp {public static void main(String[] args) {SayHelloThread sayHelloThread = new SayHelloThread();sayHelloThread.start();}
}//输出:
hello world

示例中在主线程中创建 SayHelloThread 实例并调用start()方法后,子线程才会启动!

实现 Runnable 接口

在 Java 中,除了直接继承 Thread 类来实现线程之外,我们还可以通过实现 Runnable 接口来创建线程。

首先我们看一下Runnable接口的地源码:

@FunctionalInterface
public interface Runnable {public abstract void run();
}

我们需要实现 Runnable 接口中的 run() 方法来定义线程的任务。

然而,无论是哪种方式,最终我们还是需要通过 Thread 类来启动和驱动线程。换句话说,Runnable 接口只是定义了任务的内容,而实际的线程启动和执行还是依赖于 Thread 类。

class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("Hello Runnable");}
}public class RunnableDemo {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();new Thread(myRunnable).start();}
}//输出:
Hello Runnable

此外,从 Runnable 接口的定义可以看出,它是一个函数式接口。这意味着我们可以利用 Java 8 的函数式编程 来简化代码。通过使用 Lambda 表达式,我们可以更简洁地创建和传递线程任务。

public class RunnableDemo {public static void main(String[] args) {// Java 8 functional programming, you can omit the definition of MyThread classnew Thread(() -> {System.out.println("Lambda expression implement Runnable");}).start();}
}//输出:
Lambda expression implement Runnable

Callable、Future 和 FutureTask

无论是通过实现 Runnable接口 还是继承 Thread 类来创建新线程,这两种方式都有一个缺点,就是 run() 方法没有返回值。

但是大多数场景我们希望启动一个线程执行某项任务,并在任务完成后获得一个返回值。为了解决这个问题,JDK 提供了 Callable 接口。

Callable接口

Callable 接口与 Runnable 类似,也是一个只有一个抽象方法的函数式接口。不同之处在于,Callable 的 call() 方法有返回值,并且支持泛型。这使得我们能够在执行任务时,获取到具体类型的结果。

@FunctionalInterface
public interface Callable<V> {V call() throws Exception;
}

通常,Callable 接口用于定义一个有返回值的任务。与 Runnable 类似,我们可以将实现了 Callable 接口的类传递给线程来执行。然而,你可能会发现,JDK 8 中的 Thread 类并没有提供接收 Callable 作为参数的构造函数。这并不是 Java 的错误,而是有意的设计选择。

Future 接口和 FutureTask 类

为了处理 Callable 任务并获得返回值,Java 提供了 FutureTask 类。FutureTask 实现了 RunnableFuture 接口,而 RunnableFuture 接口则继承了 Runnable 接口和 Future 接口。

这意味着 FutureTask 既可以作为 Runnable 被传递给 Thread 来执行,也可以用来处理异步计算并获取结果。

public interface RunnableFuture<V> extends Runnable, Future<V> {void run();
}

Future接口只有几个简单的方法:

public abstract interface Future<V> {public abstract boolean cancel(boolean paramBoolean);public abstract boolean isCancelled();public abstract boolean isDone();public abstract V get() throws InterruptedException, ExecutionException;public abstract V get(long paramLong, TimeUnit paramTimeUnit)throws InterruptedException, ExecutionException, TimeoutException;
}

各个方法的作用如下:

  • get():阻塞当前线程,直到计算完成,并返回结果。
  • get(long timeout, TimeUnit unit):阻塞当前线程,最多等待指定的时间。如果在这个时间内计算完成,则返回结果;如果超时未完成,则抛出 TimeoutException。
  • isDone:检查任务是否已完成。任务完成的状态包括正常终止、异常终止或被取消。无论是哪种情况,这个方法都会返回 true。
  • isCancelled:检查任务是否在正常完成之前被取消。如果是,返回 true。
  • cancel(boolean mayInterruptIfRunning):尝试取消任务的执行。取消并不是一定成功的,因为任务可能已经完成或者由于其他原因无法取消。boolean 类型的返回值表示取消是否成功。参数 mayInterruptIfRunning 决定是否在取消任务时中断正在执行的线程。

有时,我们会使用 Callable 代替 Runnable,以便能够在任务执行过程中获取结果或取消任务。如果使用 Future 是为了获取可取消任务的结果,Future 的结果可能是 null。正如前面所说,Future 只是一个接口,若自己实现可能会很复杂。因此,Java 提供了 FutureTask 类,简化了 Future 的实现。

FutureTask 类结合 Callable 用于完成有返回值的异步计算。它提供了 Future 接口的方法,如 cancel、get 和 isDone,并将它们的实现封装起来,方便我们直接使用。以下是一个简单的 FutureTask 使用示例:

class MyCallable implements Callable<Integer> {/*** Calculate the sum from 1 to 4* @return*/@Overridepublic Integer call() {int res = 0;for (int i = 0; i < 5; i++) {res += i;}return res;}
}public class CallableDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {// 1.Generate an instance of MyCallableMyCallable myCallable = new MyCallable();// 2.Create a FutureTask object through myCallableFutureTask<Integer> futureTask = new FutureTask<>(myCallable);// 3.Create a Thread object through FutureTaskThread t = new Thread(futureTask);// 4.start the threadt.start();// 5.Get calculation resultInteger res = futureTask.get();System.out.println(res);}
}//输出:
10

为什么只有一种方法来实现线程?

通过上面的介绍,相信这个问题大家基本上都有答案了:不管是通过实现Runnable接口,实现Callable接口还是直接继承Thread类来创建线程,最终的目的都是为了创建一个Thread实例来启动线程,也就是new Thread(),只是创建的形式不同而已!

除了这些常见创建线程的方法,还有其他的线程管理方式。例如,我们可以使用线程池等预置的工具类来创建和管理线程,这些工具类可以更高效地处理线程的生命周期和资源分配。后续的文章会单独介绍。

实现Runnable接口比继承Thread类更好。

Thread类的优点

  • 简单直观:由于 Thread 类直接继承自 Thread,代码结构比较简单,容易理解和使用。
  • 线程控制:可以直接调用 Thread 类的方法来控制线程的状态,例如启动、暂停和停止等操作。

Thread类的缺点

  • 单重继承的局限性:Java 不支持多重继承,因此如果继承了 Thread 类,就无法继承其他类,限制了类的扩展。
  • 代码耦合度:线程类与线程执行逻辑紧密耦合,可能导致代码的复用性和维护性下降。

Runnable接口的优点

  • 更好的代码重用:通过实现 Runnable 接口,可以将线程执行逻辑(即 run() 方法中的代码)与线程的管理逻辑(如启动和控制线程的操作)分开了,实现更高的代码重用性。
  • 多重实现灵活性:由于接口可以实现多个,因此可以避免 Thread 类的单一继承限制。

Runnable接口的缺点

  • 代码稍微复杂一些:需要创建一个实现 Runnable 接口的类,并实现 run() 方法,然后通过 Thread 类来启动线程。
  • 没有线程控制方法: Runnable 接口本身不包含线程控制方法,需要通过 Thread 对象来调用这些控制方法。

因此,综合考虑,通常建议通过实现Runnable接口来创建线程,以获得更好的代码可重用性和可扩展性。

线程的start()方法

在 Java 中,线程的启动是通过调用 start() 方法来实现的。当你在程序中调用 start() 方法时,虚拟机会首先创建一个新的线程,并且会等待这个线程首次获得 CPU 时间片之后才会调用 run() 方法来执行具体的任务。

需要注意的是,start() 方法只能被调用一次。如果尝试对同一个线程对象再次调用 start(),将会抛出 IllegalThreadStateException 异常。因为线程一旦启动,其状态会变为“准备”,之后不能再被重新启动。

下面是 start() 方法的简化源码说明:

public synchronized void start() {//0对应于状态“NEW”。if (threadStatus != 0)throw new IllegalThreadStateException();group.add(this);   boolean started = false;try {start0();   //The native method calls the underlying method that actually creates the thread.started = true;} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}}
}

start() 方法的真正工作是由 start0() 完成的。start0() 是一个原生方法,它由底层操作系统实现,用于实际创建和启动线程。这一过程包括线程的初始化和调度,使得线程能够在 JVM 中并行执行。

关于ThreadGroup的概念我们会在后续的文章中介绍,这里可以先忽略,有个印象即可。

Thread类的几个常用方法

这里我们简要介绍一下 Thread 类的一些常用方法,了解这些方法可以帮助你更好地进行多线程编程。具体的使用场景将在后续的文章中详细讨论:

  • currentThread():这是一个静态方法,用于返回当前正在执行的线程对象的引用。通过这个方法,你可以获取到当前线程的信息。
  • sleep(long millis):这是一个静态方法,使当前线程休眠指定的时间(以毫秒为单位)。调用这个方法可以暂停线程的执行,直到指定的时间到达。
  • yield():这个方法表示当前线程愿意让出对处理器的占用,从而允许其他线程获得执行机会。请注意,即使调用了 yield() 方法,当前线程仍然可能继续运行,具体是否让出取决于线程调度器的实现。
  • join():这个方法使当前线程等待另一个线程执行完成后才继续执行。内部实际上是通过调用 Object 类的 wait() 方法来实现的。这对于确保一个线程在另一个线程完成后再继续执行非常有用。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于SpringBoot的高校BBS在线互动论坛系统

&#x1f4a5;&#x1f4a5;源码和论文下载&#x1f4a5;&#x1f4a5;&#xff1a;基于SpringBoot的高校BBS在线互动论坛系统-源码论文报告数据库.rar 1. 系统介绍 本论文设计并实现了一个基于Spring Boot和Vue的校园论坛系统&#xff0c;该系统分为用户和管理员两个角色。用户…

【网络安全】服务基础第一阶段——第七节:Windows系统管理基础---- Web与FTP服务器

将某台计算机中的⽂件通过⽹络传送到可能相距很远的另⼀台计算机中&#xff0c;是⼀项基本的⽹络应⽤&#xff0c;即⽂件传送。 ⽂件传送协议FTP &#xff08;File Transfer Protocol&#xff09;是因特⽹上使⽤得最⼴泛的⽂件传送协议。 涉及到文件的上传和下载&#xff0c;很…

代码随想录 刷题记录-28 图论 (5)最短路径

一、dijkstra&#xff08;朴素版&#xff09;精讲 47. 参加科学大会 思路 本题就是求最短路&#xff0c;最短路是图论中的经典问题即&#xff1a;给出一个有向图&#xff0c;一个起点&#xff0c;一个终点&#xff0c;问起点到终点的最短路径。 接下来讲解最短路算法中的 d…

【论文分享】GPU Memory Exploitation for Fun and Profit 24‘USENIX

目录 AbstractIntroductionResponsible disclosure BackgroundGPU BasicsGPU architectureGPU virtual memory management GPU Programming and ExecutionGPU programming modelGPU kernelDevice function NVIDIA PTX and SASSSASS instruction encoding GPU Memory SpacesGlob…

【数据库】MySQL表的基本查询

关于表的增删查改主要分为CRUD&#xff1a;Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#xff0c;Delete&#xff08;删除&#xff09; 目录 1.Creat&#xff08;增加内容&#xff09; 1.1指定列插入 1.2全列插入 1.3多行插入 1.4插入冲突更新 1.5替换 2.R…

【算法每日一练及解题思路】判断字符串是否包含数字

【算法每日一练及解题思路】判断字符串是否含数字 一、题目&#xff1a;给定一个字符串&#xff0c;找出其中不含重复字符的最长子串的长度 二、举例&#xff1a; 比如"abcdefgh",不含数字&#xff1b;比如"1",含数字&#xff1b;比如"a1s",含…

fl studio24.1.1.4285中文版怎么破解?FL Studio 2024安装破解使用图文教程

fl studio24.1.1.4285中文破解版是一款功能强大的编曲软件&#xff0c;也就是众所熟知的水果软件。它可以编曲、剪辑、录音、混音&#xff0c;让您的计算机成为全功能录音室。除此之外&#xff0c;这款软件功能非常强大&#xff0c;为用户提供了许多音频处理工具&#xff0c;包…

单点登录问题【拼多多0905一面】

说一些今晚情况&#xff0c;7点腾讯音乐笔试&#xff0c;因为8点拼多多一面&#xff0c;哪个都拒不了。硬着头皮50分钟写了1.2题然后去面试。刚开始状态真的很差&#xff0c;大脑思考不动&#xff0c;面试中2个手撕&#xff0c;做出来一个&#xff0c;两个项目问题&#xff0c;…

notepad++将换行替换成空

将多行里的换行置为一行&#xff0c;例如将下面的6行置为3行 crrlH打开替换框&#xff0c; 替换目标为【,\r\n】&#xff0c;替换成空&#xff0c;勾选循环查找和 正则表达式&#xff0c;全部替换即可。 替换后的效果

【Git】本地仓库操作

Part1 基础概念 git作用&#xff1a;管理代码版本&#xff0c;记录&#xff0c;切换&#xff0c;合并代码 git仓库&#xff1a;记录文件状态内容和历史记录的地方&#xff08;.git文件夹&#xff09; git的三个区域&#xff1a;1&#xff09;工作区&#xff1a;实际开发时的文…

IP 协议详解

一、认识 IP 地址与网络层的职责 网络层是OSI七层模型中的第三层&#xff0c;也是TCP/IP四层模型中的网络接入层。在这一层&#xff0c;数据包被封装并加上IP层的头部信息&#xff0c;以便在网络之间传输。网络层的主要功能包括路由选择、分段与重组、拥塞控制以及IP地址管理等…

视频技术未来展望:EasyCVR如何引领汇聚融合平台新趋势

随着科技的飞速发展&#xff0c;视频技术已成为现代社会不可或缺的一部分&#xff0c;广泛应用于安防监控、娱乐传播、在线教育、电商直播等多个领域。本文将探讨视频技术的未来发展趋势&#xff0c;并深入分析TSINGSEE青犀EasyCVR视频汇聚融合平台的技术优势&#xff0c;展现其…

ArcGIS展线/投线教程

1 制作CSV文件 必要字段&#xff1a;起始经度&#xff0c;起始纬度&#xff0c;终止经度&#xff0c;终止纬度4列&#xff0c;其他列可以选填。 2 加载表格数据 点击号在当前地图加载表格。 3 使用工具箱 找到工具箱 - 数据管理工具 - 要素 - XY转线。 填空即可。当然&…

16 C语言连接

使用c语言连接mysql&#xff0c;需要使用mysql官网提供的库&#xff0c;可以在官网下载 准备工作&#xff1a; 保证mysql服务有效 官网下载合适的mysql connect库 也可以直接安装mysql服务 yum install -y mysql-devel Connector/C使用 库格式如下&#xff1a; [hbMiWiFi-R1…

监控平台之nodejs模拟后端接口

github&#xff1a;可以下载进行实验 https://github.com/Mr-Shi-root/sdk-platform/tree/master 1.配置node环境&#xff0c;安装express cors body-parser babel/cors body-parser - node.js 中间件&#xff0c;用于处理 JSON, Raw, Text 和 URL 编码的数据。cookie-parse…

VR虚拟驾驶未来发展_vr自动驾驶汽车所带来的改变

在自动驾驶汽车的基础上&#xff0c;VR虚拟现实技术的应用也让自动驾驶汽车更加智能化&#xff0c;能够实现更高级的驾驶体验&#xff0c;今天这篇文章就和大家一起探讨一下 VR虚拟驾驶未来发展的趋势&#xff0c;以及虚拟现实自动驾驶汽车所带来的几个改变。 一、VR 虚拟驾驶未…

UnityShaderGraph 卡通水面效果

效果预览&#xff1a; 原理&#xff1a; 使用三张噪声贴图&#xff0c;结合UV偏移制作水面波纹混合的假象效果 噪声图1&#xff1a; 噪声图2&#xff1a; 噪声图3&#xff1a; 三次采样都是同样的方法&#xff0c;使用step函数来二分噪声 三张噪声采样结果相乘得到最终的波纹…

深度学习与大模型第1课环境搭建

文章目录 深度学习与大模型第1课环境搭建1. 安装 Anaconda2. 修改环境变量2.1 修改 .condarc 文件2.2 使用 Anaconda Prompt 修改环境变量 3. 新建 .ipynb 文件 机器学习基础编程&#xff1a;常见问题&#xff1a; 深度学习与大模型第1课 环境搭建 1. 安装 Anaconda 首先&am…

Ai Illustrator 取消吸附到像素点,鼠标拖动的时候只能到像素点

Ai Illustrator 取消吸附到像素点&#xff0c;鼠标拖动的时候只能到像素点 在做图的时候无意间变成吸附到像素点了&#xff0c;导致无法更细致的移动点。 像这样&#xff1a; 关闭的方法是打开上面菜单中的 【视图】取消勾选【对齐像素】 即可。 结果就是&#xff1a;

新160个crackme - 048-monkeycrackme1

运行分析 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/8e7c9973721b4df1997cc9a83e0ef2b6.png 500x) 点击注册无反应 PE分析 Delphi程序&#xff0c;32位&#xff0c;无壳 静态分析&动态调试 使用DeDeDark进行分析&#xff0c;发现Register按钮事件地址入口…