【八股】暑期实习八股复盘(三月)

1. 计算机网络

1.1 为什么用 UDP 不用 TCP

如:视频会议场景中为什么用UDP而不用TCP?

TCP 是面向连接的协议,为了保证数据包的可靠传输,需要引入重传机制、拥塞控制等,会造成音视频不同步等延迟或者卡顿,影响用户体验。

1.1.1 超时重传

  • 当TCP发送一个数据包时,会为该数据包启动一个重传计时器(Retransmission Timer)​,等待接收方的确认(ACK)。如果发送方在计时器超时之前收到接收方的确认(ACK),则认为数据包已成功传输,计时器停止。如果计时器超时仍未收到ACK,发送方认为数据包可能丢失或接收方未收到,于是重传该数据包
  • TCP使用RTO(Retransmission Timeout)​作为超时时间,RTO的值基于RTT(Round-Trip Time,往返时间)​动态计算。
  • 如果网络延迟增加,RTO会相应增大;如果网络延迟减少,RTO会减小。
  • 如果重传后仍未收到ACK,TCP会采用指数退避策略,即每次重传时将RTO加倍,避免在网络拥塞时进一步加剧问题。
  • 除了超时重传,TCP还支持快速重传机制:如果发送方连续收到3个相同的重复ACK(Dup-ACK),则认为数据包丢失,立即重传,而不等待超时。

1.1.2 拥塞控制

TCP 的拥塞控制机制旨在防止网络过载,确保网络资源的公平使用和高效分配。其核心思想是通过动态调整发送速率来适应网络的当前状态。以下是 TCP 拥塞控制的主要机制:

  • 加性增 - 慢开始:cwnd 从 1 开始,每个 RTT 翻倍增长,直到达到 sstthresh。若超过 ssthresh,cwnd会定到ssthresh
  • 加性增 - 拥塞避免:cwnd 增长速度放缓,每个 RTT 增加一个 MSS(最大报文段长度)
  • 乘性减 - 检测拥塞:长时间未收到 ACK,认为发生拥塞,ssthresh 设置为当前 cwmd 一半,cwnd 重新被设置为 1
  • 乘性减 - 快重传:连续收到三次重复的 ACK,认为中途数据丢失,ssthresh 设置为当前 cwnd 一半
  • TCP Tahoe:cwnd 设置为 1
  • TCP Reno(快恢复):cwnd 设置为 ssthresh,即直接减半

1.1.3 TCP 首部

UDP 首部非常简单,仅包含 8 字节,记录以下信息:

  • 源端口号(16 位)
  • 目的端口号(16 位)
  • 长度(16 位)
  • 校验和(16 位)

相比之下,TCP 首部多记录了以下信息:

  1. 序列号和确认号:各32位,用于实现可靠传输,确保数据按顺序到达。
  2. 控制位(Flags)​:6位,用于管理连接状态(如建立、关闭连接)。如 SYN、ACK、RST、FIN 等。
  3. 窗口大小:用于流量控制,避免接收方缓冲区溢出。
  4. 紧急指针:支持紧急数据的传输。
  5. 选项字段:支持扩展功能,如 MSS、窗口缩放等。

1.2 从输入 URL 到页面展示

大多数八股资料上都是从顶向下进行阐述,那我也这样重新表述一遍吧。

  • 明确目标 IP ,封装到端到端传输:
  • HTTP:要想让浏览器展示网页,我们需要解析 URL 通过 HTTP 协议发 GET 请求来获取响应的 HTML。
  • TCP:其中 HTTP 基于 TCP 实现,TCP 使用 ipv4 或 ipv6 进行通信。
  • DNS:其中由域名转换为公网 ipv4 需要 DNS 解析协议。
  • 切开端到端的传输过程(IPIP):
  • WAN 与外网通信:NAT
  • LAN 内网通信:判断是否在同一子网,若不在同一子网,则访问数个网关,进行路由选择转发(RIP or OSPF)
  • 访问网关需要知道网关的 MAC 地址,需要有 ARP 协议
  • 想要走 ARP,又需要先知道自己被分配的 IP 地址 和 网关 IP 地址,所以从校园网 DHCP 开始。

具体考点

1.3 应用层负载均衡技术的单点故障

