Java中的多线程
Java中的多线程 一、线程的创建方式 JUC中为Java多线程提供了很多工具,其中创建线程有四种方式: 继承Thread类并重写run()方法: 开发者可以自定义一个类继承Thread类,并重写run()方法。然后创建该类的实例,并调用start()方法执行。 class MyThread extends Thread { @Override public void run() { System.out.println(“Thread running”); } } MyThread t = new MyThread(); t.start(); // 启动线程 实现Runnable接口并传给Thread的构造函数: 一个类不能继承多个类,但是可以实现多个接口。因此可以将目标类实现Runnable接口,并将其传给Thread构造器(也可以直接传lambda表达式给构造器)。 Runnable task = () -> System.out.println(“Runnable running”); Thread t = new Thread(task); t.start();...
JVM的垃圾回收
JVM的垃圾回收 一、JVM的垃圾回收算法 垃圾回收机制是JVM的一个重要机制,它可以自动帮助开发者清除已弃用的对象,达到内存回收的目的。而不同的内存需要采用不同的垃圾回收算法,确保兼顾效率和回收率。 常见的垃圾回收算法有以下四种: 标记-清除算法 : 标记阶段:从一组GC Roots对象出发,遍历所有的可达对象,并标记为“存活”。 清除阶段:遍历堆内存,将未标记为存活的对象清除,从而释放内存。 复制算法 : 将内存分为From和To两块区域,对象创建在From区域,触发垃圾回收机制时,JVM会将From区域的所有存活对象复制到To区域,并清除From区域的所有空间,最后再调换From和To区域。从而达到内存回收的目的。 标记-整理算法 : 标记阶段:与标记-清除算法相同。 整理阶段:将存活的对象移到内存的一端,并清除剩余的空间。 分代GC算法 : 将对象分为新生代 和老年代 新生代:分为Eden区 和Survivor区 (Survivor0和Survivor1)。新对象创建一般在Eden区,当Eden区内存不足时,会触发Minor GC ,在Survivo...
浅谈JVM(1)
浅谈JVM(1) 一、JVM的内存模型 JDK 1.8 中,JVM的内存模型主要分为五个部分:堆、原空间、虚拟机栈、本地方法栈和程序计数器 。 这几个部分可以分成线程共享 和线程私有 两类,线程共享的内存可以供所有线程共同使用且访问;线程私有的内存则会随着线程的创建而分配,随着线程的销毁而回收。 线程共享 堆 (Heap): 堆是JVM中最大的一块内存,在JVM启动的时候创建,专门用来存放数组 和对象实例 。堆中还包含了字符串常量池 ,用于存储字符串字面量和常量。 从内存回收的角度,堆中又分为新生代(占1/3)和 老年代(占2/3),新生代又分为1个Eden区 和2个Survivor区 (From & To)。 当堆中没有内存分配给对象实例 ,并且堆再也无法拓展内存 的时候,就会出现OOM异常 (OutOfMemoryError)。 元空间 (MetaSpace): 元空间用来存储类元信息 、常量 、方法字节码 等。元空间包含运行时常量池 ,用于存储编译时期生成的各种字面量和符号引用。 在JDK 1.7及之前,元空间被称为方法区 ,并且它的内存分...
接口限流Part 3:令牌桶算法
接口限流Part 3:令牌桶算法 本文基于开源项目api-rate-limiter编写 一、令牌桶算法原理 令牌桶算法是限流算法中最灵活的一种,它的核心思想是: 系统以固定速率生成令牌放入桶中,每次请求需要消耗令牌才能通过,桶满时不再添加令牌。 工作原理 令牌生成 :系统按照固定速率(如每秒10个)向桶中添加令牌 容量限制 :桶有最大容量,超过容量的令牌会被丢弃 令牌消费 :每次请求消耗一个或多个令牌 流量控制 :桶空时拒绝请求,桶满时允许突发流量 核心优势 突发流量支持 :桶满时可以处理短时间的大量请求 平均速率控制 :长期来看,请求速率不会超过令牌生成速率 灵活性高 :可以根据业务需求调整令牌生成速率和桶容量 实际场景举例 假设设置:桶容量100个,生成速率10个/秒 正常情况:每秒最多处理10个请求 突发情况:可以瞬间处理100个请求,然后需要10秒恢复 长期效果:平均每秒不超过10个请求 二、Redis + Lua 实现方案 令牌桶算法需要维护每个用户的桶状态,包括当前令牌数和最后更新时间。这里采用Redis Hash + Lua脚本 的方案: Hash...
接口限流Part 2:滑动窗口
接口限流Part 2:滑动窗口 在上一篇文章中,我们了解了固定窗口限流的基本原理和实现方式。虽然固定窗口实现简单,但在实际应用中存在一个致命问题:边界突刺 。 一、固定窗口的边界突刺问题 固定窗口算法虽然简单有效,但在窗口边界处存在明显的流量突刺问题: 时间轴:|----窗口1----|----窗口2----| 请求: 10:00:59 10:01:00 结果: 第20次 第1次(新窗口) 问题: 在边界时刻可能出现2倍阈值的突发流量 具体场景 : 设置限制:每分钟20次请求 用户在10:00:59发送20次请求(窗口1的最后时刻) 用户在10:01:00又发送20次请求(窗口2的开始时刻) 结果:1秒内实际处理了40次请求,远超预期限制 这就是为什么我们需要滑动窗口 算法来解决这个问题。 二、滑动窗窗口实现原理 滑动窗口算法的核心思想是:统计任意时刻向前推N秒内的请求总数,而不是固定时间块的请求数 。 工作原理 滑动窗口通过以下方式实现平滑限流: 动态时间窗口 :每次请求时,都计算”当前时间向前推N秒”这个时间窗口内的请求总数 实时清理 ...
接口限流Part 1:固定窗口
接口限流Part 1:固定窗口 在微服务架构中,接口限流是保障系统稳定性的重要手段。当某个接口突然收到大量请求时,如果没有限流保护,可能导致数据库连接池耗尽、内存溢出,甚至整个服务崩溃。 **接口限流(Rate Limiting)**的核心目标很明确:控制每个用户或IP的请求频率,防止恶意攻击和异常流量对系统造成冲击。 一、固定窗口限流原理 固定窗口算法的核心思想很简单:将时间划分为固定长度的窗口,在每个窗口内统计请求次数,超过阈值就拒绝服务 。 工作原理 时间窗口:比如设置1分钟为一个窗口 计数规则:每个窗口内,同一IP对同一接口的请求次数不能超过设定值 重置机制:新窗口开始时,计数器清零重新计数 具体示例 假设限制为每分钟10次请求: 10:00-10:01窗口:用户请求8次 → 允许 10:01-10:02窗口:用户请求5次 → 允许(新窗口重新计数) 10:00-10:01窗口:用户请求12次 → 第11次开始返回429错误 技术实现 : 使用Redis存储计数器 Key格式:rate_limit:IP:接口路径 通过EXPIRE实现窗口重置 二、Redi...
谈谈分布式ID生成器
谈谈分布式ID生成器 在后端开发中,唯一 ID 是一个基础而关键的需求。无论是用户注册时生成的用户 ID、下单时的订单号,还是日志系统中的 TraceId,唯一 ID 都承担着 数据唯一标识 的核心作用。 在单体应用时代,很多系统直接依赖数据库的自增主键来生成 ID,这种方式简单易用,能够满足小规模系统的需求。但随着系统规模不断扩大,传统自增ID逐渐暴露出数据泄露风险、分库分表困难、性能瓶颈等严重问题,促使我们寻找更适合分布式环境的ID生成方案。 常见分布式唯一 ID 方案 一、自增ID 利用数据库的 AUTO_INCREMENT 实现,这是最直观的ID生成方式,也是初学者的首选方案。 CREATE TABLE user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL ); 优点 :实现简单,性能良好,ID具有递增特性,适合作为数据库主键。 缺点 : 安全风险 :自增ID具有明显的规律性,容易被恶意用户通过枚举方式获取敏感数据,存在严重的数据泄露风险 分库分表困难 :在分布...
HashSet底层初探
HashSet底层初探 HashSet是Java集合框架中一个重要的实现类,它基于HashMap实现,具有不允许重复元素 、允许null值 、无序存储 等特性。作为最常用的Set实现,HashSet在去重、快速查找等场景中发挥着重要作用。 核心数据结构 HashSet的内部实现采用了适配器模式 ,通过组合HashMap来实现Set接口的功能。 private transient HashMap<E,Object> map; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); 关键设计要点 : map属性 :HashSet内部维护一个HashMap实例,所有Set操作都委托给这个HashMap处理 PRESENT常量 :作为HashMap的value值,由于Set只需要存储key,所以使用一个固定的Object对象作为占位符 transient关键字 :map字段被标记为transient,...
图灵二面:手撕HashMap
图灵二面:手撕HashMap 面试官:既然你刚刚提到HashMap底层原理,想必你一定了解HashMap的结构。那你就手写一份自己的HashMap吧。 我:??? 先来看看我当时写的依托答辩: /** * @author Aromatic * @date 2025/6/18 下午3:21 * @description: */ public class MyHashMap { private int size; private List<List<Integer>>[] table; public void MyHashMap(){ this.size = 16; table = new List[size]; } public void MyHashMap(int size){ this.size = size; table = new List[size]; } pri...
ConcurrentHashMap是如何解决并发问题的?
ConcurrentHashMap是如何解决并发问题的? 本文部分参考:吊打Java面试官之ConcurrentHashMap(线程安全的哈希表) | 二哥的Java进阶之路 ConcurrentHashMap JDK 1.7 在JDK 1.7中,ConcurrenctHashMap采用了分段锁的机制,把整个HashMap分成了多个Segment,当某个桶需要加锁时,只会锁住对应的Segment,而不是整个Map。如图: 同时,在单个Segment的内部,也存有若干Entry(即内部有一个Entry数组): 基于以上特殊结构,不难发现: 操作 是否能并发执行 对不同Segment进行写操作 是 对同一Segment进行读写操作 是 对同一Segment进行写操作 否 get方法 伪代码如下: // ConcurrentHashMap 类的 get 方法 V get(Object key) { // 1. 如果 key 为 null,抛出 NullPointerException if (key == null) ...



