什么是多线程?进程和线程的区别是什么?如何使用Java实现多线程?

在这里插入图片描述

文章目录

  • 前言
  • 我们为什么要使用线程而不是进程来实现并发编程
  • 什么是线程
  • 进程和线程的区别
  • 如何使用Java实现多线程
    • 创建线程
      • 1.创建一个继承 Thread 类的线程类
      • 2.实现 Runnable 接口
        • 匿名内部类方式实现 Runnable 接口
        • lambda 表达式实现 Runnable 接口
    • Thread 类的常见构造方法
    • Thread 的几个常见属性
    • 启动线程
    • 终止线程
      • 1.自定义标志位终止线程
      • 2.使用 Thread 自带的标志位终止线程
    • 线程等待

前言

前面我们了解了什么是进程以及如何实现进程调度,那么今天我将为大家分享关于线程相关的知识。在学习线程之前,我们认为进程是操作系统执行独立执行的单位,但其实并不然。线程是操作系统中能够独立执行的最小单元。只有掌握了什么是线程,我们才能实现后面的并发编程。

我们为什么要使用线程而不是进程来实现并发编程

实现并发编程为什么不使用多进程,而是使用多线程呢?主要体现在几个方面:

  • 创建一个进程的开销很大
  • 调度一个进程的开销很大
  • 销毁一个进程的开销很大

开销不仅体现在时间上,还体现在内存和 CPU 上。现在以”快“著称的互联网时代,这种大开销是不受人们欢迎的。那么为什么多线程就可以实现快捷的并发编程呢?

  • 共享资源:多个线程之间共用同一部分资源,大大减少了资源的浪费
  • 创建、调度、销毁的开销小:相较于进程的创建、调度和销毁,线程的创建、调度和销毁就显得很轻量,这样也大大节省了时间和资源的浪费
  • 现在的计算机 CPU 大多都是多核心模式,我们的多线程模式也更能利用这些优势

什么是线程

线程是操作系统能够独立调度和执行的最小执行单元。线程是进程内的一个执行流程,也可以看作是进程的子任务。与进程不同,线程在进程内部创建和管理,并且与同一进程中的其他线程共享相同的地址空间和系统资源。
只有当第一个线程创建的时候会有较大的开销,后面线程的创建开销就会小一点。并发编程会尽量保证每一个线程在不同的核心上单独执行,互不干扰,但也不可避免的出现在单核处理器系统中,线程在一个 CPU 核心上运行,它们通过时间片轮转调度算法使得多个线程轮流执行,给我们一种同时执行感觉。

线程是操作系统调度执行的基本单位

进程和线程的区别

一个进程中可以包含一个线程,也可以包含多个线程。

  1. 资源和隔离:进程是操作系统中的一个独立执行单位,具有独立的内存空间、文件描述符、打开的文件、网络连接等系统资源。每个进程都拥有自己的地址空间,进程间的数据不共享。而线程是进程内的执行流程,共享同一进程的地址空间和系统资源,可以直接访问和修改相同的数据。

  2. 创建和销毁开销:相对于进程,线程的创建和销毁开销较小。线程的创建通常只涉及创建一个新的执行上下文和一些少量的内存。而进程的创建需要分配独立的内存空间、加载可执行文件、建立进程控制块等操作,开销较大。

  3. 并发性和响应性:由于线程共享进程的地址空间,多个线程可以在同一进程内并发执行任务,共享数据和通信更加方便。因此,线程的切换成本较低,可以实现更高的并发性和响应性。而进程之间通常需要进程间通信(IPC)的机制来进行数据交换和共享,开销较大,响应性较低。

  4. 管理和调度:进程由操作系统负责管理和调度,每个进程之间是相互独立的。而线程是在进程内部创建和管理的,线程调度和切换由操作系统的线程调度器负责。线程的调度通常比进程的调度开销小,线程切换更快。

  5. 安全性和稳定性:由于进程之间相互独立,一个进程的崩溃不会影响其他进程的正常运行,因此进程具有更好的安全性和稳定性。而一个线程的错误或异常可能会导致整个进程崩溃。