我们访问一个 ip 时,如果我们采用 Nginx 或者 SpringCloud GateWay 对应这个进程负责转发到集群进程时,如果负责转发这个进程(如 Nginx)需要高可用怎么办?

  • 主备机制:通过 keepalived 等组件配置主备节点,当主节点故障时,备用节点会自动接管 VIP,确保服务不中断。
  • CDN:使用 CDN 或全球负载均衡(GSLB)将流量分发到不同区域的负载均衡器。
  • 传输层负载均衡:四层负载均衡工作在 OSI 模型的传输层(第四层)​,基于 IP 地址和端口号进行流量分发。四层负载均衡器不解析应用层内容,通过修改数据包的目标 IP 地址和端口号,将请求转发到不同的后端进程。常见的四层负载均衡器包括 ​LVS 和 ​F5。

1.4 TCP 三次握手四次挥手各个状态

2. 操作系统

2.1 虚拟内存

以下是整个框架的内容梳理:

2.1.1 地址空间与转换

  • 地址空间与虚拟内存
  • 早期机器的物理内存情况:操作系统和运行程序直接在物理内存中,没有太多抽象,用户要求少,如单片机开发直接操作物理地址。
  • 多道程序系统和分时共享系统出现后,引入进程概念,但存在内存分配冲突、进程间相互干扰等问题。
  • 引入地址空间和虚拟内存:
  • 每个进程有自己的地址空间,如假设 16KB,包括代码段(静态空间)、栈(向下增长)、堆(向上增长)。
  • 程序认为自己被加载到地址 0 开始的内存中,但实际上通过虚拟化内存机制加载到合适的物理地址。
  • 虚拟化内存的目标:透明假象(程序不知内存被虚拟化)、效率(需硬件支持与 TLB)、保护(保证进程隔离)。
  • 地址转换机制
  • 假设:用户地址空间连续放物理内存中、地址空间小于物理内存大小、每个地址空间大小相同。
  • 动态重定位实现:
  • 通过基地址寄存器和界限寄存器(统称 MMU)实现。
  • 特殊指令用于修改基址和界限寄存器,这些指令是特权指令,只能在内核模式下修改。
  • 操作系统在动态重定位中的介入工作:进程创建时分配内存、进程终止时回收内存、上下文切换时保存和恢复寄存器内容、CPU 异常时提供异常处理程序。

2.1.2 内存管理机制

  • 分段
  • 需要考虑的问题:栈和堆之间存在未使用的“空闲”空间占用物理内存;剩余物理内存可能无法提供连续区域放置完整地址空间。
  • 分段概念:在 MMU 中引入多个基址和界限寄存器对,每个逻辑段(代码、栈、堆)一对。分段机制避免了虚拟地址空间中未使用部分占用物理内存。
  • 地址转换例子:代码段、堆、栈的虚拟地址到物理地址的转换过程,包括偏移量的计算和反向增长的处理。硬件通过虚拟地址的前两位确定段,后 12 位作为段内偏移,与基址寄存器相加得到物理地址。
  • 分段的好处:代码共享,独立的代码段可被多个程序共享。
  • 问题:
  • 外部碎片(物理内存中存在许多小的空闲空间,难以分配给新段或扩大已有段);
  • 管理物理内存空闲空间成本高;
  • 分段不能很好地支持稀疏地址空间。
  • 解决外部碎片的方法:紧凑物理内存,重新安排原有段,但成本高。
  • 分页:
  • 分页思路:将空间分割成固定长度的分片(页),物理内存看成定长槽块的阵列(页帧),每个页帧包含一个虚拟内存页。
  • 页表:操作系统为每个进程保存的数据结构,记录虚拟页在物理内存中的位置。
  • 虚拟地址转换:将虚拟地址分成虚拟页面号(VPN)和页内偏移量,通过 VPN 查找页表得到物理帧号(PFN),偏移量不变,得到最终物理地址。
  • 优点:不会导致外部碎片,支持稀疏虚拟地址空间。
  • 问题:额外的内存访问访问页表导致速度变慢,页表可能占用过多内存。
  • 快速地址转换 TLB
  • 问题:分页方法中,每个内存引用都需要额外的内存引用从页表中获取地址转换,工作量大。
  • 解决办法:引入 TLB(地址转换旁路缓冲存储器),作为“缓存”存储当前 VPN 的页表项。
  • TLB 未命中处理方式:
  • 硬件处理(CISC 指令集):硬件遍历页表,找到正确页表项,更新 TLB,重试指令。
  • 操作系统软件处理(RISC 指令集):硬件抛出异常,操作系统处理,查找页表中的转换映射,更新 TLB,从陷阱返回,硬件重试指令。
  • TLB 插入新项时的缓存替换策略:LRU、随机策略等。
  • 上下文切换问题:TLB 内容只对当前进程有效,上下文切换时需处理 TLB 内容。直接清空 TLB 有开销,一些系统通过硬件支持实现跨上下文切换的 TLB 共享,如添加 ASID。
  • 实现较小页表的数据结构单级页表问题:
  • 页表大小与 VPN 位数有关,降低 VPN 位数会导致内存碎片问题。
  • 多级页表:
  • 将 VPN 分成多个部分,每一部分对应一个页表层次。
  • 以两级页表为例,第一级页表(页目录)索引第二级页表,第二级页表索引物理页帧。
  • 优势:稀疏映射(节省未使用虚拟页的页表空间)、按需加载(只有部分页表需驻留内存)。

