【Linux】多线程1——线程概念与线程控制
文章目录
- 1. 线程概念
- 什么是线程
- Linux中的线程
- 线程的优点
- 线程的缺点
- 线程的独立资源和共享资源
- 2. 线程控制
- Linux的pthread库
- 用户级线程
- 📝 个人主页 :超人不会飞)
- 📑 本文收录专栏:《Linux》
- 💭 如果本文对您有帮助,不妨点赞、收藏、关注支持博主,我们一起进步,共同成长!
1. 线程概念
什么是线程
💭理解线程需要和进程的概念紧密联系。
- 线程是一个执行分支,执行粒度比进程更细,调度成本更低;
- 进程是分配系统资源的基本单位,线程是CPU调度的基本单位。
- 线程是运行在进程中的一个执行流,本质上是在进程的地址空间中运行,一个进程至少包含一个线程,称为主线程。
Linux中的线程
线程是操作系统中的抽象概念,用于实现多任务并发执行。不同的操作系统可以有不同的线程实现方法和模型。例如,在Windows操作系统中,与进程PCB对标的,构建了描述线程的数据结构 —— 线程控制块,但这样子设计有以下几个缺点:
- 创建线程在Windows中开销较大,因为它涉及到较多的内核资源和数据结构的分配
- 线程与进程无法统一组织起来
- 线程的调度效率低
Linux的设计者发现,线程控制块与进程控制块(PCB)大部分描述属性相同,且进程与其内部创建的线程看到的都是同一个地址空间。因此,在Linux中,线程控制块直接复用了PCB的代码,也就是说,Linux底层并没有真正的“线程”,这种复用之后的线程称之为轻量级进程。

- 每个轻量级进程(后面直接称为线程)都有自己的一个编号——LWP,同一个进程中的各个线程具有相同的PID。
🔎那我们之前讨论的进程是什么?这里都是轻量级进程的话,需要另有一个进程PCB来管理整个进程吗?
答案是不用。事实上,在Linux中,因为每个进程都至少有一个线程,即主线程(主执行流),这个线程的LWP和PID是相同的,因此,我们之前讨论的进程PCB,实际上就是这个主线程的task_struct。
ps -aL命令查看系统中的轻量级进程。
测试:在一个进程中,创建了10个线程,并用ps -aL命令查看。可以看到有一个主线程和10个新线程,主线程的PID和LWP相同。

-
线程的调度成本低于进程,是因为同一个进程中的线程共享同一个地址空间,因此这些线程的调度只需要保存和更改一些上下文信息、CPU寄存器即可,如pc指针。而进程的调度需要修改较多的内存资源,如页表、地址空间等,而开销更大的是修改cache缓存的数据。
cache缓存
CPU内部的高速存储器中,保存着一些频繁访问的指令和数据,基于局部性原理,这些数据可能是未来将要被访问的,也可能是当前正在访问的。这么做的目的是减少CPU与内存的IO次数,以便快速响应CPU的请求,而不必每次都从较慢的内存中获取数据。不同进程的cache缓存数据是不同的,因此调度进程是需要切换这部分数据,而同一个进程的不同线程的cache缓存相同。
CPU根据PID和LWP的对比,区分当前调度是线程级还是进程级,进而执行对应的调度策略。
线程的优点
- 线程占用的资源比进程少很多,因此创建线程的开销比创建进程小
- 线程的调度成本低于进程调度,线程切换时OS的工作量小
- 充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程的缺点
- 性能损失。 一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。 如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。 例如有10个处理器,11个线程,一对一的关系被破坏后,多出来的线程就增加了额外的调度开销。
- 复杂性和错误难以调试。 多线程编程涉及到共享资源、并发访问和同步等问题,这增加了程序的复杂性。
- 健壮性降低。 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说多线程之间是缺乏保护的。
⭕补充:
线程发生异常(如野指针、除零错误等),会导致线程崩溃,进而引发整个进程退出。从宏观角度,因为线程是进程的一个执行分支,线程干的事就是进程干的事,因此线程异常相当于进程异常,进程就会退出。从内核角度,线程出错,OS发送信号给进程,而不是单发给线程。
线程的独立资源和共享资源
进程是资源分配的基本单位,线程是调度的基本单位。一个进程中的多个线程共享线程数据,当然也有自己独立的数据。
线程的独立资源:
- 栈
- 寄存器中的上下文信息
- 线程ID(在Linux中表现为LWP)
- errno
- 信号屏蔽字和未决信号集
- 调度优先级
线程的共享资源:
- 进程地址空间(包括进程的数据段、代码段等)
- 文件描述符表
- 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
- 当前工作目录
- 用户ID和组ID
2. 线程控制
Linux的pthread库
Liunx中,提供给用户层进行线程控制的函数被打包在一个动态库中 —— pthread。使用线程控制接口时,需要包含头文件
pthread.h,并在gcc/g++编译时加上-l pthread选项确定链接动态库。
在/lib64目录下找到pthread库:

编译时应该添加的选项:
g++ threadTest.cc -o threadTest -l pthread # -lpthread也可以
-
pthread_create
功能:
创建一个线程
接口:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);参数:
thread:线程库中定义了一个线程ID类型phtread_t,这里的thread是一个输出型参数,函数会向其指向的空间写入创建线程的ID
attr:线程的属性,一般设为nullptr即可
start_routine:线程执行的函数,是一个返回值类型void*,参数类型void*的函数指针
arg:传入start_routine的参数,使用前后一般需要类型转换。
返回值:
RETURN VALUEOn success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined.
💭关于线程退出的问题:
同子进程退出,需要父进程回收,线程也需要被另外的线程回收。回收的原因如下:1. 一个线程退出后,对应的资源不会被释放,而是留存在地址空间中。一个进程能运行的线程数是有限的,如果不加以回收,可能会导致内存泄漏!2. 一个线程退出后,其它线程可能需要获取其执行任务的结果。
-
pthread_join
功能:
阻塞等待一个线程
接口:
int pthread_join(pthread_t thread, void **retval);参数:
thread:线程ID
retval:指向的空间中存储的是线程返回的结果(注意类型转换),因为线程函数的返回结果是void*类型,所以要用二级指针接收。如果不关心回收线程的结果,则设置为nullptr。
返回值:
RETURN VALUEOn success, pthread_join() returns 0; on error, it returns an error number. -
pthread_exit
线程函数中,可以直接用return退出线程并返回结果(可以被其它线程join接收)
void *run(void *arg) {int cnt = 5;while (cnt--){cout << "I am new thread" << endl;sleep(1);}return nullptr; // }也可以用
pthread_exit函数。void pthread_exit(void *retval); //和return一样,返回一个void*指针
Linux中,线程只有joinable和unjoinable两种状态。默认情况下,线程是joinable状态,该状态下的线程退出后,占有资源不会被释放,必须等待其它线程调用pthread_join回收它,释放资源,或者进程退出,资源全部被释放。当然,可以通过调用pthread_detach分离线程,将线程设置为unjoinable状态,使其无需被等待回收,退出即被系统自动释放资源。
-
pthread_detach
功能:
分离线程ID为thread的线程,使其无需被join等待。
接口:
int pthread_detach(pthread_t thread);返回值:
RETURN VALUEOn success, pthread_detach() returns 0; on error, it returns an error number.
线程分离可以由别的线程分离,也可以自己分离。
-
pthread_self
功能:
获取当前线程的线程ID
接口:
pthread_t pthread_self(void);
⭕测试
void *run(void *arg)
{int cnt = 10;while(cnt--){cout << "I am new thread, cnt: " << cnt << endl;sleep(1);}pthread_exit(nullptr);
}int main()
{cout << "I am main thread" << endl;pthread_t tid;pthread_create(&tid, nullptr, run, nullptr);int n = pthread_join(tid, nullptr);if (n != 0){cout << "join new thread fail!!" << endl;exit(1);}cout << "join new thread success!!" << endl;return 0;
}
主线程创建新线程后,调用pthread_join会阻塞等待新线程退出。运行结果如下:
[ckf@VM-8-3-centos lesson9_thread]$ ./mythread
I am main thread
I am new thread, cnt: 9
I am new thread, cnt: 8
I am new thread, cnt: 7
I am new thread, cnt: 6
I am new thread, cnt: 5
I am new thread, cnt: 4
I am new thread, cnt: 3
I am new thread, cnt: 2
I am new thread, cnt: 1
I am new thread, cnt: 0
join new thread success!!
可以在主线程中detach线程ID为tid的新线程,也可以在新线程中detach自己。
void *run(void *arg)
{//pthread_detach(pthread_self()); // 在新线程中detach自己int cnt = 10;while(cnt--){cout << "I am new thread, cnt: " << cnt << endl;sleep(1);}pthread_exit(nullptr);
}int main()
{cout << "I am main thread" << endl;pthread_t tid;pthread_create(&tid, nullptr, run, nullptr);pthread_detach(tid); // 在主线程中detach线程ID为tid的新线程int n = pthread_join(tid, nullptr);if (n != 0){cout << "join new thread fail!!" << endl;exit(1);}cout << "join new thread success!!" << endl;return 0;
}
[ckf@VM-8-3-centos lesson9_thread]$ ./mythread
I am main thread
join new thread fail!! #等待失败,pthread_join无法等待已分离的线程,返回值非0
如果在新线程中detach自己,可能依然能够join成功。要想成功detach线程,必须在join之前detach,因为调用pthread_join函数时,已经将线程视为joinable并阻塞等待了,此后再detach是无效的。上面代码中,如果在新线程中detach自己,由于主线程和新线程调度的先后顺序不确定性,很可能线程先join再detach,此时的detach是无效的。
-
pthread_cancel
功能:
撤销(终止)一个线程ID为thread的线程
接口:
int pthread_cancel(pthread_t thread);返回值:
RETURN VALUEOn success, pthread_cancel() returns 0; on error, it returns a nonzero error number.撤销一个线程后,如果有另外的线程join该线程,那么其收到的退出结果是
PTHREAD_CANCELED。#define PTHREAD_CANCELED ((void *) -1)
⭕测试
void *run(void *arg)
{while (true){cout << "I am new thread" << endl;sleep(1);}pthread_exit(nullptr);
}int main()
{cout << "I am main thread" << endl;pthread_t tid;pthread_create(&tid, nullptr, run, nullptr);sleep(3);pthread_cancel(tid);void *ret = nullptr;int n = pthread_join(tid, &ret);if (n != 0){cout << "join new thread fail!!" << endl;exit(1);}if (ret == PTHREAD_CANCELED){cout << "new thread is canceled" << endl;}cout << "join new thread success!!" << endl;return 0;
}
[ckf@VM-8-3-centos lesson9_thread]$ ./mythread
I am main thread
I am new thread
I am new thread
I am new thread
new thread is canceled #新线程被撤销了
join new thread success!!
用户级线程
💭pthread库的线程控制接口,都不是直接操作Linux底层的轻量级进程,而是操作用户级线程。pthread库将底层的轻量级进程封装成为用户级线程,用户看到的便是线程而不是所谓的轻量级进程。动态库load到进程的共享区中,因此,用户级线程的空间也是load到进程的共享区中,线程的大部分独立资源保存在这块空间中,包括线程栈。
🔎线程库是怎么管理用户级线程的?
先描述再组织。 创建类似TCB的数据结构来描述线程,并将这些数据结构组织为一张表,如下。

-
前面使用接口获取到的线程tid,其实就是该线程的用户级页表的首地址,只不过将其转换成整型的格式。
int g_val = 100;string toHex(pthread_t tid) {char buf[64];snprintf(buf, sizeof(buf), "0x%x", tid);return string(buf); }void *run(void *arg) {cout << toHex(pthread_self()) << endl;pthread_exit(nullptr); }int main() {pthread_t t1;pthread_t t2;cout << "&g_val: " << &g_val <<endl;pthread_create(&t1, nullptr, run, nullptr);pthread_create(&t2, nullptr, run, nullptr);pthread_join(t1, nullptr);pthread_join(t2, nullptr);return 0; }[ckf@VM-8-3-centos lesson9_thread]$ ./mythread &g_val: 0x6020cc #全局数据区 0x4b30f700 #共享区 0x4ab0e700 #共享区 -
全局变量默认是所有线程共享的,开发者需要处理多线程竞争问题。有些情况下我们需要保证一个线程独享一份数据,其它线程无法访问。这时候就要用到线程局部存储。gcc/g++编译环境中,可以用
__thread声明一个全局变量,从而每个线程都会独有一个该全局变量,存储在线程局部存储区中。__thread int g_val = 0; //__thread修饰全局变量,可以理解为从进程的全局变量变成线程的全局变量string toHex(pthread_t tid) {char buf[64];snprintf(buf, sizeof(buf), "0x%x", tid);return string(buf); }void *run(void *arg) {cout << "g_val: " << ++g_val << " " << "&g_val: " << &g_val << endl;pthread_exit(nullptr); }int main() {pthread_t t1;pthread_t t2;pthread_t t3;pthread_create(&t1, nullptr, run, nullptr);pthread_create(&t2, nullptr, run, nullptr);pthread_create(&t3, nullptr, run, nullptr);cout << "g_val: " << ++g_val << " " << "&g_val: " << &g_val << endl;pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);return 0; }[ckf@VM-8-3-centos lesson9_thread]$ ./mythread #使用了线程局部存储 g_val: 1 &g_val: 0x7fcb7cfcb77c g_val: 1 &g_val: 0x7fcb7bf366fc g_val: 1 &g_val: 0x7fcb7b7356fc g_val: 1 &g_val: 0x7fcb7af346fc[ckf@VM-8-3-centos lesson9_thread]$ ./mythread #未使用线程局部存储 g_val: 1 &g_val: 0x6021d4 g_val: 2 &g_val: 0x6021d4 g_val: 3 &g_val: 0x6021d4 g_val: 4 &g_val: 0x6021d4 -
每个线程都有一个独立的栈结构,用于存储运行时的临时数据和压入函数栈帧。注意,主线程的栈就是进程地址空间中的栈。
ENDING…
相关文章:
【Linux】多线程1——线程概念与线程控制
文章目录 1. 线程概念什么是线程Linux中的线程线程的优点线程的缺点线程的独立资源和共享资源 2. 线程控制Linux的pthread库用户级线程 📝 个人主页 :超人不会飞)📑 本文收录专栏:《Linux》💭 如果本文对您有帮助&…...
【Maven】SpringBoot项目使用maven-assembly-plugin插件多环境打包
SpringBoot项目使用maven-assembly-plugin插件多环境打包 1.创建SpringBoot项目并在pom.xml文件中添加maven-assembly-plugin配置 <!-- 多环境配置 --><profiles><!-- 开发环境 --><profile><id>dev</id><properties><prof…...
指令集_基础
指令集-基础 一、提示过程1,文章摘要2,数学问题求解 二、角色提示三、多范例提示 一、提示过程 指导人工智能,执行任务的过程,称为提示过程。向AI 提供一组指令(提示),然后它执行任务 1,文章摘要 例如&a…...
学习Vue:数据绑定的基本概念
在 Vue.js 中,Vue 实例是您构建应用程序的核心。它允许您将数据和界面连接起来,实现动态的数据绑定,使您的应用程序能够根据数据的变化自动更新界面。让我们来深入了解 Vue 实例与数据绑定的基本概念。 Vue 实例与数据绑定 什么是 Vue 实例&…...
Python 装饰器 - 推导式(列表推导式) - 迭代器 - 生成器 - 闭包
目录 推导式 1、列表推导式(用得最多的) 给你一个列表,求所有数据的绝对值 列表推导式跟if运算 打印50以内能被3整除的数的平方(filter)(if的使用) 找到1000以内开平方的结果是整数的数&am…...
【大数据】Flink 详解(二):核心篇 Ⅲ
Flink 详解(二):核心篇 Ⅲ 29、Flink 通过什么实现可靠的容错机制? Flink 使用 轻量级分布式快照,设计检查点(checkpoint)实现可靠容错。 30、什么是 Checkpoin 检查点? Checkpoint …...
Jmeter性能测试系列-性能测试需求分析
性能测试需求分析 性能测试需求分析与传统的功能测试需求有所不同,功能测试需求分析重点在于从用户层面分析被测对象的功能性、易用性等质量特性,性能测试则需要从终端用户应用、系统架构设计、硬件配置等多个纬度分析系统可能存在性能瓶颈的业务。 性…...
Syncfusion Essential Studio JavaScrip Crack
Syncfusion Essential Studio JavaScrip Crack 数据透视表 添加了在将数据透视表导出到PDF文档时自定义列宽的支持。 签名 添加了对在特定位置绘制文本的支持。 Syncfusion Essential Studio for JavaScript在一个包中包含80多个高性能、轻量级、模块化和响应式UI组件。包括Jav…...
8.13黄金是否进入下行通道?下周开盘如何布局
近期有哪些消息面影响黄金走势?黄金多空该如何研判? 黄金消息面解析:周五(8月11日)现货黄金小幅收低,受累于美元走强和美国国债收益率上升,本周录得6月底以来最差单周表现。投资者在评估最新一批通胀报告和消费者信…...
Idea的基本使用带案例---详细易懂
一.idea是什么 有专业人士说,idea是天生适合做微软,当时我还想肯定是夸大其词了,但当你用起来的时候确实很爽,😊😊 ntelliJ IDEA是一种集成开发环境(IDE),由JetBrains开发…...
MySQL中的用户管理
系列文章目录 MySQL常见的几种约束 MySQL中的函数 MySQL中的事务 MySQL中的视图 MySQL中的索引 文章目录 系列文章目录前言一、用户管理1、用户管理入门2、用户管理操作及示例 二、权限管理1.权限管理语法2.权限操作示例 三、角色管理1、角色管理入门2、角色操作示例 总结…...
【STM32】利用CubeMX对FreeRTOS用按键控制任务
对于FreeRTOS中的操作,最常用的就是创建、删除、暂停和恢复任务。 此次实验目标: 1.创建任务一:LED1每间隔1秒闪烁一次,并通过串口打印 2.创建任务二:LED2每间隔0.5秒闪烁一次,并通过串口打印 3.创建任…...
c# .net mvc的IHttpHandler奇妙之旅--图片文件请求安全过滤,图片防盗链
源码下载: c# .net mvc图片文件请求安全过滤,图片防盗链 https://download.csdn.net/download/cplvfx/88206428 在阅读该文章前,请先阅读该文章 c# .net mvc的IHttpHandler奇妙之旅。.net的生命周期和管道你听说过吗?你可以利用他处理业务如:跳转业务页面,文件请求的安全…...
STM32F407使用Helix库软解MP3并通过DAC输出,最精简的STM32+SD卡实现MP3播放器
只用STM32单片机SD卡耳机插座,实现播放MP3播放器! 看过很多STM32软解MP3的方案,即不通过类似VS1053之类的解码器芯片,直接用STM32和软件库解码MP3文件,通常使用了labmad或者Helix解码库实现,Helix相对labm…...
STM32 CAN 过滤器设置
做个笔记吧 ,免得以后忘记了 芯片是stm32F207 ,用cubeMX 6.80 版本生成 CAN 的使用总体包含4个部分 第一步:CAN初始化,配置波特率 (cubeMX 里面配置好后自动生成,不需要手动添加) MX_CAN1_Init(); 第二步&#…...
日常BUG—— maven编译报错
😜作 者:是江迪呀✒️本文关键词:日常BUG、BUG、问题分析☀️每日 一言 :存在错误说明你在进步! 一、问题描述 一个maven项目在由于在代码中书写了如下代码: public static ConcurrentMap<…...
Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理
Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理 目录 Unity 工具 之 Azure 微软SSML语音合成TTS流式获取音频数据的简单整理 一、简单介绍 二、实现原理 三、实现步骤 四、关键代码 一、简单介绍 Unity 工具类,自己整理的一些游戏开发可…...
学习Vue:插值表达式和指令
在 Vue.js 中,Vue 实例与数据绑定是构建动态交互界面的关键。在这篇文章中,我们将重点介绍 Vue 实例中两种实现数据绑定的方式:插值表达式和指令。这些机制允许您将数据无缝地渲染到界面上,实现实时的数据更新和展示。 插值表达式…...
echart 3d立体颜色渐变柱状图
如果可以实现记得点赞分享,谢谢老铁~ 1.需求描述 根据业务需求将不同的法律法规,展示不同的3d立体渐变柱状图。 2.先看下效果图 3. 确定三面的颜色,这里我是自定义的颜色 // 右面生成颜色const rightColorArr ref(["#79D…...
linux shell变量
linux shell变量 1、变量命名规则2、只读变量3、删除变量 1、变量命名规则 变量名不能加$命名只能使用英文字母、数字和下划线,首个字母不能以数字开头中间不能有空格。可以有下划线不能使用标点符号不能使用bash中的关键字 username"tom"引用 $userna…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
git: early EOF
macOS报错: Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...
mac:大模型系列测试
0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何,是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试,是可以跑通文章里面的代码。训练速度也是很快的。 注意…...
一些实用的chrome扩展0x01
简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序,无论是测试应用程序、搜寻漏洞还是收集情报,它们都能提升工作流程。 FoxyProxy 代理管理工具,此扩展简化了使用代理(如 Burp…...
java高级——高阶函数、如何定义一个函数式接口类似stream流的filter
java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用(Math::max) 2 函数接口…...
