当前位置: 首页 > news >正文

多线程(一):线程的基本特点线程安全问题ThreadRunnable

目录

1、线程的引入

2、什么是线程

3、线程的基本特点

4、线程安全问题 

5、创建线程

5.1 继承Thread类,重写run

5.1.1 创建Thread类对象

5.1.2 重写run方法

5.1.3 start方法创建线程

5.1.4 抢占式执行

5.2 实现Runnable,重写run【解耦合】★★★

6、知识拓展

6.1 拓展一:名词解释——api

6.2 拓展二:异常处理方式

6.3 拓展三:名词解释——客户端&服务器

 6.4 拓展四:高内聚,低耦合

6.4.1 耦合

6.4.2 内聚


1、线程的引入

大家都知道,当代CPU为多任务处理器,具备多个核心,而为了充分发挥CPU多核心的性能,避免出现“一核有难,多核围观”的情况,“并发编程”就成为刚需。

而通过多进程的方式,可以实现“并发编程”的效果。

虽然说多进程的方式可以实现“并发编程”,但是要知道,进程整体是一个比较“重量级”的概念,如果频繁的创建与销毁,开销是很大的。

尤其是对于服务器来说,一个服务器会为多个客户端提供服务,服务的客户多了,进程的创建与销毁操作自然也会增多。

举个例子:此时,我们打开了百度的网页,但是,这时全国甚至全球会有大量的用户与我们进行相同的操作,会有大量用户对服务器发送大量的请求,大量的进行创建与销毁操作,如果采用多进程方式的话,开销是很大的。

为了解决上述问题,引入一个轻量级的概念——线程(thread)。

2、什么是线程

线程(thread)又称为轻量级进程。

也就是说,线程是一个轻量级的东西,它的创建与销毁的开销要比进程小得多。

因此,可以通过多线程的方式,来实现“并发编程”。

3、线程的基本特点

上篇博客说到,一个进程,相当于一个要执行的任务。

而,一个线程,也相当于一个要执行的任务。

线程与进程的区别如下:

  • 进程包含线程:每个进程中,都会有一个或者多个线程。且至少有一个线程,这个线程在进程创建时随进程一起创建,称为主线程。
  • 进程是操作系统资源分配的基本单位,每个进程都会分配一定的CPU资源、内存资源、硬盘(文件描述符表)资源、网络带宽资源.....。也就是说,在进程创建时,需要申请资源;在进程销毁时,需要释放资源。(会增大系统开销)
  • 而对于线程来说,在进程内部管辖的多个线程之间,会共享进程分配到的资源。对于线程,只是在第一个线程创建时(随进程创建时创建的主线程)需要申请资源,后续再创建的线程,不需要进行资源申请操作。且只有所有的线程都销毁(进程销毁)时,才会释放资源,运行过程中销毁某个进程,也不会释放资源。(系统开销低)
  • 进程和进程间,每个进程分配到的资源都是各自独立的,彼此之间互不干扰,具有稳定性。
  • 进程内部的线程间,会出现相互影响的情况,具有“线程安全问题”
  • 上文所讲的“进程调度”,准确的来说,其实是“线程调度”(当一个进程中只有一个线程时,可以称为“进程调度”)。也就是说,线程是CPU上调度执行的基本单位。如果一个进程中有多个线程,那么这些线程是各自去CPU上调度执行的(可能多个线程由1个核心执行(并发),也可能多个线程由多个核心同时执行(并行),也可能在不同的CPU上来回切换),具体线程是怎么调度执行的,由操作系统内部“调度器”自行完成,程序猿感知不到也干预不了。
  • 每个线程,都会有属于自己单独的调度相关的信息:线程状态、线程上下文、线程优先级、线程记账信息。(也就是说,如果一个进程中有10个线程,就会有10份这样的信息)。但是,一个进程中的线程,共用一个文件描述符表和内存指针。

4、线程安全问题 

对于线程安全问题,先举个例子:

一个房间的桌子上放着100只烧鸡,把小明同学叫来,让他把这100只烧鸡全部吃完(小明相当于一个线程),但是一个人吃100只鸡,显然效率很低。于是,再把小刚同学叫来(再创建一个线程),让小刚和小明共同把这100只鸡吃完。此时,两个人吃100只鸡,显然比一个人吃100只鸡的效率要高的多。如果再叫来两三个其他同学,这时的效率就会更高。但是如果一直再叫来其他同学,比如叫到了50名同学,50名同学共同吃这100只鸡,其中两人都想吃同一只鸡,这两人间就会发生冲突(即线程安全问题),甚至冲突过大会把桌子掀翻,这时所有的人都吃不了鸡了(直接带走进程,所有线程无法继续工作)。

综上,总结如下:

  • 虽然多线程的方式能够提高工作效率,但是也并非“线性增长”,当一个进程中的线程过多时,线程与线程间就会出现互相影响的情况,会拖慢效率,甚至会抛出异常使整个进程终止(如果及时捕捉到异常,也是不会终止的)。
  • 线程数目如果太多,线程的调度开销也会非常明显,会因为调度开销拖慢程序性能。

5、创建线程

线程,是操作系统提供的概念,同时操作系统也提供了一些线程相关的api供程序员使用。

操作系统提供的原生api是C语言写的,并且不同操作系统所提供的线程api是不同的,是不是我们Java程序猿就得去学习C语言呢?

并不是的,Java对操作系统提供的线程api统一进行了封装,在标准库中提供了Thread类,我们可以通过Thread类来创建和使用多线程。

而创建线程的方式有两种:

  1. 继承Thread,重写run
  2. 实现Runnable,重写run

5.1 继承Thread类,重写run

5.1.1 创建Thread类对象

Thread类被封装在了java.lang包中,java.lang是Java的核心包,包含了String、Math、System、Thread、Runnable等等,这个包中的类被自动导入到每个Java程序中,无需显式导入。所以当我们使用Thread类时,不会自动显示导入包。

5.1.2 重写run方法

作为程序员,我们需要创建一个类继承于Thread并且重写其中的run方法,在run这个方法中,我们可以根据自己的思维将这个线程要做的任务写在这个run方法中。

这个run方法,就相当于线程的入口。

为后续观察多线程的状态,这里使用死循环的方式打印“hello thread”,再使用Thread中静态的sleep方法休眠1秒(防止CPU红温)。

使用sleep方法会抛出受查异常,解决方法有两个:

  1. throws:进行异常声明
  2. try-catch:进行异常捕获

但是由于run为重写方法,不能使用throws在函数头声明,只能使用try-catch捕获。

5.1.3 start方法创建线程

start方法的作用是真正创建一个新的进程,相当于多了一个执行流,多了一个干活的人,让代码能够“一心两用”,同时做两件事。

在main方法(主线程)中使用Thread对象调用start方法创建线程,并且在main方法中循环打印“hello main”,观察多线程现象。

注意,start的作用才是创建线程,run方法只是线程的入口,不是创建线程。

如果按照我们之前学习的程序运行逻辑,程序遇到死循环就会一直停留在那里(单线程模式),但是我们现在创建了多个线程,会发生什么样的情况呢?

多线程运行:

运行后,“hello main”和“hello thread”无规律交替打印,这就是多线程。

class MyThread extends Thread{@Overridepublic void run() {//线程入口while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
public class Demo1 {public static void main(String[] args) throws InterruptedException {Thread thread = new MyThread();//start -> 真正的创建线程thread.start();while (true) {System.out.println("hello main");Thread.sleep(1000);}}
}

5.1.4 抢占式执行

观察到,“hello main”和“hello thread”的打印是随机的,也就是,这两个线程的调度是随机的,谁先执行,谁后执行,都是无法预测的,我们称这种情况为“抢占式执行”,通俗来说,就是谁先抢到谁就先执行。

我们唯一能做的就是给线程设置优先级,但是对于操作系统来说,也只是仅供参考,不会一定的按照优先级的顺序来调度执行。

5.2 实现Runnable,重写run【解耦合】★★★

我们可以把要重写的run方法抽象出来,使用自定义类实现Runnable接口,在类中重写run方法(即要完成的任务),将要完成的任务和线程分离开来,实现与线程Thread的解耦合。

就是仅仅把runnable当做一个任务,单纯的把任务抽象到runnable接口的run方法中,最后线程还是要靠Thread来创建。

这样解耦合的有以下优点:

  1. 将所要完成的任务和线程分类开来,而不是把任务直接写到线程当中
  2. 以后可以通过其他方式执行该任务(不一定是在线程中),使线程是线程,任务是任务。
  3. 方便以后修改任务时不会影响到线程,方便代码的维护(容易改,不会一改一大片)
class MyRunnable implements Runnable {@Overridepublic void run() {//run --> 相当于线程的入口while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
public class Demo2 {public static void main(String[] args) throws InterruptedException {//任务Runnable runnable = new MyRunnable();//线程Thread thread = new Thread(runnable);//start --> 真正的创建线程thread.start();while (true) {System.out.println("hello main");Thread.sleep(1000);}}
}

6、知识拓展

6.1 拓展一:名词解释——api

上文很多地方都提到了api,但是大家都知道啥是api嘛?

api(application programming interface),应用程序编程接口。

  • 通俗来说,api就是别人写的一些函数/类,你直接拿过来就能用。

api是一个广义的概念,操作系统会提供api、标准库会提供api、第三方库会提供api、其他各种开源项目会提供api、甚至工作中项目组给你的代码中也会提供api。

  • api也可以理解为,别人给你提供的库/程序,你都能用来干啥。

举个例子:对于同班同学,你可以给他在微信上发消息、可以问他题、可以和他聊天、....,这是你同学向你提供的api;你谈了个对象,你可以和你的对象亲亲抱抱举高高....,这是你对象向你提供的api。

  • 而基于api,你可以用来编程(api的目的就是用于编程)。

比如接上例,基于你同学或者对象向你提供的api,你可以做出规划(编程):周末约同学打球;周末约对象看电影......

而对于Java程序猿的我们,我们可以使用标准库向我们提供的api去编程,比如ArrayList、StringBuffer、.......

在计算机界,Demo/Sample/quick start 的意思是示例、演示的意思,告诉我们如何使用。

test是更为详细的测试过程。

6.2 拓展二:异常处理方式

在上文中,我们提到对于受查异常有两种处理方式:

  1. throws
  2. try-catch

当我们使用IDEA进行自动的异常处理时,它是这样处理的:

它在catch中又重新拋了一个新的异常,只不过拋了个非受查的异常,所以没有再编译报错了,这种方法仅仅是满足了语法的要求,但是对于异常来说,就相当于没处理异常。在实际开发中,我们并不会这么干~

在实际工作中,通常会这样处理异常:

  1. 记录异常信息作为日志,后续根据日志调查问题。——使程序仍然正常执行,不会因为这个异常就终止。(不交给jvm处理)(服务器是7*24小时运行的,如果服务器因为异常导致崩溃,就无法给客户提供服务,这对于服务器来说非常关键)
  2. 进行重试。(有的异常是概率性发生的,如:网络抖动原因)
  3. 报警机制——如果是特别严重的问题,程序会立即通知程序猿处理(通过写代码来以短信、电话、微信等方式通知程序猿)。

6.3 拓展三:名词解释——客户端&服务器

客户端(client),服务器(server)指的两个程序(两个软件),这两个程序,通过配合完成一些工作。

客户端向服务器发送的数据,称为“请求”(request)。

服务器向客户端返回的数据,称为“响应”(response)。

客服端和服务器的主要区别如下:

  1. 主动发起请求的一方叫做客户端。被动接受请求,返回响应的一方叫做服务器。
  2. 通常一个服务器,给多个客户端提供服务。
  3. 服务器,不知道客户端来不来,啥时候来,所以只能将程序一直持续的运行下去,即7*24小时的跑(007)。(正因此,异常导致服务器崩溃的后果是非常严重的,必须将异常处理好)