2.1.3 页面置换 Swap

  • 页表可能太大无法一次装入内存,系统将页表放入内核虚拟内存,内存压力大时可将部分页表交换到磁盘。
  • 缺页中断:定义:CPU 访问页面不在物理内存时产生,请求操作系统将所缺页调入物理内存。与一般中断的区别:产生和处理时间不同,返回执行位置不同。处理流程:发生缺页中断:硬件查找页表中 PTE,存在位无效则发出缺页中断请求。操作系统进行 I/O:根据 PTE 中的硬盘地址查找磁盘页面位置,将页面换入物理内存空闲页。操作系统更新页表:更新 PTE 标记页面存在,更新 PFN 字段记录新物理页位置。重试指令。

3. RocketMQ

3.1 RocketMQ 消息模型

3.2 一个 Broker 是如何保存数据的

RocketMQ 主要的存储文件包括 CommitLog 文件、ConsumeQueue 文件、Indexfile 文件。

3.2.1 存储机制

1. ​CommitLog 文件

  • 作用:CommitLog 是 RocketMQ 的核心存储文件,所有消息(无论属于哪个 Topic 或 Queue)都按顺序追加写入到 CommitLog 文件中。
  • 特点
  • 顺序写入:消息按照到达 Broker 的顺序写入 CommitLog,确保高性能。
  • 持久化存储:CommitLog 是消息的最终存储位置,消息不会丢失。
  • 不分 Topic 或 Queue:所有消息混在一起存储,通过其他文件(如 ConsumeQueue)来组织消息的索引。
  • 存储结构
  • 每条消息包含 Topic、QueueId、消息体、消息属性等信息。
  • 消息在 CommitLog 中的位置(偏移量)称为 物理偏移量(Physical Offset)。

2. ​ConsumeQueue 文件

  • 作用:ConsumeQueue 是 CommitLog 的索引文件,用于将消息按 Topic 和 Queue 进行逻辑分组,方便消费者拉取消息。
  • 特点
  • 按 Topic 和 Queue 组织:每个 Topic 的每个 Queue 对应一个 ConsumeQueue 文件。
  • 逻辑偏移量:ConsumeQueue 中存储的是消息的 逻辑偏移量(Logical Offset),用于标识消息在 Queue 中的顺序。
  • 快速定位:ConsumeQueue 记录了消息在 CommitLog 中的物理偏移量、消息大小等信息,方便快速定位消息。
  • 存储结构
  • 每条记录包含:消息在 CommitLog 中的物理偏移量、消息大小、消息的 Tag 哈希值。
  • ConsumeQueue 文件是定长的(20 字节),便于快速查找。

3. ​IndexFile 文件

  • 作用:IndexFile 是 RocketMQ 的二级索引文件,用于支持基于消息 Key 或时间范围的消息查询。
  • 特点
  • 按消息 Key 索引:IndexFile 记录了消息的 Key 和其在 CommitLog 中的物理偏移量。
  • 支持快速查询:通过 Key 或时间范围,可以快速定位消息。
  • 非必须:IndexFile 是可选的,主要用于消息回溯或查询场景。
  • 存储结构
  • 每条记录包含:消息 Key 的哈希值、消息在 CommitLog 中的物理偏移量、消息存储时间戳。

