共读《redis设计与实现》-单机(一)

上一章我们讲了 redis 基本类型的 数据结构对象系统 ,这篇来说一下单机redis 的知识点。

一、数据库

一个数据库在redis中就有一个结构体,而数据库的结构体是由redisServer这个结构体持有。
也就是redis服务器对应一个redisService 结构体,一个redisServer结构体持有多个redisDB数组,并且存储了数组的大小。

struct redisServer {
    ...

    // 一个数组保存服务器所有的数据库
    redisDb *db;
    // 服务器的数据库的数量//初始默认为16
    int dbnum;
    // 过期字典// 保存键的过期时间
    dict *expires;

    // 记录rdb 保存条件的数组
    struct saveparam *saveparam;

    // 修改计数器
    long long dirty;

    // 上一次执行保存的时间
    time_t lastsave;

    // aof 缓冲区
    sds aof_buf;

    // 一个链表保存了所有客户端的状态
    list *client;
    ...

}

共读《redis设计与实现》-单机(一)

二、客户端

然后我们再说一下客户端,每个客户端是也是有一个redisClient结构体

typedef struct redisClient {
    . . .

    // 客户端的名称,使用Client setname 命令设置
    rojb *name;

    // 记录客户端正在使用的数据库
    redisDb *db;

    // 套接字描述符号,伪客户端为 -1;客户端为>-1的整数
    int fd;

    // 记录了客户端的角色,可以是单个值 也可以多个值
    int flags;

    // 客户端输入缓冲区,用来保存客户端输入的请求//不能超过1G,否则会关闭
    sds querbuf;

    // 客户端发送服务端请求之后,
    //服务端解析请求,将命令参数保存在客户端 argv 中,命令个数 保存在argc
    robj **argv;

    int argc;
    . . .
}

共读《redis设计与实现》-单机(一)
客户端的结构体中db 的指针指向 当前正在使用的数据库的地址。
共读《redis设计与实现》-单机(一)
共读《redis设计与实现》-单机(一)
所以当我们 使用 SELECT 命令切换数据库的时候就是将 redisClient 的db 指针切换了一个位置
共读《redis设计与实现》-单机(一)
注意点
共读《redis设计与实现》-单机(一)

三、键空间

我们之前看字典的时候已经讲过, 每个数据库其实就是一个字典,我们平常存储的数据在数据库这个字典中,key是字典的key,value 是字典的value。(其实每个key 就是一个SDS结构,所以字典的key 是一个SDS 结构的存储体,value 可能是SDS 可能是 字典、序列等其他基本结构体)

共读《redis设计与实现》-单机(一)

3.1 添加/更新/删除

其实键的 添加/更新/删除 就是在字典中 添加key-value 键值对 和 更新 删除 键值对的动作。

3.2 键的生存时间/过期时间

我们可以给键 设置一个时间 ,当 创建之后过多久就失效生存时间;当 到达某个时间点就失效过期时间

3.2.1 SETEX/SEPIRE/PEXPIRE/EXPIREAT/PEXPIREAT

键盘的过期时间,我们可以从redisServer 的结构体中可以看出,其实就是对 &#x6BCF;&#x4E2A;&#x952E;存储了一个</code>过期时间<code>。 ![](https://cdn.nlark.com/yuque/0/2022/png/25537751/1649908248233-6645ddfa-6e50-4b19-ab97-79e0ff07bf38.png#clientId=ud3c5508d-badf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=280&id=u228ad928&margin=%5Bobject%20Object%5D&name=image.png&originHeight=280&originWidth=1313&originalType=binary&ratio=1&rotation=0&showTitle=false&size=189635&status=done&style=none&taskId=u0eb92a1a-881a-402a-bb0e-1de8df137f0&title=&width=1313) Redis 有</code>四个不同的命令可以用于设置键的生存时间(键可以存在名久)或过期时间
(键什么时候会被删除):

  • EXPIRE

