多线程(超详细) (ε≡٩(๑>₃<)۶ 一心向学)

多线程目录

一、认识线程

1、概念:

1) 线程是什么

2) 线程为什么存在

3) 进程与线程的区别

二、创建线程

1、方法1:继承Thread类

2、方法2:实现 Runnable 接口

3、方法3:匿名内部类创建 Thread 子类对象

4、方法4:匿名内部类创建 Runnable 子类对象

5、方法5:lambda 表达式创建 Runnable 子类对象

三、Thread类 及其 常见的方法

1、Thread 的常见构造方法

2、Thread 的常见的几个属性

3、启动一个线程

4、中断一个线程

5、等待一个线程

6、获得当前线程的引用

7、休眠当前线程

四、线程的状态

1、线程的所有状态

2、线程状态和状态转义的意义​编辑

3、观察线程的状态和转移

1)观察 NEW、RUNNABLE、TERMINATED

2)观察 WAITING、BLOCKED、TIMED_WAITING

五、多线程带来的风险-线程安全(重中之重)

1、观察一下线程不安全

2、线程安全的概念

3、线程不安全的原因

1)线程调度是随机的

2)修改共享数据

3)原子性

什么是原子性

4)可见性 

5)指令重排序

4、解决之前的线程不安全的问题


如果对于下文的进程是什么有疑问的,可以跳转到我的上一篇博客中:

初识操作系统 感谢各位大佬的支持💓💓💓

一、认识线程

1、概念:

1) 线程是什么

线程是操作系统中最小的执行单元,它是进程中的一个独立执行路径。线程可以共享进程的资源,但每个线程都有自己的程序计数器、栈和寄存器。线程的使用可以提高程序的并发性和执行效率。main() 这个线程一般称之为——主线程(Main Thread)

2) 线程为什么存在

首先「并发编程」称为「刚需」

  因为对于单核CPU的发展遇到了瓶颈,算力不够,就需要多核CPU,而并发编程就可以充分利用多核CPU资源。

  有时候我们会出现等待"I/O"的时候,那么在等待的过程中,我们可以在等待I/O过程中,可以去干一些别的事情,这时候同样需要使用并发编程。

虽然多进程也可以实现「并发编程」,但是线程比进程更加的轻量。因为对于线程来说:创建、销毁、调度线程比进程更快。

3) 进程与线程的区别

 进程是包含线程的,每个进程至少有一个线程存在,即为主线程。

• 进程和进程之间不共享内存空间的,但是在同一个进程中的线程和线程之间是共享同一个内存空间的。

• 进程是系统分配资源的最小单位,线程是系统调度的最小单位

• 一个进程挂了一般是不会影响到其他的进程,但是当一个进程中的某个线程挂了,可能会把同进程的其余线程一起带走,进而导致整个进程崩溃。


二、创建线程

1、方法1:继承Thread类

继承 Thread 来创建一个线程类,使用 start 启动线程

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 Test {public static void main(String[] args) {// 创建 Thread 类子类,在子类中 重写 run 方法Thread thread = new MyThread();thread.start();while(true) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

运行此代码的话,就会发现 “hello thread” 和 “hello main” 是随机执行的,多线程的调度顺序是随机的,这种也叫做 —— “抢占式执行”。

后面的构造方法就不进行循环观察了。

2、方法2:实现 Runnable 接口

实现 Runnable 接口,创建 Thread 实例,调用构造方法,使用实现Runnable 接口的类作为参数

class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("线程运行的代码");}
}public class Test2 {public static void main(String[] args) {Thread thread = new Thread(new MyRunnable());thread.start();System.out.println("main线程运行的代码");}
}

3、方法3:匿名内部类创建 Thread 子类对象