3.2.2 Topic-MessageQueue-ConsumerGroup 模型的实现

  • ​Topic:
  • Topic 是消息的逻辑分类,生产者将消息发送到指定的 Topic。
  • 在 RocketMQ 中,Topic 是一个逻辑概念,实际存储是通过 CommitLog 和 ConsumeQueue 实现的。
  • ​MessageQueue:
  • 每个 Topic 可以划分为多个 Queue(默认 4 个),Queue 是消息的并行单元。
  • 消息按照 Queue 的顺序写入 CommitLog,并通过 ConsumeQueue 记录每个 Queue 的消息索引。
  • 消费者按 Queue 拉取消息,确保消息的顺序性和并行消费。
  • ​ConsumerGroup:
  • ConsumerGroup 是消费者的逻辑分组,多个消费者可以属于同一个 ConsumerGroup。
  • 同一个 ConsumerGroup 的消费者共享 Queue 的消费进度(Offset),实现负载均衡。
  • 每个 Queue 只能被一个 ConsumerGroup 中的一个消费者消费,确保消息不会被重复消费。

3.2.3 工作流程

  1. ​消息写入:
  2. 生产者将消息发送到 Broker,Broker 将消息按顺序追加写入 CommitLog。
  3. Broker 同时更新对应的 ConsumeQueue 文件,记录消息的物理偏移量和逻辑偏移量。
  4. 如果需要,Broker 还会更新 IndexFile 文件,记录消息的 Key 和物理偏移量。
  5. ​消息读取:
  6. 消费者从 ConsumeQueue 中获取消息的逻辑偏移量。
  7. 根据逻辑偏移量,从 CommitLog 中读取消息的物理偏移量,并拉取消息内容。
  8. 如果需要按 Key 查询消息,可以通过 IndexFile 快速定位消息。
  9. ​消息消费:
  10. 消费者按 Queue 拉取消息,并更新消费进度(Offset)。
  11. Broker 会记录每个 ConsumerGroup 的消费进度,确保消息不会重复消费。

3.3 RocketMQ 顺序消息

RocketMQ 如何保证消息顺序呢?

1. ​顺序消息的分类

RocketMQ 支持两种级别的顺序消息:

  1. 分区顺序消息(Partitionally Ordered Message)​:保证同一个 ​MessageQueue 中的消息按照发送顺序被消费。适用于需要部分顺序的场景,例如同一个订单 ID 的消息需要按顺序处理。
  2. 全局顺序消息(Globally Ordered Message)​:保证整个 ​Topic 中的消息按照发送顺序被消费。适用于需要严格全局顺序的场景,但性能较低。

2. ​实现顺序消息的关键机制

2.1 ​MessageQueue 的顺序性

  • RocketMQ 的 Topic 被划分为多个 ​MessageQueue,每个 Queue 是消息的并行单元。
  • 消息在同一个 Queue 中是严格按顺序存储的(写入 CommitLog 和 ConsumeQueue 的顺序)。
  • 消费者按 Queue 拉取消息,确保同一个 Queue 中的消息按顺序消费。

2.2 ​队列锁定机制

  • 为了保证顺序消费,RocketMQ 引入了 ​队列锁定机制:消费者在消费某个 Queue 时,会锁定该 Queue,确保同一时间只有一个消费者消费该 Queue。如果消费者宕机或超时,锁会被释放,其他消费者可以接管该 Queue。
  • 这种机制确保了同一个 Queue 中的消息不会被多个消费者并发消费,从而保证顺序性。

2.3 ​顺序消息的生产

  • 生产者发送顺序消息时,需要指定 ​消息选择器(MessageQueueSelector)​,将同一组消息发送到同一个 Queue。
  • 例如,订单消息可以根据订单 ID 选择 Queue,确保同一个订单的消息进入同一个 Queue。

2.4 ​顺序消息的消费

  • 消费者通过 ​顺序消费模式(MessageListenerOrderly)​ 消费消息。
  • 在顺序消费模式下,消费者会按顺序逐个处理 Queue 中的消息,不会并发消费。
  • 如果某条消息消费失败,消费者会重试该消息,直到成功或达到最大重试次数。

3.4 RocketMQ 事务消息

RocketMQ 事务消息是 Apache RocketMQ 提供的一种分布式事务解决方案,它能够确保消息发送与本地事务执行的最终一致性。

3.4.1 核心概念

  1. 半消息(Half Message):事务消息的第一阶段,消息会被发送到 Broker,但此时消费者不可见
  2. 本地事务执行:消息发送方执行本地业务逻辑
  3. 事务状态回查:Broker 定期向生产者询问事务状态
  4. 事务提交/回滚:根据本地事务执行结果决定消息是提交(对消费者可见)还是回滚(删除)