 6.4 拓展四:高内聚,低耦合

6.4.1 耦合

耦合,指两个东西的关联程度。关联度越高,耦合就越大;关联度越低,耦合就越小。

在代码中,我们希望代码间是低耦合的(解耦),因为在开发中,代码是经常会修改的,低耦合的代码可维护性高(也就是好改),要修改代码的话,改一小部分就行,能够防止“改一个,改坏一片”的情况发生。

举个例子:

你结婚后,你媳妇生病住院了,你只能立刻放下手中的活,到医院来,照顾她、陪伴她,什么工作也干不了。因为你媳妇对你来说是很主要的人,你媳妇的生病对你的工作/生活影响很大,你必须放下你手头的事,哪怕再紧急的工作也得放下。

这就说明,你和你媳妇是高耦合,你媳妇出现了状况,对你的影响很大,你啥事也干不了。

而,如果你高中时的白月光发了个朋友圈说她生病住院了,对你来说呢,你只是点了个赞,评论了句“早日康复”,接着放下手机回头就忘了这件事,对你一点影响也没有。

这就说明,你和你高中的白月光是低耦合,她出现了啥状况,对你一定影响也没有。

6.4.2 内聚

内聚是指有相同的功能、逻辑关系的东西的集中程度。

代码中,我们希望高内聚,将相同逻辑、功能的或者有关联的代码放到一起,

而不是这放一块,那放一块的(低内聚)。

举个例子:

你结婚有了孩子后,你媳妇这个人她比较懒,总是把衣服这扔一件那扔一件的,有的衣服在沙发,有的衣服在床上,有的衣服在椅子上,有的还在沙发缝里。有一天,你媳妇让你给孩子拿一件衣服,由于衣服哪都有,你非常的痛苦,遍历了整个屋子都没找到孩子的衣服在哪。

这就反应的是低内聚。

后来,你媳妇变得贤惠了,把衣服都知道收拾整理好放到衣柜了,你再给孩子找衣服的时候,直接去衣柜里拿就行了。

这反应的就是高内聚。

综上:高内聚(一个模块内,有关联的东西放在一块),低耦合(模块之间,依赖尽量小,影响尽量小)。


END

相关文章:

多线程(一):线程的基本特点线程安全问题ThreadRunnable

目录 1、线程的引入 2、什么是线程 3、线程的基本特点 4、线程安全问题 5、创建线程 5.1 继承Thread类,重写run 5.1.1 创建Thread类对象 5.1.2 重写run方法 5.1.3 start方法创建线程 5.1.4 抢占式执行 5.2 实现Runnable,重写run【解耦合】★…...

启动hadoop集群出现there is no HDFS_NAMENODE_USER defined.Aborting operation

解决方案 在hadoop-env.sh中添加 export HDFS_DATANODE_USERroot export HDFS_NAMENODE_USERroot export HDFS_SECONDARYNAMENODE_USERroot export YARN_RESOURCEMANAGER_USERroot export YARN_NODEMANAGER_USERroot 再次运行即可。...

Redis实现短信登录解决状态登录刷新的问题

Redis实现短信登录 获取验证码控制层 /*** 发送手机验证码*/PostMapping("/code")public Result sendCode(RequestParam("phone") String phone) {// TODO 发送短信验证码并保存验证码return userService.sendCode(phone);} 获取验证码服务层 Result sendC…...

33. java快速排序

1. 前言 排序算法是数据结构中最基础的算法,快速排序则是面试中最常见的排序算法。无论是校招面试还是社招面试,快速排序算法的出现频率远高于其他算法,而且经常会要求候选人白板手写实现算法。快速排序算法的核心是分治处理,重点是分析时间复杂度。 2. 快速排序算法 面试…...

普通二叉搜索树的模拟实现【C++】

二叉搜素树简单介绍 二叉搜索树又称二叉排序树,是具有以下性质的二叉树: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 它的左右子树也分别为二叉搜索树 注意…...

unity 介绍Visual Scripting Scene Variables

Visual Scripting中的场景变量是指在Unity中使用可视化脚本时,能够在不同场景间传递和存储数据的变量。这些变量可以用来跟踪游戏状态、玩家信息或其他动态数据,允许开发者在不编写代码的情况下创建复杂的游戏逻辑。 场景变量的优势包括: 1…...

linux服务器部署filebeat

# 下载filebeat curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.17.23-linux-x86_64.tar.gz # 解压 tar xzvf filebeat-7.17.23-linux-x86_64.tar.gz# 所在位置(自定义) /opt/filebeat-7.17.23-linux-x86_64/filebeat.ym…...

个人获取Wiley 、ScienceDirect、SpringerLink三个数据库文献的方法

在同学们的求助文献中经常出现Wiley 、ScienceDirect、SpringerLink这三个数据库文献。本文下面就讲解一下个人如何不用求助他人自己搞定这三个数据库文献下载的方法。 个人下载文献首先要先获取数据库资源,小编平时下载文献是通过科研工具——文献党下载器获取的数…...

Java五子棋

目录 一:案例要求: 二:代码: 三:结果: 一:案例要求: 实现一个控制台下五子棋的程序。用一个二维数组模拟一个15*15路的五子棋棋盘,把每个元素赋值位“┼”可以画出棋…...

【从0开始自动驾驶】用python做一个简单的自动驾驶仿真可视化界面

【从0开始自动驾驶】用python做一个简单的自动驾驶仿真可视化界面 废话几句废话不多说,直接上源码目录结构init.pysimulator.pysimple_simulator_app.pyvehicle_config.json 废话几句 自动驾驶开发离不开仿真软件成品仿真软件种类多https://zhuanlan.zhihu.com/p/3…...

一拖二快充线:单接与双接的多场景应用

在当代社会,随着智能手机等电子设备的普及,充电问题成为了人们关注的焦点。一拖二快充线作为一种创新的充电解决方案,因其便捷性与高效性而受到广泛关注。本文将深入探讨一拖二快充线的定义、原理以及在单接与双接手机场景下的应用&#xff0…...

接口自动化测试概述

目录 1 接口自动化测试简介 1.1 什么是接口 1.2 什么是接口测试 1.3 为什么要做接口测试 1.4 什么是接口测试自动化 1.5 为什么要做接口测试自动化 2 接口自动化测试规范 2.1 文档准备 2.1.1 需求文档 2.1.2 接口文档 2.1.3 UI 交互图 2.1.4 数据表设计文档 2.2 明…...

Fingerprint.js:精准用户识别的浏览器指纹技术

在数字化时代,用户识别成为互联网服务中不可或缺的一环。随着隐私保护意识的增强,传统的用户识别方法如Cookies和本地存储面临着越来越多的挑战。而Fingerprint.js作为一种创新的浏览器指纹技术,以其高效、隐私友好的特性,逐渐在个…...

Gson将对象转换为JSON(学习笔记)

JSON有两种表示结构,对象和数组。对象结构以"{"大括号开始,以"}"大括号结束。中间部分由0或多个以”,"分隔的”key(关键字)/value(值)"对构成,关键字和值之间以":"分隔,语法结…...

什么是IPv6

目前国内的网络正在快速的向IPv6升级中,从网络基础设施如运营商骨干网、城域网,到互联网服务商如各类云服务,以及各类终端设备厂商如手机、电脑、路由器、交换机等。目前运营商提供的IPv6线路主要分为支持前缀授权和不支持前缀授权两种。 说…...

python画图|放大和缩小图像

在较多的画图场景中,需要对图像进行局部放大,掌握相关方法非常有用,因此我们很有必要一起学习 【1】官网教程 首先是进入官网教程,找到学习资料: https://matplotlib.org/stable/gallery/subplots_axes_and_figures…...

Mac优化清理工具CleanMyMac X 4.15.6 for mac中文版

CleanMyMac X 4.15.6 for mac中文版下载是一款功能更加强大的系统优化清理工具,软件只需两个简单步骤就可以把系统里那些乱七八糟的无用文件统统清理掉,节省宝贵的磁盘空间。CleanMyMac X 4.15.6 for mac 软件与最新macOS系统更加兼容,流畅地…...

资质申请中常见的错误有哪些?

在申请建筑资质的过程中,企业可能会犯一些常见的错误,以下是一些需要避免的错误: 1. 资料准备不充分: 申请资质需要提交大量的资料,包括企业法人资料、财务报表、业绩证明等。资料不齐全或不准确都可能导致申请失败。…...

基于单片机的多路温度检测系统

**单片机设计介绍,基于单片机CAN总线的多路温度检测系统设计 文章目录 前言概要功能设计设计思路 软件设计效果图 程序设计程序 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探…...

面试题:通过栈实现队列

题目描述: 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty): 实现 MyQueue 类: void push(int x) 将元素 x 推到队列的末尾int pop() 从队列的开头移除并返回元素i…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

EtherNet/IP转DeviceNet协议网关详解

一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...

Python 包管理器 uv 介绍

Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

【网络安全】开源系统getshell漏洞挖掘

审计过程: 在入口文件admin/index.php中: 用户可以通过m,c,a等参数控制加载的文件和方法,在app/system/entrance.php中存在重点代码: 当M_TYPE system并且M_MODULE include时,会设置常量PATH_OWN_FILE为PATH_APP.M_T…...