前面我们所说的 PCB 其实也是针对线程来说的,一个线程具有一个 PCB 属性,一个进程可以含有一个或多个 PCB。

PCB 里的状态:上下文,优先级,记账信息,都是每个线程有自己的,各自记录自己的,但是同一个进程里的PCB之间,pid是一样的,内存指针和文件描述符表也是一样的。

如何使用Java实现多线程

在Java中使用一个线程大致分为以下几个步骤:

  1. 创建线程
  2. 启动线程
  3. 终止线程
  4. 线程等待

创建线程

在Java中执行线程操作依赖于 Thread 类。并且创建一个线程具有多种方法。

  1. 创建一个线程类继承自 Thread 类
  2. 实现 Runnable 接口

1.创建一个继承 Thread 类的线程类

class MyThread extends Thread {@Overridepublic void run() {System.out.println("这是一个MyThread线程");}
}

我们需要重写 run 方法,而 run 方法是指该线程要干什么。

创建实例对象

public class TreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();}
}

2.实现 Runnable 接口

创建一个线程我们不仅可以直接创建一个继承自 Thread 的线程类,我们也可以直接实现 Runnable 接口,因为通过源码我们可以知道 Thread 类也实现了 Runnable 接口。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们可以将 Runnable 作为一个构造方法的参数传进去。

class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("这是一个线程");}
}public class ThreadDemo2 {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());}
}

但是这种实现 Runnable 接口的方式会显得很麻烦,因为每个线程执行的内容大多是不同的,所以我们可以采用下面两种方式来实现 Runnable 接口。

  • 匿名内部类
  • lambda 表达式

匿名内部类方式实现 Runnable 接口

public class ThreadDemo3 {public static void main(String[] args) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("这是一个线程");}});}
}

lambda 表达式实现 Runnable 接口

public class ThreadDemo4 {public static void main(String[] args) {Thread t = new Thread(() -> {System.out.println("这是一个线程");});}
}

Thread 类的常见构造方法

方法说明
Thread()创建对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target,String name)使用 Runnable 对象创建线程对象,并命名
Thread(ThreadGroup group,Runnable target线程可以被用来分组管理,分好的组即为线程组,这个我们目前了解即可

在这里插入图片描述

Thread 类有很多构造方法,大家有兴趣可以自己去看看。

Thread 的几个常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台进程isDaemon()
是否存活isAlive
是否被中断isInterrupted()
  • ID 是线程的唯一标识,不同线程不会重复
  • 名称是各种调试工具用到
  • 状态表示线程当前所处的一个情况
  • 优先级高的线程理论上来说更容易被调度到
  • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
  • 是否存活,即简单的理解,为 run 方法是否运行结束了
  • 线程的中断问题,下面我们进一步说明

我们前面创建的都是前台进程,我们可以感知到的,那么什么叫做后台进程呢?

后台进程是指在计算机系统中以低优先级运行且不与用户交互的进程。与前台进程相比,后台进程在运行时不会占据用户界面或终端窗口,并且通常在后台默默地执行任务。

后台进程通常用于执行系统服务、长时间运行的任务、系统维护或监控等。它们在后台运行,不需要用户的直接参与或操作,而且可以持续运行,即使用户退出或注销系统。

启动线程

我们上面只是创建了线程,要想让线程真正的起作用,我们需要手动启动线程。线程对象.start()

class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("这是一个线程");}
}public class ThreadDemo2 {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());t.start();}
}

在这里插入图片描述

这里有人看到输出结果可能会问了,这跟我直接调用 run 方法好像没什么区别吧?我们这个代码肯定看不出来区别,所以我们稍稍修改一下代码。