3.4.2 工作原理

  1. 发送半消息:生产者发送"准备就绪"状态的消息到 Broker
  2. 执行本地事务:生产者执行与消息相关的业务逻辑
  3. 提交/回滚事务
  4. 成功:提交事务,消息变为可消费状态
  5. 失败:回滚事务,消息被丢弃
  6. 事务状态回查:如果生产者未明确提交或回滚,Broker 会定期询问事务状态

3.4.3 使用场景

  • 订单创建与库存扣减的分布式事务
  • 支付与账户余额变更的一致性保证
  • 任何需要确保消息发送与业务操作一致性的场景

4. 设计模式

阿里面试官非常喜欢设计模式,面阿里之前一定要看一下。

4.1 工厂模式

核心思想:使用一个 XxxxFactory接口,用它的实现来进行各种接口对象的实例化。

interface CreatorFactory {
	ProductA getProductA();
	ProductB getProductB();
}

// 可以有实现类 ProductA1 ProductA2 等
interface ProductA() {
	void operationA();
}

interface ProductB() {
	void operationA();
}

4.2 单例模式

其实单例模式不只是实例化,有的时候它也可以延迟类加载的初始化部分。

4.2.1 JVM 类加载

在Java中,一个类的加载过程可以分为三个主要步骤,这些步骤是按照特定的顺序执行的:

  1. 加载:在这个阶段,JVM会为这个类创建一个java.lang.Class对象,这个对象代表了这个类在JVM中的一个引用。
  2. 链接:链接阶段可以进一步细分为三个子阶段:
  3. 验证(Verification):确保加载的类信息符合JVM规范,没有安全问题。
  4. 准备(Preparation):为类的静态变量(类变量)分配内存,并设置默认初始值。
  5. 解析(Resolution):将类、接口、字段和方法的符号引用转换为直接引用。这个过程涉及到常量池的解析,将常量池中的符号引用替换为指向内存中的直接引用。
  6. 初始化:
  7. 在这个阶段,JVM会执行类的构造器方法<clinit>(),这通常包含了类变量的赋值操作。对于Java代码来说,就是执行类中的静态初始化块和静态变量的赋值操作
  8. 需要注意的是,只有在真正需要使用到类时,JVM才会对类进行初始化(可以实现后面我们所说的 Lazy 加载),即执行<clinit>()方法。

这三个步骤是类加载机制的核心,它们确保了类的正确加载和初始化。在实际的类加载过程中,JVM会遵循双亲委派模型来确定哪个类加载器负责加载特定的类。此外,类的加载和链接过程是被动的,只有在首次主动使用类时才会开始,而初始化则是主动的,由JVM在确定类被使用时触发

  1. 类的加载过程是原子操作:当一个类被加载时,其他线程不能同时加载同一个类。JVM 会保证一个类在被加载时不会被其他线程加载,这避免了多个线程同时加载同一个类的问题。
  2. 类的链接和初始化是同步的:尽管类加载器可以并发工作,但是类的链接和初始化阶段是同步的。这意味着,一旦类被加载,链接和初始化过程会同步执行,确保类的状态在被使用前是正确的。
  3. 类的初始化是线程安全的:类的静态初始化器 <clinit>() 方法在类被初始化时执行,JVM 确保这个初始化过程是线程安全的,即在类的 <clinit>() 方法执行期间,其他线程不能进入这个类。

4.2.2 两种常见的实现方案

  • 在多线程的情况下,保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 私有构造方法,使用 getInstance() 静态方法获取实例而非 new 对象,建议设为 final
  • 要求第一次调用 getInstance() 静态方法才进行类的初始化,而非类加载就初始化。

双重校验锁:

public class Singleton {
	private volatile static Singleton singleton;
  	private Singleton() {}
  	public final static Singleton getInstance() {
		if (singleton == null) {
		  	synchronized (Singleton.class) {
				if (singleton == null) {
					singleton = new Singleton();
				}
			}
		}
	  	return singleton;
	}
}

静态内部类:

  • 创建一个内部类 SingletonHolder ,里面装单例
  • 内部类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder类,从而实例化 instance。实现Singleton类延迟加载。
public class Singleton {
	private static class SingletonHolder {
		private static final Singleton INSTANCE = new Singleton();
	}
	private Singleton() {}
  	public final static Singleton getInstance() {
	  	return SingletonHolder.INSTANCE;
	}
}

4.3 策略模式

将不同的策略抽象成一个 XXXXStrategy 接口,放进上下文类中。

