创建 Java 多线程有哪几种方式?

news/2024/4/26 15:03:26/文章来源:https://blog.csdn.net/mukewangguanfang/article/details/128948312

本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻,欢迎关注!

作者| 慕课网精英讲师 ColorfulC

Java 多线程

本篇文章我们介绍一下如何创建线程,创建线程有哪几种方式,线程的状态、生命周期等内容。

1. 什么是线程

要了解什么是线程,就要先了解进程的概念。

进程,是指计算机中已运行的程序,它是一个动态执行的过程。假设我们电脑上同时运行了浏览器、QQ 以及代码编辑器三个软件,这三个软件之所以同时运行,就是进程所起的作用。

线程是操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。也就是说一个进程可以包含多个线程, 因此线程也被称为轻量级进程。

如果你还是对于进程和线程的概念有所困惑,推荐一篇比较优秀的文章,有助于帮助你理解进程和线程的概念。

2. 创建线程

在 Java 中,创建线程有以下 3 种方式:

  1. 继承 Thread 类,重写 run() 方法,该方法代表线程要执行的任务;
  2. 实现 Runnable 接口,实现 run() 方法,该方法代表线程要执行的任务;
  3. 实现 Callable 接口,实现 call() 方法,call() 方法作为线程的执行体,具有返回值,并且可以对异常进行声明和抛出。

下面我们分别来看下这 3 种方法的具体实现。

2.1 Thread 类

Thread 类是一个线程类,位于 java.lang 包下。

2.1.1 构造方法

Thread 类的常用构造方法如下:

  • Thread():创建一个线程对象;
  • Thread(String name):创建一个指定名称的线程对象;
  • Thread(Runnable target):创建一个基于 Runnable 接口实现类的线程对象;
  • Thread(Runnable target, String name):创建一个基于 Runnable 接口实现类,并具有指定名称的线程对象。

2.1.2 常用方法

void run():线程相关的代码写在该方法中,一般需要重写;

void start():启动当前线程;

static void sleep(long m):使当前线程休眠 m 毫秒;

void join():优先执行调用 join() 方法的线程。

Tips:run() 方法是一个非常重要的方法,它是用于编写线程执行体的方法,不同线程之间的一个最主要区别就是 run() 方法中的代码是不同的。

可翻阅官方文档以查看更多 API。

2.1.3 实例

通过继承 Thread 类创建线程可分为以下 3 步:

  1. 定义 Thread 类的子类,并重写该类的 run() 方法。run() 方法的方法体就代表了线程要完成的任务;
  2. 创建 Thread 子类的实例,即创建线程对象;
  3. 调用线程对象的 start 方法来启动该线程。

具体实例如下:

/*** @author colorful@TaleLin*/
public class ThreadDemo1 extends Thread {/*** 重写 Thread() 的方法*/@Overridepublic void run() {System.out.println("这里是线程体");// 当前打印线程的名称System.out.println(getName());}public static void main(String[] args) {// 实例化 ThreadDemo1 对象ThreadDemo1 threadDemo1 = new ThreadDemo1();// 调用 start() 方法,以启动线程threadDemo1.start();}}
代码块1234567891011121314151617181920212223

运行结果:

这里是线程体
Thread-0
代码块12

小伙伴们可能会有疑问,上面这样的代码,和普通的类实例化以及方法调用有什么区别的,下面我们来看一个稍微复杂些的实例:

/*** @author colorful@TaleLin*/
public class ThreadDemo2 {/*** 静态内部类*/static class MyThread extends Thread {private int i = 3;MyThread(String name) {super(name);}@Overridepublic void run() {while (i > 0) {System.out.println(getName() + " i = " + i);i--;}}}public static void main(String[] args) {// 创建两个线程对象MyThread thread1 = new MyThread("线程1");MyThread thread2 = new MyThread("线程2");// 启动线程thread1.start();thread2.start();}}
代码块123456789101112131415161718192021222324252627282930313233343536

运行结果:

线程2 i = 3
线程1 i = 3
线程1 i = 2
线程2 i = 2
线程1 i = 1
线程2 i = 1
代码块123456

代码中我们是先启动了线程 1,再启动了线程 2 的,观察运行结果,线程并不是按照我们所预想的顺序执行的。这里就要划重点了,不同线程,执行顺序是随机的。如果你再执行几次代码,可以观察到每次的运行结果都可能不同:

2.2 Runnable 接口

2.2.1 为什么需要Runnable接口

通过实现 Runnable 接口的方案来创建线程,要优于继承 Thread 类的方案,主要有以下原因:

  1. Java 不支持多继承,所有的类都只允许继承一个父类,但可以实现多个接口。如果继承了 Thread 类就无法继承其它类,这不利于扩展;
  2. 继承 Thread 类通常只重写 run() 方法,其他方法一般不会重写。继承整个 Thread 类成本过高,开销过大。

2.2.2 实例

通过实现 Runnable 接口创建线程的步骤如下:

  1. 定义 Runnable 接口的实现类,并实现该接口的 run() 方法。这个 run() 方法的方法体同样是该线程的线程执行体;
  2. 创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象;
  3. 调用线程对象的 start 方法来启动该线程。

具体实例如下:

/*** @author colorful@TaleLin*/
public class RunnableDemo1 implements Runnable {private int i = 5;@Overridepublic void run() {while (i > 0) {System.out.println(Thread.currentThread().getName() + " i = " + i);i--;}}public static void main(String[] args) {// 创建两个实现 Runnable 实现类的实例RunnableDemo1 runnableDemo1 = new RunnableDemo1();RunnableDemo1 runnableDemo2 = new RunnableDemo1();// 创建两个线程对象Thread thread1 = new Thread(runnableDemo1, "线程1");Thread thread2 = new Thread(runnableDemo2, "线程2");// 启动线程thread1.start();thread2.start();}}
代码块12345678910111213141516171819202122232425262728

运行结果:

线程1 i = 5
线程1 i = 4
线程1 i = 3
线程1 i = 2
线程2 i = 5
线程1 i = 1
线程2 i = 4
线程2 i = 3
线程2 i = 2
线程2 i = 1
代码块12345678910

2.3 Callable 接口

2.3.1 为什么需要Callable接口

继承 Thread 类和实现 Runnable 接口这两种创建线程的方式都没有返回值。所以,线程执行完毕后,无法得到执行结果。为了解决这个问题,Java 5 后,提供了 Callable 接口和 Future 接口,通过它们,可以在线程执行结束后,返回执行结果。

2.3.2 实例

通过实现 Callable 接口创建线程步骤如下:

  1. 创建 Callable 接口的实现类,并实现 call() 方法。这个 call() 方法将作为线程执行体,并且有返回值;
  2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,这个 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值;
  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程;
  4. 调用 FutureTask 对象的 get() 方法来获得线程执行结束后的返回值。

具体实例如下:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** @author colorful@TaleLin*/
public class CallableDemo1 {static class MyThread implements Callable<String> {@Overridepublic String call() { // 方法返回值类型是一个泛型,在上面 Callable<String> 处定义return "我是线程中返回的字符串";}}public static void main(String[] args) throws ExecutionException, InterruptedException {// 常见实现类的实例Callable<String> callable = new MyThread();// 使用 FutureTask 类来包装 Callable 对象FutureTask<String> futureTask = new FutureTask<>(callable);// 创建 Thread 对象Thread thread = new Thread(futureTask);// 启动线程thread.start();// 调用 FutureTask 对象的 get() 方法来获得线程执行结束后的返回值String s = futureTask.get();System.out.println(s);}}
代码块123456789101112131415161718192021222324252627282930313233

运行结果:

我是线程中返回的字符串
代码块1

3. 线程休眠

在前面介绍 Thread 类的常用方法时,我们介绍了 sleep() 静态方法,该方法可以使当前执行的线程睡眠(暂时停止执行)指定的毫秒数。

线程休眠的实例如下:

/*** @author colorful@TaleLin*/
public class SleepDemo implements Runnable {@Overridepublic void run() {for (int i = 1; i <= 5; i ++) {// 打印语句System.out.println(Thread.currentThread().getName() + ":执行第" + i + "次");try {// 使当前线程休眠Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {// 实例化 Runnable 的实现类SleepDemo sleepDemo = new SleepDemo();// 实例化线程对象Thread thread = new Thread(sleepDemo);// 启动线程thread.start();}}
代码块123456789101112131415161718192021222324252627282930

运行结果:

Thread-0:执行第1次
Thread-0:执行第2次
Thread-0:执行第3次
Thread-0:执行第4次
Thread-0:执行第5次
代码块12345

4. 线程的状态和生命周期

java.lang.Thread.Starte 枚举类中定义了 6 种不同的线程状态:

  1. NEW:新建状态,尚未启动的线程处于此状态;
  2. RUNNABLE:可运行状态,Java 虚拟机中执行的线程处于此状态;
  3. BLOCK:阻塞状态,等待监视器锁定而被阻塞的线程处于此状态;
  4. WAITING:等待状态,无限期等待另一线程执行特定操作的线程处于此状态;
  5. TIME_WAITING:定时等待状态,在指定等待时间内等待另一线程执行操作的线程处于此状态;
  6. TERMINATED:结束状态,已退出的线程处于此状态。

值得注意的是,一个线程在给定的时间点只能处于一种状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。

线程的生命周期,实际上就是上述 6 个线程状态的转换过程。下图展示了一个完整的生命周期:

5. 小结

通过本篇文章,我们知道了线程是操作系统能够进行运算调度的最小单位。线程也被称为轻量级进程。在 Java 中,可以以 3 种方式创建线程,分别是继承 Thread 类、实现 Runnable 接口以及实现 Callable 接口。可以使用静态方法 sleep() 让线程休眠。线程状态有 6 种,也有资料上说线程有 5 种,这部分内容我们按照 Java 源码中的定义 6 种来记忆即可。

欢迎关注「慕课网」,发现更多IT圈优质内容,分享干货知识,帮助你成为更好的程序员!

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

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

相关文章

WorkTool无障碍服务实现企业微信机器人接口

前言 想要实现一个企业微信机器人&#xff0c;如京东/拼多多福利群、美团瑞幸定时营销群、自助订单查询、智能咨询或社群管理机器人等&#xff0c;首先官方未提供外部群/客户群的机器人API&#xff0c;会话存档也只在一定场景下适用&#xff0c;及时使用会话存档也存在只能收不…

opencv+python物体检测【03-模仿学习】

仿照练习&#xff1a;原文链接 步骤一&#xff1a;准备图片 正样本集&#xff1a;正样本集为包含“识别物体”的灰度图&#xff0c;一般大于等于2000张&#xff0c;尺寸不能太大&#xff0c;尺寸太大会导致训练时间过长。 负样本集&#xff1a;负样本集为不含“识别物体”的…

微服务--Gateway网关学习

Gateway服务网关 为什么需要网关 网关功能&#xff1a; 身份认证和权限校验服务路由&#xff0c;负载均衡请求限流 网关的技术实现&#xff1a;在SpringCloud网关的实现包括两种&#xff1a; gatewayzuul Zuul是基于Servlet的实现&#xff0c;属于阻塞式编程。而SpringCloudGa…

车道线检测-E2E_LSFitting 论文学习笔记

论文&#xff1a;《End-to-end Lane Detection through Differentiable Least-Squares Fitting》 代码&#xff1a;https://github.com/wvangansbeke/LaneDetection_End2End 材料&#xff1a;https://zhuanlan.zhihu.com/p/94419168 特点&#xff1a; 拟合二次曲线&#xff1b…

专家说年轻人工资低是能力不行….

我们国家的很多专家总讲究语不惊人死不休&#xff0c;同时他们还很喜欢话风高速原地调头。 最近又有一个碉堡了的专家在大放厥词&#xff0c;就在前几天的首届长白山高峰论坛上中航基金副总经理邓海清发表批评年轻人的言论&#xff0c;邓老板是这么说的&#xff1a;很多年轻人…

synchronized 关键字-监视器锁 monitor lock

1.代码示例&#xff1a; package thread3;import java.util.Scanner;public class Test2 {public static Object object new Object();public static void main(String[] args) throws InterruptedException {Thread thread1 new Thread(() -> {Scanner scanner new Sca…

第五十一章 BFS进阶(一)——双端队列广搜

第五十一章 BFS进阶&#xff08;一&#xff09;——双端队列广搜一、原理二、例题1、问题2、分析三、代码一、原理 在介绍双端队列广搜之前&#xff0c;我们先回顾一下堆优化版本的dijkstradijkstradijkstra算法。 在这个算法中&#xff0c;我们使用的是小根堆来找到距离起点…

Windows/VM虚拟机安装黑群晖6.1-----保证有效而且简单操作

1视频&#xff1a;Windows/VM虚拟机安装黑群晖教程_哔哩哔哩_bilibili2:网址&#xff1a;Synology Web Assistant3&#xff1a;重新打开群晖操作步骤1&#xff1a;按着视频下载好资源后&#xff0c;按照视频操作&#xff0c;途中修改地方&#xff08;两个情况选择其中一个&…

Flowable进阶学习(九)数据对象DataObject、租户Tenant、接收任务ReceiveTask

文章目录一、数据对象DataObject二、租户 Tenant三、接收任务 ReceiveTask案例一、数据对象DataObject DataObject可以⽤来定义⼀些流程的全局属性。 绘制流程图&#xff0c;并配置数据对象&#xff08;不需要选择任意节点&#xff09; 2. 编码与测试 /*** 部署流程*/ Test…

函数/任意波形发生器 DG5072 技术资料

函数/任意波形发生器 DG5072 DG5000人性化的界面设计和键盘布局&#xff0c;给用户带来非凡体验&#xff1b;丰富的标准配置接口&#xff0c;可轻松实现仪器远程控制&#xff0c;为用户提供更多解决方案。 产品特性 4.3英寸16M真彩TFT液晶显示屏 350 MHz、250MHz、100 MHz或70…

微信卸载后重装的聊天记录还能找回吗?

很多人微信卸载后&#xff0c;问能不能恢复之前的聊天记录&#xff1f; 我想大家肯定都去百度搜索了&#xff0c;能搜出来可行的办法了么&#xff0c;没有是吧&#xff0c;那就看看我能不能帮到你&#xff0c;根据我的经验来解决。 答&#xff1a;理论上是不能的&#xff0c;因…

详细聊聊spring核心思想

犹记我当年初学 Spring 时&#xff0c;还需写一个个 XML 文件&#xff0c;当时心里不知所以然&#xff0c;跟着网上的步骤一个一个配置下来&#xff0c;配错一个看着 error 懵半天&#xff0c;不知所谓地瞎改到最后能跑就行&#xff0c;暗自感叹 tmd 这玩意真复杂。 到后来用上…

C语言入门教程||C语言 循环||C语言 函数

C语言 循环有的时候&#xff0c;可能需要多次执行同一块代码。一般情况下&#xff0c;语句是顺序执行的&#xff1a;函数中的第一个语句先执行&#xff0c;接着是第二个语句&#xff0c;依此类推。编程语言提供了允许更为复杂的执行路径的多种控制结构。循环语句允许我们多次执…

【Django】云笔记项目

一、介绍 用户可在系统中记录自己的笔记&#xff0c;用户的数据被存储在云笔记平台&#xff1b;用户和用户之间的数据为隔离存储&#xff08;登陆后才能使用相关笔记功能&#xff0c;且只能查阅自己的笔记&#xff09; 二、功能拆解 1、用户模块 注册&#xff1a;成为平台…

【Java 面试合集】简述下自定义异常的应用场景

简述下自定义异常的应用场景 1. 概述 如上图所示&#xff0c;我们想回答这个问题就要了解异常的基本结构。哪些是我们可以控制的&#xff0c;哪些是我们不能控制的。 也许有人会问了&#xff0c;其实在逻辑中可以多加判断&#xff0c;为什么要需要自定义呢。 其实判断的内容无…

跳跃游戏 II 解析

题目描述给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处:0 < j < nums[i] i j < n返回到达 nums[n - 1] 的…

【i2c协议介绍】

文章目录协议简单介绍五种速度模式master/slave和transmitter/receiver关系第一种情况&#xff1a;master作为transmitter&#xff0c;slave作为receiver第二种情况&#xff1a;当master作为receiver&#xff0c;slave作为transmitteri2c基本信号start产生stop信号数据传输有效…

基于OpenCV 的车牌识别

基于OpenCV 的车牌识别 车牌识别是一种图像处理技术&#xff0c;用于识别不同车辆。这项技术被广泛用于各种安全检测中。现在让我一起基于 OpenCV 编写 Python 代码来完成这一任务。 车牌识别的相关步骤 1. 车牌检测&#xff1a;第一步是从汽车上检测车牌所在位置。我们将使用…

《Spring揭秘》记录

IOC部分 IOC不等于IOC容器&#xff0c;即使不使用spring&#xff0c;我们也可以使用IOC&#xff0c;只不过spring提供了IOC容器实现。Spring的IoC容器的功能就包含一个提供依赖注入服务的IoC Service Provider。它提供两方面的支持&#xff0c;业务对象的构建管理和业务对象间的…

python读取.stl文件

目录 .1 文本方式读取 1.2 stl解析 1.3 stl创建 .2 把点转换为.stl .1 文本方式读取 代码如下 stl_path/home/pxing/codes/point_improve/data/003_cracker_box/0.stlpoints[] f open(stl_path) lines f.readlines() prefixvertex num3 for line in lines:#print (l…