public class Test3 {public static void main(String[] args) {Thread thread = new Thread(){// 使用匿名类创建 Thread 子类对象@Overridepublic void run() {System.out.println("使用匿名内部类创建 Thread 子类对象");}};thread.start();System.out.println("这是main线程");}
}

4、方法4:匿名内部类创建 Runnable 子类对象

public class Test4 {public static void main(String[] args) {// 使用匿名内部类创建 Runnable 子类对象Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("使用匿名内部类创建 Runnable 子类对象");}});thread.start();System.out.println("这是main线程");}
}

5、方法5:lambda 表达式创建 Runnable 子类对象

public class Test5 {public static void main(String[] args) {// 使用lambda 配合 匿名内部类创建 Runnable 子类对象Thread thread1 = new Thread(() -> {System.out.println("使用匿名内部类创建 Runnable 子类对象");});Thread thread2 = new Thread(() -> System.out.println("使用lambda 配合 匿名内部类创建 Runnable 子类对象"));thread1.start();thread2.start();System.out.println("这是main线程");}
}

注:如果 不造 lambda 表达式的那么可以去这里进行参考一下 Java-数据结构-Lambda表达式 (✪ω✪) 感谢支持💓💓💓


三、Thread类 及其 常见的方法

1、Thread 的常见构造方法

由上面的创建线程的介绍,我们可以得出一个大概的构造方法,接下来看看完整的构造方法:

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

简略的看一下如何创建:

Thread thread1 = new Thread();
Thread thread2 = new Thread(new MyRunnable());
Thread thread3 = new Thread("线程的名称");
Thread thread4 = new Thread(new MyRunnable(),"线程的名称");

2、Thread 的常见的几个属性

属性获得方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否是后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

  ID 是线程的唯一标识,不同的线程不会重复

  名称 是在各种调试工具中用到的

  状态 表示线程当前所处的一个情况

  优先级 高的线程理论上更容易被调用

  后台线程 JVM会在一个进程的所有非后台线程结束后,才会结束运行

  是否存活 简单来说,就是 run 方法是否运行结束了

  至于线程中断问题呢,我们在后面进行了解

先来简单的使用一下这些方法:

public class Test5 {public static void main(String[] args) {Thread thread = new Thread(() -> {for(int i = 0;i < 10;i++) {try {System.out.println(Thread.currentThread().getName() + ": 活着");Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName() + ": 即将死去");});thread.start();System.out.println(Thread.currentThread().getName() + ": ID: " + thread.getId());System.out.println(Thread.currentThread().getName() + ": 名称: " + thread.getName());System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());System.out.println(Thread.currentThread().getName() + ": 优先级: " + thread.getPriority());System.out.println(Thread.currentThread().getName() + ": 后台线程: " + thread.isDaemon());System.out.println(Thread.currentThread().getName() + ": 活着: " + thread.isAlive());System.out.println(Thread.currentThread().getName() + ": 被中断: " + thread.isInterrupted());}
}

3、启动一个线程

在前面我们知道了,如何使用重写 run 方法创建一个线程对象,但是线程对象创建出来并不会直接进行运行,这时候我们需要使用另一个方法进行执行线程——start()

调用 start() 方法,才真的在操作系统的底层创建出一个线程。


4、中断一个线程

方法说明
public void intreeupt()中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位
public static boolean intreeupted()判断当前线程的中断标志位是否设置,调用后清除标志位
public boolean isIntreeupted()判断对象关联的线程的标志位是否设置,调用后不清除标志位

 进行一下简单的测试一下:

class MyThread extends Thread {@Overridepublic void run() {// 下面的两个方法都可以进行while(!Thread.interrupted()) {
//      while(!Thread.currentThread().isInterrupted())System.out.println(Thread.currentThread().getName() + ": 正在运行");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println(Thread.currentThread().getName() + ": 中断线程");// 这里不要忘记进行 breakbreak;}}System.out.println(Thread.currentThread().getName() + ": 中断");}
}public class Test {public static void main(String[] args) throws InterruptedException {Thread thread = new MyThread();thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName() + ": 即将中断thread线程");thread.interrupt();}
}

上述代码运行结果: 


thread 收到通知的方式有两种:

1、如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志

  •  当出现 InterruptedException 的时候,要不要结束线程取决于 catch 中代码的写法。可以选择         忽略这个异常,也可以跳出循环结束线程。

2、否则,只是内部的一个中断标志被设置,thread 可以通过

  •  Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志这中方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。


5、等待一个线程

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等待 millis 毫秒
public void join(long millis,int nanos)等待线程结束,最多等待 millis 毫秒,但是可以更高精度
public class Test6 {public static void main(String[] args) throws InterruptedException {Runnable target = () -> {for(int i = 0; i < 5;i++) {System.out.println(Thread.currentThread().getName() + ": 正在执行");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 结束");};Thread thread1 = new Thread(target,"线程1");Thread thread2 = new Thread(target,"线程2");System.out.println("先让线程1致性");thread1.start();thread1.join();System.out.println("线程1结束,再让线程2执行");thread2.start();thread2.join();System.out.println("线程2结束");}
}

大家可以去运行一下试一试,并且试一下当把 join() 的方法的代码注释掉会发生什么情况呢?


6、获得当前线程的引用

方法说明
public static Thread currentThread()返回当前线程对象的引用
public class Test4 {public static void main(String[] args) {Thread thread = Thread.currentThread();System.out.println(thread.getName());}
}

7、休眠当前线程

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis 毫秒
public static void sleep(long millis,int nanos) throws InterruptedException休眠当前线程 millis 毫秒,并且可以高精度的休眠

我们要注意的是,因为线程的调度是不可控的,所以这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。


四、线程的状态

1、线程的所有状态

NEW: 安排了工作,还未开始行动。
RUNNABLE: 可工作的。又可以分成正在工作中和即将开始工作。
BLOCKED: 表示排队等着其他事情。
WAITING: 表示排队等着其他事情。
TIMED_WAITING: 表示排队等着其他事情。
TERMINATED: 工作完成了。

2、线程状态和状态转义的意义