interface Strategy {
  	void operation();
}

class Context {
  	private Strategy strategy;
  	public Context(Strategy strategy) {
	  	this.strategy = strategy;
	}
  	public void opration {
	  	strategy.operation();
	}
}

可以优化 if-else。

4.4 观察者模式

在 Subject 中使用 List 实现观察者接口(有response方法)列表,然后 Subject 可以遍历列表调用每个观察者 response 方法

// 抽象观察者接口
interface Observer {
    void response(); // 观察者的响应方法
}

// 抽象目标角色
abstract class Subject {
    protected List<Observer> observers = new ArrayList<>(); // 观察者列表

    // 添加观察者
    public void add(Observer observer) {
        observers.add(observer);
    }

    // 移除观察者
    public void remove(Observer observer) {
        observers.remove(observer);
    }

    // 通知所有观察者
    public abstract void notifyObservers();
}

// 具体目标角色
class ConcreteSubject extends Subject {
    @Override
    public void notifyObservers() {
        // 遍历观察者列表,调用每个观察者的响应方法
        for (Observer observer : observers) {
            observer.response();
        }
    }
}

5. 并发

5.1 Redis 分布式乐观锁

乐观锁:乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。

面试官:文档协作场景下,如何在分布式环境下实现乐观锁?

啊啊啊啊之前把乐观锁理解错了,理解成乐观锁自旋悲观锁阻塞了,结果还是“悲观锁”方案。

乐观锁的核心是,先修改,再判断能不能成功修改!

5.1.1 版本号机制

  • 核心思想:为每个文档维护一个版本号,每次更新文档时检查版本号是否匹配。
  • 实现步骤:每个文档有一个唯一的版本号(如 version 字段)。用户编辑文档时,先获取当前版本号。用户提交修改时,附带获取的版本号。服务器检查提交的版本号是否与当前版本号一致:如果一致,更新文档并递增版本号。如果不一致,拒绝更新并通知用户冲突。
  • 优点:简单易实现,适用于大多数文档协作场景。
  • 缺点:需要维护版本号,冲突时需要用户手动解决。

5.1.2 基于时间戳的乐观锁

  • 核心思想:使用时间戳作为版本控制的依据,确保更新的顺序性。
  • 实现步骤:每个文档保存一个最后更新时间戳(如 last_updated 字段)。用户编辑文档时,获取当前时间戳。用户提交修改时,附带获取的时间戳。服务器检查提交的时间戳是否与当前时间戳一致:如果一致,更新文档并更新时间戳。如果不一致,拒绝更新并通知用户冲突。
  • 优点:无需额外维护版本号,时间戳天然具有顺序性。
  • 缺点:时钟同步问题可能导致冲突检测不准确。

5.2 volatile 内存屏障

volatile 是一种类型修饰符,通常用于变量声明。它的主要作用是告诉编译器,这个变量的值可能会在程序的控制之外被修改(例如,硬件设备、其他线程等),因此编译器不应该对这个变量进行优化(如缓存到寄存器中)。

Java volatile 的的读写操作隐式包含了内存屏障,它的核心实现在 CPU 层面,依赖于 CPU 的指令集。

内存屏障的作用:

  • 顺序性:内存屏障可以防止编译器和处理器对指令进行重排序。它确保在屏障之前的操作在屏障之后的操作之前完成。
  • 可见性:内存屏障可以确保在屏障之前的所有内存操作对其他线程可见。

内存屏障的类型:

  • Load Barrier:确保在屏障之前的读操作在屏障之后的读操作之前完成。
  • Store Barrier:确保在屏障之前的写操作在屏障之后的写操作之前完成。
  • Full Barrier:确保在屏障之前的所有内存操作(读和写)在屏障之后的所有内存操作之前完成。

6. Java 集合

6.1 ConcurrentHashMap

JDK 1.7 之前:将哈希表分成多个 ​段(Segment)​,每个段是一个独立的哈希表。每个段都有自己的锁(由 ReentrantLock 继承),不同段的操作可以并发执行。

JDK 1.8 之后:使用 ​CAS 操作 和 ​synchronized 关键字 对每个桶实现细粒度的锁。

我在看并发集合的时候收到了美团的 HR 电话,所以留着以后再背啦... 祝大家好运!!

全部评论
接好运
点赞 回复 分享
发布于 03-30 15:40 山东

相关推荐

评论
4
33
分享

创作者周榜

更多
牛客网
牛客企业服务