虽然有多种不同单位和不同形式的设置命令,但实际上 EXPIRE、PEXPIRE、EXPIREAI
三个命令都是使用 PEXPIREAT 命令来实现的:无论客户端执行的是以上四个命令中的哪-
个, &#x7ECF;&#x8FC7;&#x8F6C;&#x6362;之后, &#x6700;&#x7EC8;的执行效果都和执行 PEXPIREAT命令一样

共读《redis设计与实现》-单机(一)
所以最后干活的是PEXPIREAT ,其他的就是对于不同业务下的衍生api 而已。

关于TTL/PTTL 命令

共读《redis设计与实现》-单机(一)
共读《redis设计与实现》-单机(一)

3.2.2 过期键的删除策略

  • 定时删除:在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作。
  • 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。
  • 定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。

在这三种策略中,第一种和第三种为主动删除策略,而第二种则为被动删除策略。

三种策略优缺点

定时删除 能够及时释放 内存空间,但是如果遇到大量对键 过期,那么会占用很大对cpu 资源
惰性删除 能够解决cpu 资源对问题,但是 会浪费大量对存储空间,有 内存泄漏 的风险
定期删除 相当于平衡 前两种的优缺点。

一般我们将 &#x60F0;&#x6027;&#x5220;&#x9664;&#x5B9A;&#x671F;&#x5220;&#x9664;配合使用 具体使用: **惰性**:所有读写库的redis 数据库执行 命令之前 都会调用expireIfveeded 函数检查过期时间,如果过期,那么就删除 ![](https://cdn.nlark.com/yuque/0/2022/png/25537751/1649910262435-f2901713-7cfc-48bc-9e22-3842ce8604ac.png#clientId=ud3c5508d-badf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=582&id=ub92309b4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=582&originWidth=1241&originalType=binary&ratio=1&rotation=0&showTitle=true&size=225843&status=done&style=none&taskId=ubf65c1b3-3e06-4ed3-8ff0-4da25bbe291&title=%E6%83%B0%E6%80%A7%E5%88%A0%E9%99%A4%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B&width=1241 "惰性删除执行过程") **定期**:redis</code>周期性<code>函数</code>Servercron<code>执行 的时候 会调用</code>activeExpireCycle<code>函数,在</code>规定时间<code>内 遍历一次数据库,</code>随机<code>的访问一些键 查看过期时间,</code>过期删除

3.3 RDB/AOF 持久化时 过期键处理

RDB

生成
对于RDB 快照类型的,如果是过期了,那么下一次生成快照的时候就不会记录在RDB文件中
载入
如果是 &#x4E3B;&#x670D;&#x52A1;&#x5668; 加载 RDB文件,那么会对键的过期时间进行 &#x68C0;&#x67E5;,如果是过期了(在生成RDB文件和加载RDB文件之间的时间段内过期),那就不会被加载。
如果是 &#x4ECE;&#x670D;&#x52A1;&#x5668; 加载 RDB 文件,那么 &#x4E0D;&#x4F1A;检查过期键, &#x5168;&#x90E8;&#x52A0;&#x8F7D;。(但是和 &#x4E3B;&#x670D;&#x52A1;&#x5668;同步的时候,从服务器的数据库都会被</code>清空

AOF

生成:
只要还没有被删除,那么不会对AOF产生影响,AOF会全部记录,如果执行期间被删除 会增加一条DELE命令
加载:
如果是过期了,那么不会被加载

3.4 复制期间过期间处理

主服务器 删除的时候 会向 从服务器 发送删除命令
从服务器 遇到过期键 不会处理,和平常的键一样,即使客户端查询也会返回。

共读《redis设计与实现》-单机(一)
共读《redis设计与实现》-单机(一)
我们知道 redis 是一个内存数据库,也就是所有的数据都在内存中,cpu 取数据的时候直接从内存中查找,不用在调用系统io 从磁盘加载数据了。这也就是为何redis 比其他 存储磁盘的数据库快的原因。
但是在内存中的数据有个极大的缺点:如果服务器一旦关闭,那么数据就不在了,因为内存的数据并没有写到磁盘上,所以redis 需要提供一个能够写入磁盘的机制。
redis 提供了两种持久化机制:rdb持久化 aof持久化