 来进行一下简单的介绍:


3、观察线程的状态和转移

1)观察 NEW、RUNNABLE、TERMINATED
public class Test4 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 1000; i++) {}});System.out.println(t.getName() + ": " + t.getState());t.start();while (t.isAlive()) {System.out.println(t.getName() + ": " + t.getState());}System.out.println(t.getName() + ": " + t.getState());}
}

这个就是上述代码所执行到状态


2)观察 WAITING、BLOCKED、TIMED_WAITING
public class Test4 {public static void main(String[] args) throws InterruptedException {Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}}, "t1");t1.start();Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {System.out.println("hehe");}}}, "t2");t2.start();}
}

上述的代码我们是用 jconsole 来进行观察 BLOCKED、TIMED_WAITING 这两个状态

 把上述的代码中的 t1 的sleep 更改成 wait,进行观察 WAITING 状态

public class Test4 {public static void main(String[] args) throws InterruptedException {Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {while (true) {try {object.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}, "t1");t1.start();}
}

可以得出:

• BLOCKED 表示等待获取锁,WAITING 和 TIMED_WAITING 表示等待其他线程发来通知。

• TIMED_WAITING 线程在等待唤醒,但设置了时限。WAITING 线程在无限等待唤醒。

在上面的代码中 出现了一些关键词如:synchronized 、wait 这些关键字会在后面进行详细的介绍


五、多线程带来的风险-线程安全(重中之重)

1、观察一下线程不安全

public class Test4 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {for(int i = 0;i < 50000;i++) {count++;}});Thread thread2 = new Thread(() -> {for(int i = 0;i < 50000;i++) {count++;}});thread1.start();thread2.start();thread1.join();thread2.join();// 预期为100000System.out.println("count = " + count);}
}

运行之后会发现,这个程序的 count 不会出现 100000,并且每次运行的结果是不同的,这就是线程不安全的。


2、线程安全的概念

对于这个概念是非常之复杂的,但是我们可以这样去理解:

如果多线程环境下代码的结果是符合我们预期的,即在单线程环境应该的结果,则说这个结果是正确的。


3、线程不安全的原因

1)线程调度是随机的

这是线程安全的罪魁祸首,随机调度使一个程序在多线程的环境下,执行顺序存在很多的变数,程序员必须保证 在任意执行顺序下 ,代码都能正常运行。

2)修改共享数据

也就是多线程修改同一个数据。上面的线程不安全的代码中,涉及到多个线程针对 count 变量进行修改。此时这个 count 是一个多个线程都能访问到的 “共享数据”。

3)原子性
什么是原子性

我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入这个房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
那我们应该如何解决这个问题呢?可以给房间加⼀把锁,A 进去就把门锁上,其他人就进不来了。这样就保证了这段代码的原子性了。
有时也把这个现象叫做 同步互斥,表示操作是互相排斥的。

不保证原子性会给线程带来什么问题

如果一个线程正在对一个变量进行操作,这个时候如果有其余的线程进来了,打断了这个操作,结果可能就是错误的。

这一点也和线程的 抢占式 调度密切相关,如果线程不是 “抢占” 的 ,就算没有原子性,问题也不大

4)可见性 

可见性是指,一个线程对共享变量值的修改,能够及时被其他线程所看到。

Java内存模型:

 • 线程之间的共享变量存在 主内存

 • 每个线程都有自己的 “工作内存”

 • 当线程读取一个共享变量的时候,会先把变量从主内存拷贝到工作内存,再从工作内存读取数据

 • 当线程要修改一个共享变量的时候,也会先修改工作内存中的副本,再同步回主内存

由于每个线程有自己的工作内存,这些工作内存中的内容相当于同⼀个共享变量的 "副本"。此时修改线程1的工作内存中的值,线程2的工作内存不一定会及时变化。