class MyRunnable implements Runnable {@Overridepublic void run() {while(true) {System.out.println("hello MyThread!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}public class ThreadDemo2 {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());t.start();while(true) {System.out.println("hello main!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

这里 Thread.sleep() 的作用是使线程停止一会,防止进行的太快,我们不容易看到结果,并且这里的 Thread.sleep() 方法还需要我们抛出异常

在这里插入图片描述

我们可以看到这里的执行结果是 main 线程和 t 线程都执行了,而不是只执行其中的一个线程。不仅如此,这两个线程之间没有什么规定的顺序执行,而是随机的,这种叫做抢占式执行,每个线程都会争抢资源,所以会导致执行顺序的不确定,也正是因为多线程的抢占式执行,会导致后面的线程安全问题。

那么我们再来看看,如果直接调用 run 方法,而不是 start 方法会有什么结果。

在这里插入图片描述

当直接调用 run 方法的话,也就只会执行 t 对象的 run 方法,而没有执行 main 方法后面的代码,也就是说:当直接调用 run 方法的时候,线程并没有真正的启动,只有调用 start 方法,线程才会启动。

我们也可以通过 Java 自带的 jconsle 来查看当前有哪些Java进程。

我们需要找到 jconsole.exe 可执行程序。通常在这个目录下C:\Program Files\Java\jdk1.8.0_192\bin
在这里插入图片描述

我们也可以点进来看看。

在这里插入图片描述

在这里插入图片描述

终止线程

通常当主线程 main 执行完 mian 方法之后或者其他线程执行完 run 方法之后,线程就会终止,但是我们也可以在这之前手动终止线程。但是我们这里终止线程并不是立刻终止,也就相当于这里只是建议他这个线程停止,具体要不要停止得看线程的判断。

  • 自定义标志位来终止线程
  • 使用 Thread 自带的标志位来终止线程

1.自定义标志位终止线程

public class ThreadDemo4 {private static boolean flg = false;  //定义一个标志位public static void main(String[] args) {Thread t = new Thread(() -> {while(!flg) {System.out.println("hello mythread!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();System.out.println("线程开始");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}flg = true;  /修改标志位使线程终止System.out.println("线程结束");}
}

在这里插入图片描述

2.使用 Thread 自带的标志位终止线程

可以使用 线程对象.interrupt() 来申请终止线程。并且使用 Thread.currentThread,isInterrupted() 来判断是否终止线程。

  • Thread.currentThread() 获取到当前线程对象
public class ThreadDemo4 {private static boolean flg = false;public static void main(String[] args) {Thread t = new Thread(() -> {while(!Thread.currentThread().isInterrupted()) {System.out.println("hello mythread!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();System.out.println("线程开始");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}t.interrupt();System.out.println("线程结束");}
}

在这里插入图片描述

发现了没,这里抛出了异常,但是线程并没有终止,为什么呢?问题出在哪里呢?

其实这里问题出在 Thread.sleep 上,如果线程在 sleep 中休眠,此时调用 interrupt() 会终止休眠,并且唤醒该线程,这里会触发 sleep 内部的异常,所以我们上面的运行结果就抛出了异常。那么为什么线程又被唤醒了呢?

interrupt 会做两件事:

  1. 把线程内部的标志位给设置成true,也就是 !Thread.current.isInterrupt() 的结果为true
  2. 如果线程在进行 sleep ,就会触发吟唱,把 sleep 唤醒

但是 sleep 在唤醒的时候,还会做一件事,把刚才设置的这个标志位,再设置回false(清空标志位),所以就导致了线程继续执行。那么如何解决呢?

很简单,因为 sleep 内部发生了异常,并且我们捕获到了异常,所以我们只需要在 catch 中添加 break 就行了。

try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();break;}

在这里插入图片描述

这也就相当于,我 t 线程拒绝了你的终止请求。

线程等待

在多线程中,可以使用 线程对象.join() 来使一个线程等待另一个线程执行完或者等待多长时间后再开始自己的线程。

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等 millis 毫秒
public void join(long millis,int nanos)同理,但可以更高精度
public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread(() -> {for(int i = 0; i < 5; i++) {System.out.println("hello mythread!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();try {t.join();} catch (InterruptedException e) {throw new RuntimeException(e);}for(int i = 0; i < 5; i++) {System.out.println("hello main!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

在这里插入图片描述

在那个线程中调用的 线程对象.join() 就是哪个线程等待,而哪个线程调用 join() 方法,那么这个线程就是被等待的。而这个等待的过程也被称为阻塞。如果在执行 join 的时候,调用 join 方法的线程如果已经结束了,那么就不会发生阻塞。

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

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

相关文章

T113-S3-RTL8211网口phy芯片调试

目录 前言 一、RTL8211介绍 二、硬件连接 三、设备树配置 四、内核配置 五、phy芯片配置 六、调试问题 总结 前言 在嵌入式系统开发中&#xff0c;网络连接是至关重要的一部分。T113-S3开发板搭载了RTL8211系列的网口PHY芯片&#xff0c;用于实现以太网连接。在开发过程…

C++ QT(二)

目录 Qt 控件按钮QPushButton控件简介用法示例运行效果 QToolButton控件简介用法示例运行效果 QRadioButton控件简介用法示例运行效果 QCheckBox控件简介用法示例运行效果 QCommandLinkButton控件简介用法示例运行效果 QDialogButtonBox控件简介用法示例运行效果 输入窗口部件Q…

用 React+ts 实现无缝滚动的走马灯

一、走马灯的作用 走马灯是一种常见的网页交互组件&#xff0c;可以展示多张图片或者内容&#xff0c;通过自动播放或者手动切换的方式&#xff0c;让用户能够方便地浏览多张图片或者内容。 本次实现的不是轮播图而是像传送带一样的无限滚动的形式。 二、需求梳理 走马灯可设…

Go Gin 中使用 JWT

一、JWT JWT全称JSON Web Token是一种跨域认证解决方案&#xff0c;属于一个开放的标准&#xff0c;它规定了一种Token实现方式&#xff0c;目前多用于前后端分离项目和OAuth2.0业务场景下。 二、为什么要用在你的Gin中使用JWT 传统的Cookie-Sesson模式占用服务器内存, 拓展性…

uniapp实现自定义导航内容高度居中(兼容APP端以及小程序端与胶囊对齐)

①效果图如下 1.小程序端与胶囊对齐 2.APP端内容区域居中 注意&#xff1a;上面使用的是colorui里面的自定义导航样式。 ②思路&#xff1a; 1.APP端和小程序端走不同的方法&#xff0c;因为小程序端要计算不同屏幕下右侧胶囊的高度。 2.其次最重要的要清晰App端和小程序端…

【数学建模】清风数模更新5 灰色关联分析

灰色关联分析综述 诸如经济系统、生态系统、社会系统等抽象系统都包含许多因素&#xff0c;系统整体的发展受各个因素共同影响。 为了更好地推动系统发展&#xff0c;我们需要清楚哪些因素是主要的&#xff0c;哪些是次要的&#xff0c;哪些是积极的&#xff0c;哪些是消极的…

网络基础——网络的由来与发展史

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 作者会持续更新网络知识和python基础知识&#xff0c;期待你的关注 目录 一、网络的由来 二、计算机网络的发展史 1、第一阶段 2、第二阶段 3、第三阶段 前言 每天都是使用网络&#xff0c;那么你知道网络…

FPGA----Vivado SDK创建并使用静态链接库(C/C++代码移植)

1、在进行SoC开发时&#xff0c;PS端的C/C代码可能涉及到核心算法需要移植操作&#xff0c;为此&#xff0c;本文讲述了如何将C/C代码打包为.a文件供程序调用 2、文章以我的程序为例&#xff0c;逐步讲述代码生成静态链接库并调用的方法。 下面是我程序的目录结构&#xff0c…

RocketMQ 延迟消息

RocketMQ 延迟消息 RocketMQ 消费者启动流程 什么是延迟消息 RocketMQ 延迟消息是指&#xff0c;生产者发送消息给消费者消息&#xff0c;消费者需要等待一段时间后才能消费到。 使用场景 用户下单之后&#xff0c;15分钟未支付&#xff0c;对支付账单进行提醒或者关单处理…

PostgreSQL查询慢sql原因和优化方案

PostgreSQL sql查询慢优化方案有一下几种解决方案&#xff1a; 1.关闭会话 查询慢sql的执行会话&#xff0c;关闭进程。 查看数据库后台连接进程 SELECT count(*) FROM pg_stat_activity;SELECT * FROM pg_stat_activity; 查看数据库后台连接进程&#xff0c;但是此条SQL不…

用HARU-Net增强核分割:一种基于混合注意的残差u块网络

文章目录 Enhancing Nucleus Segmentation with HARU-Net: A Hybrid Attention Based Residual U-Blocks Network摘要本文方法损失函数后处理消融实验 Enhancing Nucleus Segmentation with HARU-Net: A Hybrid Attention Based Residual U-Blocks Network 摘要 核图像分割是…

W6100-EVB-PICO 做TCP Server进行回环测试(六)

前言 上一章我们用W6100-EVB-PICO开发板做TCP 客户端连接服务器进行数据回环测试&#xff0c;那么本章将用开发板做TCP服务器来进行数据回环测试。 TCP是什么&#xff1f;什么是TCP Server&#xff1f;能干什么&#xff1f; TCP (Transmission Control Protocol) 是一种面向连…

zabbix监控安装部署

目录 一、环境 二、配置 1.配置yum源&#xff0c;这里用的清华的 2.过滤一下安装包&#xff0c;查看依赖包 安装依赖包 3.配置数据库 开机自启 创建数据库 创建用户 授权 导入数据到数据库 查看zabbix数据库有没有表和数据 4.修改zabbix配置文件 1.修改zabbix配置…

去趋势化一个心电图信号、信号功率谱、低通IIR滤波器并平滑信号、对滤波器引起的延迟进行补偿研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

SPM实现framework自动管理和分发

一、前言 Swift Package Manager (SPM) 是苹果官方提供的用于管理 Swift 项目的依赖关系和构建过程的工具。它是一个集成在 Swift 编程语言中的包管理器&#xff0c;用于解决在开发过程中管理和构建包依赖项的需求。 那么如何使用SPM管理和分发Objective C编写的二进制库呢&a…

不同路径 II——力扣63

class Solution {public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int n=

【APITable】教程:创建并运行一个自建小程序

1.进入APITable&#xff0c;在想要创建小程序的看板页面点击右上角的【小程序】&#xff0c;进入小程序编辑页面。 2.创建一个新的小程序区。 点击【 添加小程序】 点击创建小程序&#xff0c;选择模板&#xff0c;输入名字。 3.确定后进入小程序部署引导页面。 4.打开Xshell 7…

初识鸿蒙跨平台开发框架ArkUI-X

HarmonyOS是一款面向万物互联时代的、全新的分布式操作系统。在传统的单设备系统能力基础上&#xff0c;HarmonyOS提出了基于同一套系统能力、适配多种终端形态的分布式理念&#xff0c;能够支持手机、平板、智能穿戴、智慧屏、车机等多种终端设备&#xff0c;提供全场景&#…

99. for循环练习题-3种方式输出0-9

【目录】 文章目录 99. for循环练习题-3种方式输出0-91. for循环和while循环的区别2. 输出 0~(n-1)的数字2.1 基础代码2.2 自定义函数代码2.3 异常处理语句代码 【正文】 99. for循环练习题-3种方式输出0-9 1. for循环和while循环的区别 for循环和while循环都用于重复执行特定…

idea模板的使用(配置xml文件模板)

1. 问题的引出 我们在日常项目中可以发现&#xff0c;sql映射文件和mybatis主配置文件&#xff0c;以及application.yml文件中有很多固定不变的内容&#xff0c;为了方面使用&#xff0c;所以可以把这些xml文件设置为模板 2. 创建模板的步骤 按照图片一步一步进行即可 点击…