键空间的维护操作

当redis 命令对于数据库的读写时,服务器不仅会对键空间进行客户端的请求命令,还会执行一些额外的操作

  1. 命中率:读取一个键之后,服务器会依据进键是否存在来更新 键空间命中率 和 不命中率
  2. 闲置时间:就是键多久没有访问过了,等到命中这个键时会更新这个值。
  3. 过期键
  4. watch 命令:使用warch 命令监控一个键,那么键修改时会将这个键置成 脏
  5. 脏数据:服务器每修改一个键,会对 脏键 计数器的值+1;
  6. 数据库通知功能:如果开启这个功能,那么修改redis 会通知数据库

四、RDB 持久化

对于RDB 持久化,我们可以认为就是一个内存的快照,也就是将某一瞬间redis 的数据给存储下来。
使用这个持久化有两种命令:SAVE / BGSAVE

SAVE

这个命令持久化的时候,redis 处于阻塞状态,也就是redis 不接受客户端发过来的任何请求,全力的去处理这个请求。

BGSAVE

对于save 来说,我们要是因为持久化导致redis 不能使用,这个显然会有问题。因为如果数据量特别多,那么我们为了持久化,消耗的时间也就很多,业务阻塞了。
和save 不同,为了能够使得持久化同时也能运行客户端请求,redis 对于bgsave 分出一个线程去处理 持久化,这样就不是阻塞的了。

save 和 bgsave 不能够同时执行,考虑防止竞争问题、同时操作io的效率问题。

载入

因为RDB存储的是redis 的快照,所以redis 没有载入 rdb文件的命令,程序启动的时候会自动加载rdb文件。

另外一点需要注意的是:因为aof文件比rdb 更新的更频繁,也就是数据更新,那么存在aof文件,就会加载aof文件而不会加载rdb文件了,可以使用配置将aof文件读取关闭

共读《redis设计与实现》-单机(一)

自动保存条件

redis 对于bgsave 可以设置每隔多久进行一次rdb 保存的,可以通过启动是save 参数进行设置。
我们可以从redisServer 的结构中看出,这个自动保存的条件其实是存储起来的,也就是redisServer持有这个自动存储条件,并在规定条件下进行一次调用BGSAVE命令

共读《redis设计与实现》-单机(一)

dirty 计数器 和lastsave 属性

服务器在每执行一次操作都会更新一次dirty计数器,比如dirty 计数器为123,那么说明距离上次保存,服务器执行了123次命令。

服务器 之所以可以 &#x81EA;&#x52A8;&#x4FDD;&#x5B58;,是因为 &#x65F6;&#x95F4;&#x4E8B;&#x4EF6; 不断的去扫描 redisServer 然后看 saveparams &#x5C5E;&#x6027; 是否满足自动保存。然后在调用 bgsave

共读《redis设计与实现》-单机(一)
serverCrom 函数会 遍历 saveparams ,看其中的条件是不是被满足了

RDB文件结构

rdb文件以二进制形式存储,我们可以通过 od 命令来解析 rdb文件

文件结构

共读《redis设计与实现》-单机(一)
说明
我们用全大写表示 常量标识;使用全小写标识 变量
  1. REDIS:常量标识符(5字节)
  2. db_version:版本号(4字节)
  3. 数据库:也就是存储的具体数据,具体长度由保存的数据来说明,如果没有数据,那就没有这个字段
  4. EOF:结束标识位(1字节),也就是如果读到这个地方表示 rdb文件正文 &#x8BFB;&#x53D6;完毕
  5. check_sum:校验位置,就是前面的数字长度;

共读《redis设计与实现》-单机(一)
SELECTDB:常量(1字节),标识为这里是数据库
db_number:数据库号码(1-5字节不等)
key_value_pairs:数据库中具体存储的值
共读《redis设计与实现》-单机(一)
rdb文件中 数据库中 数据的值(k-v结构)