5)指令重排序

一段代码是这样的:
1. 去前台取下U盘
2. 去教室写10分钟作业
3. 去前台取下快递
如果是在单线程情况下,JVM、CPU指令集会对其进行优化,比如,按 1->3->2 的方式执行,也是没问题,可以少跑一次前台。这种叫做指令重排序。


总结:

线程安全问题 产生原因

1、「根本」操作系统对于线程的调度是随机的,抢占式执行

2、多个线程同时修改同一个变量

3、修改操作,不是原子的

4、内存可见性问题,引起的线程不安全问题

5、指令重排序,引起的线程不安全问题


4、解决之前的线程不安全的问题

public class Test4 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Object object = new Object();Thread thread1 = new Thread(() -> {for(int i = 0;i < 50000;i++) {synchronized (object) {count++;}}});Thread thread2 = new Thread(() -> {for(int i = 0;i < 50000;i++) {synchronized (object) {count++;}}});thread1.start();thread2.start();thread1.join();thread2.join();// 预期为100000System.out.println("count = " + count);}
}

这里面同样遇到了 synchronized 关键词,这里可能不会知道是什么意思,在下一篇的文章中,会进行详细的介绍,如何解决线程不安全的问题,敬请期待吧~~~ 


 

如果觉得这篇文章不错的话,期待你的一键三连哦 ~ ~ ~ 让我们一起加油,顶峰相见!! !

                           💓💓💓💓💓💓💓💓💓💓💓💓💓💓💓

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

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

相关文章

SpringBoot——Maven篇

Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的工具。它具有许多特性&#xff0c;其中一些重要的特性包括&#xff1a; 1. 自动配置&#xff1a;Spring Boot 提供了自动配置的机制&#xff0c;可以根据应用程序的依赖和环境自动配置应用程序的各种组件&#xff…

中文大语言模型提示工程:解锁AI力量的密钥(完整优化版)

文章目录 **引言&#xff1a;AI时代的"咒语"革命****一、为什么中文提示工程是技术深水区&#xff1f;****1.1 中文的"模糊美学"挑战****1.2 文化认知鸿沟****1.3 分词歧义陷阱** **二、中文提示工程六脉神剑&#xff08;附实战代码&#xff09;****2.1 结…

C++中虚析构函数的作用是什么?为什么基类需要虚析构函数?

C中虚析构函数的作用是什么&#xff1f;为什么基类需要虚析构函数&#xff1f; 在C中&#xff0c;虚析构函数&#xff08;virtual destructor&#xff09;的作用是确保在通过基类指针或引用删除派生类对象时&#xff0c;能够正确调用派生类的析构函数&#xff0c;从而避免资源…

【C++项目实战】校园公告搜索引擎:完整实现与优化指南

&#x1f3ac; 个人主页&#xff1a;谁在夜里看海. &#x1f4d6; 个人专栏&#xff1a;《C系列》《Linux系列》《算法系列》 ⛰️ 道阻且长&#xff0c;行则将至 目录 &#x1f4da;一、项目概述 &#x1f4d6;1.项目背景 &#x1f4d6;2.主要功能 &#x1f4d6;3.界面展…

大数据技术之Spark优化

第 1 章 Spark 性能调优 问:spark 优化 第一句:我们可以从性能,算子,shuffle 过程以及 jvm 四个方面展开优化。 1 常规性能调优 1.1 常规性能调优一:最优资源配置 Spark 性能调优的第一步,就是为任务分配更多的资源,在一定范围内,增加资源的分配与性能的提升是成正…

【 Manus平替开源项目】

文章目录 Manus平替开源项目1 OpenManus1.1 简介1.2 安装教程1.3 运行 2 OWL2.1 简介2.2 安装教程2.3 运行 3 OpenHands&#xff08;原OpenDevin&#xff09;3.1 简介3.2 安装教程和运行 Manus平替开源项目 1 OpenManus 1.1 简介 开发团队: MetaGPT 核心贡献者&#xff08;5…

《Java SQL 操作指南:深入理解 Statement 用法与优化》

在 Java 数据库编程中&#xff0c;Statement 是用于执行 SQL 语句的接口&#xff0c;允许程序与数据库进行交互。本文将详细介绍 Statement 的基本概念、常见用法以及 PreparedStatement 和 CallableStatement 等相关接口。 1. Statement 基本介绍 Statement 接口继承了 AutoC…

FFMPEG录制远程监控摄像头MP4