共读《redis设计与实现》-单机(一)
EXPIRETIME_MS:常量,标识带有过期键
过期键的时间
KEYTYPE:上图写的是REDIS_RDB_TYPE_SET,这个是 存储的类型,方便读取 value 的值
key: 存储的key
value :存储的value 可能是 SDS、HASH 之类的。

五、AOF 持久化

共读《redis设计与实现》-单机(一)
AOF 持久化功能的实现可以分为 &#x547D;&#x4EE4;&#x8FFD;&#x52A0;&#xFF08;append &#xFF09;&#x6587;&#x4EF6;&#x5199;&#x4EBA;&#x6587;&#x4EF6;&#x540C;&#x6B65; (sync)三个步骤。

命令追加

当 AOF 持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到redisServer 结构体中 的 aof buf &#x7F13;&#x51B2;&#x533A;&#x672B;&#x5C3E;如果在来一条命令,那么在向缓冲区末尾添加。

Redis 的服务器进程就是一个 &#x4E8B;&#x4EF6;&#x5FAA;&#x73AF;(1oop),这个循环中的 &#x6587;&#x4EF6;&#x4E8B;&#x4EF6;负责接收 &#x5BA2;&#x6237;&#x7AEF;&#x547D;&#x4EE4;&#x8BF7;&#x6C42;,以及向客户端发送命令回复,而 &#x65F6;&#x95F4;&#x4E8B;&#x4EF6;则负责执行像servercron 函数这样需要 &#x5B9A;&#x65F6;&#x8FD0;&#x884C;的函数。---这段看不懂就略过,其实是 下面要说 的事件

因为服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof buf 缓冲区里面,所以在服务器每次结束一个事件循环之前,它都会调用 f1ushAppendonlyFile函数,考虑是否需要将 aof buf 缓冲区中的内容写人和保存到 AOF 文件里面。

共读《redis设计与实现》-单机(一)
共读《redis设计与实现》-单机(一)
共读《redis设计与实现》-单机(一)

载入/数据还原

共读《redis设计与实现》-单机(一)
创建一个 &#x4E0D;&#x5E26;&#x7F51;&#x7EDC;&#x8FDE;&#x63A5;&#x4F2A;&#x5BA2;&#x6237;&#x7AEF;(fake client )是因为 Redis 的命令 &#x53EA;&#x80FD;&#x5BA2;&#x6237;&#x7AEF;&#x4E0A;&#x4E0B;&#x6587;&#x6267;&#x884C;,而载人 AOF 文件时所使用的命令直接来 &#x6E90;&#x4E8E; AOF &#x6587;&#x4EF6;而不是网络连接,所以服务器使用了一个 &#x6CA1;&#x6709;&#x7F51;&#x7EDC;&#x8FDE;&#x63A5;&#x4F2A;&#x5BA2;&#x6237;,和客户端效果是一样的。

AOF 重写

如果我们redis 运行的事件长了,那么就会使得aof 文件变得很大,而且这个文件中很多命令是浪费空间的,比如 push key v1;push key v2... 所以,redis 对aof 文件进行了重写,让这些命令合并为一条命令,减少aof 的空间

重写原理:

aof 重写 &#x4E0D;&#x9700;&#x8981; 进行 读取/写入 原 aof 文件,也就是 完全 &#x4E0D;&#x64CD;&#x4F5C;&#x539F;&#x6587;&#x4EF6;
他主要是 &#x770B;&#x6570;&#x636E;&#x5E93;&#x4E2D; 数据的 &#x72B6;&#x6001;,使用命令将 数据库中的数据 写入文件

比如:
数据库中有个 numbers:one,two,three
这样的结构,之前是
push numbers one;
push numbers two;
push numbers three;
三个命令
我们直接读取数据库,我们不清楚过程,所以我们将之前的三个命令变成一个:
push numbers one two three
这样就是压缩了

之前重写aof 文件的时候都是 不接受新的命令,为了不影响 使用,所以使用了后台重写 命令。

后台重写,为了保证数据的一致性,使用了aof 重写缓冲区。

共读《redis设计与实现》-单机(一)

共读《redis设计与实现》-单机(一)
共读《redis设计与实现》-单机(一)
共读《redis设计与实现》-单机(一)

文件写入和同步

共读《redis设计与实现》-单机(一)

六、事件

Redis 服务器是一个 &#x4E8B;&#x4EF6;&#x9A71;&#x52A8;&#x7A0B;&#x5E8F;,服务器需要处理以下 &#x4E24;&#x7C7B;事件:

  • &#x6587;&#x4EF6;&#x4E8B;&#x4EF6;(file event ):Redis &#x670D;&#x52A1;&#x5668;通过 &#x5957;&#x63A5;&#x5B57;(含义就是通过网络链接)与客户端(或者其他 Redis 服务器)进行连接,而 &#x6587;&#x4EF6;&#x4E8B;&#x4EF6;就是 &#x670D;&#x52A1;&#x5668;&#x5957;&#x63A5;&#x5B57;操作的抽象。服务器与客户端(或者其他服务器)的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作。
  • &#x65F6;&#x95F4;&#x4E8B;&#x4EF6;(time event ):Redis 服务器中的一些操作(比如servercron 两数)需要在 &#x7ED9;&#x5B9A;&#x65F6;&#x95F4;&#x70B9;执行,而 &#x65F6;&#x95F4;&#x4E8B;&#x4EF6;就是服务器对这类定时操作的抽象。

文本事件

Redis 基于 Reactor&#x6A21;&#x5F0F;开发了自己的 &#x7F51;&#x7EDC;&#x4E8B;&#x4EF6;&#x5904;&#x7406;&#x5668;:这个处理器被称为 &#x6587;&#x4EF6;&#x4E8B;&#x4EF6;&#x5904;&#x7406;&#x5668;(file event handler):

  • 文件事件处理器使用 I/0 &#x591A;&#x8DEF;&#x590D;&#x7528;(multiplexing)【https://www.cnblogs.com/zhangxiaoji/p/16152141.html】程序来同时监听多个套接宇,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。