手绘效果图 上图是录制功能的HTML前端页面&#xff0c;录制功能和解码视频放在一起。录制功能关键是录制(开始录制按钮)、停止录像按钮。当点击“录制”的时候则会开始录制MP4文件, 当点击停止的时候就会停止录制MP4。经过录制后&#xff0c;则会生成MP4,并放到我的RV1126的/tm…

数据类型及sizeof,进制转换

其实数据类型可以讲很多内容&#xff0c;这里看情况需要讲多久吧。 本篇基本都是理论。 目录 数据类型的分类 基本数据类型 构造数据类型 指针类型 空类型 计算数据类型或变量所占用的内存字节数 基本语法 进制转换 二进制 二进制的概念 二进制与十进制的转换 十六进…

网络安全之tcpdump工具

引言 wireshark是一款非常不错的抓包软件&#xff0c;在图形化界面占绝对统治地位&#xff1b;尽管其在字符界面下有些许选项可供使用&#xff0c;但终究不太方便&#xff0c;下面我再介绍一款NB的终端抓包工具 tcpdump 1、混杂模式 linux的网卡有混杂模式一说&#xff0c;当开…

建筑管理(2): 施工承包模式,工程监理,质量监督

文章目录 一. 施工承包模式1. 施工总承包模式1.1 施工总承包的特点1.2 施工总承包模式中的承包方 2. 平行承包模式3. 联合体与合作体承包模式 二. 工程监理1. 强制实行监理的工程范围1.1 国家重点建设工程1.2 大中型公用事业工程(重点)1.3 成片开发建设的住宅小区工程1.4 必须实…

[LeetCode热门100题]|137,260,268,面试17.19

1、137 只出现一次数字|| 1、题目描述 137 只出现一次数字||https://leetcode.cn/problems/single-number-ii/description/ 给你一个整数数组 nums &#xff0c;除某个元素仅出现 一次 外&#xff0c;其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。 你…

MySQL中有哪几种锁?

大家好&#xff0c;我是锋哥。今天分享关于【MySQL中有哪几种锁&#xff1f;】面试题。希望对大家有帮助&#xff1b; MySQL中有哪几种锁&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在MySQL中&#xff0c;锁是用来控制并发访问的机制&#xff0c;确…

外贸企业可以申请网络专线吗?

在对外业务不断扩大的情况下&#xff0c;外贸企业对网络的需求愈发迫切。稳定、快速的网络连接不仅是企业开展国际业务的基础&#xff0c;更是提升竞争力的关键。外贸企业是否可以申请网络专线&#xff1f;如何选择适合的外贸网络专线服务&#xff1f;本文将为您详细解答。 网络…

python笔记2

变量&#xff1a;含义 一个容器&#xff0c;计算机当中的存储空间。 可以理解为一个用于标识或引用数据的名字或标签。 作用&#xff1a; 可以通过定义一个变量来给需要使用多次的数据命名&#xff0c;就像一个标签一样。下次需要使用这个数据时&#xff0c;只需要通过这个变…

【算法】动态规划

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;Linux 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 持续更新中...1、常规动态规划Fibonacci数列杨辉三角最小花费爬楼梯孩子们的游戏 2、背包问题3、最长公共子序列4、最长递增子序列…

ECU BootLoader开发——Flash编程

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 简单&#xff0c;单纯&#xff0c;喜欢独处&#xff0c;独来独往&#xff0c;不易合同频过着接地气的生活…

NetLink内核套接字案例分析

一、基础知识 Netlink 是 Linux 系统中一种内核与用户空间通信的高效机制&#xff0c;而 Netlink 消息是这种通信的核心载体。它允许用户态程序&#xff08;如网络配置工具、监控工具&#xff09;与内核子系统&#xff08;如网络协议栈、设备驱动&#xff09;交换数据&#xff…

Linux 配置静态 IP

一、简介 在 Linux CentOS 系统中默认动态分配 IP 地址&#xff0c;每次启动虚拟机服务都是不一样的 IP&#xff0c;因此要配置静态 IP 地址避免每次都发生变化&#xff0c;下面将介绍配置静态 IP 的详细步骤。 首先先理解一下动态 IP 和静态 IP 的概念&#xff1a; 动态 IP…

开源:LMDB 操作工具:lmcmd

目录 什么是 LMDB为什么编写 lmcmd安装方法如何使用 连接数据库命令列表 小结 1. 什么是 LMDB LMDB&#xff08;Lightning Memory-Mapped Database&#xff09;是一种高效的键值存储数据库&#xff0c;基于内存映射&#xff08;memory-mapping&#xff09;技术&#xff0c;提供…