共读《redis设计与实现》-单机(一)
  • 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写人(write入关闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件虽然文件事件处理器以单线程方式运行,但通过使用 IO 多路复用程序来监听多个套接宇,文件事件处理器既实现了高性能的网络通信模型(可以理解为 一个服务端使用了一个线程(或者少量的线程)来处理多个客户端请求),又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接,这保持了 Redis 内部单线程设计的简单性

共读《redis设计与实现》-单机(一)
注意:io 多路复用和 文本事件分派器 中间的队列是 单个的,也就是文件事件分派器一次只处理一个事件。
共读《redis设计与实现》-单机(一)
共读《redis设计与实现》-单机(一)

时间事件

我们从之前的讲述中可以看到,和时间相关的都是由 redisCron 函数进行处理的,那么它就是我们的 时间事件应用实例了。
Redis 的时间事件分为以下两类:

  • 定时事件:让一段程序在指定的时间之后执行一次。比如说,让程序× 在当前时间
    的 30毫秒之后执行一次。
  • 周期性事件:让一段程序每隔指定时间就执行一次。比如说,让程序Y每隔 30毫秒就执行-
    一次。

个时间事件主要由以下三个属性组成:

  1. id:服务器为时间事件创建的全局唯一四D(标识号)。1D号按从小到大的顺序递增,
    新事件的1D 号比旧事件的1D 号要大。
  2. when:毫秒精度的 UNIX 时间戳,记录了时间事件的到达(arrive)时间。
  3. timeProc:时间事件处理器,一个两数。当时间事件到达时,服务器就会调用相
    应的处理器来处理事件。

事件的调度与执行

因为redis 存在两种事件类型,所以 redis 必须有个调度器去解决何时处理 文本事件 何时 处理 时间事件
这个是 aeProcessEvents函数来进行的

共读《redis设计与实现》-单机(一)

后面对 服务器 和 客户端 在进行详细的研究。

参考资料#
《Redis设计与实现》-黄健宏
部分图片来与百度搜索

Original: https://www.cnblogs.com/zhangxiaoji/p/16156115.html
Author: 张小吉
Title: 共读《redis设计与实现》-单机(一)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/579224/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 多线程基础篇

    基本概念 (程序,进程,线程) 线程的创建和使用 线程的生命周期 线程的同步 线程的通信 JDK5.0新增线程创建方式 &#x7A0B;&#x5E8F; (prog…

    Java 2023年6月6日
    086
  • 自写一个生成ID的工具类

    平时项目中只要涉及表,那么一定能接触到众多各式各样的ID编号,博主整理一些常用的ID格式,整合一个ID生成工具类,供大家参考,如果有什么不足指出,烦请留言批评指正,尽量改正,感激不…

    Java 2023年6月7日
    093
  • Lambda-让人又爱又恨的“->”

    写在前边 聊到Java8新特性,我们第一反应想到的肯定是Lambda表达式和函数式接口的出现。要说ta到底有没有在一定程度上”优化”了代码的简洁性呢?抑或是…

    Java 2023年6月5日
    088
  • C# 线程手册 第六章 线程调试与跟踪 DataImport 例子

    现在我们要集中精力实现一个实战实例来描述到目前为止我们已经看过的内容。这里要实现的DataImport 例子是那种等待文件到达指定目录然后将其导入到一个SQL Server 数据库…

    Java 2023年5月29日
    061
  • JAVA使用Session获取用户信息

    JAVA使用Session获取用户信息 1. 在登录的Controller中将用户信息塞入Session //前端传入用户信息 @RequestMapping("/log…

    Java 2023年6月13日
    081
  • MySQL服务器启动失败的问题

    MySQL服务器启动失败的问题 在启动mysql服务的时候失败了使用 mysqld –console这个命令查看一下日志信息,查询到如下的报错情况: [ERROR] Can’t …

    Java 2023年6月15日
    083
  • 实际业务处理 Kafka 消息丢失、重复消费和顺序消费的问题

    消息丢失,消息重复消费,消息顺序消费等问题是我们使用 MQ 时不得不考虑的一个问题,下面我结合实际的业务来和你分享一下解决方案。 比如我们使用 Kakfa 时,以下场景都会发生消息…

    Java 2023年6月9日
    070
  • git使用技巧

    1.如果commit了不想提交的内容,如何回滚本地不影响其他 使用 命令 git reset –soft HEAD^ , 可以回退本次commit的内容 , 并保持本地…

    Java 2023年6月15日
    088
  • Tomcat源码分析(二)Bootstrap启动类分析

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年6月5日
    077
  • SpringBoot整合JDBC

    SpringBoot整合JDBC 1、新建一个 jdbccontroller.java @RestController public class JDBCController { …

    Java 2023年6月7日
    075
  • 12.Zuul网关

    Zuul简介 网关介绍 由于微服务”各自为政的特性”使微服务的使用非常麻烦 通常我们会雇佣一个”传达室大爷”作为统一入口,这就是网关…

    Java 2023年6月8日
    078
  • Java: Visitor Pattern

    调用: 输出: Original: https://www.cnblogs.com/geovindu/p/16743696.htmlAuthor: ®Geovin Du Dream…

    Java 2023年6月16日
    090
  • springboot修改打包后的项目(jar war)名称

    springboot修改打包后的项目(jar war)名称 在build里面添加inalName,指定好想要额项目名称即可: Original: https://www.cnblo…

    Java 2023年5月30日
    076
  • Java 将HTML转为XML

    本文介绍如何通过Java后端程序代码来展示如何将html转为XML。此功能通过采用Word API- Free Spire.Doc for Java 提供的 Document.sa…

    Java 2023年5月29日
    083
  • Java基础–异常处理

    Java中的错误会以对象方式呈现为 java.lang.Throwable的个种子类示例。通过捕获包装错误的对象,可以针对错误做一些对应的处理。本文主要记录其中比较容易记错的点。 …

    Java 2023年6月5日
    084
  • 10、线程强制执行jion

    10、线程强制执行jion java;gutter:true; package com.testthread1;</p> <p>public class T…

    Java 2023年6月8日
    081
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球