meowrain

MeowRain

Life is Simple

ThreadLocal作用

ThreadLocal作用 线程隔离: ThreadLocal为每个线程提供了独立的变量副本,意味着线程之间不会相互影响,可以安全地在多线程环境中使用这些变量,不需要担心数据竞争或者同步问题。 降低耦合度: 在同一个线程内的多个函数或者组件之间,使用ThreadLocal可以减少参数的传递,降低代码之间的耦合度,使得代码更加清晰和模块化。 性能优势: 由于ThreadLocal避免了线程之间的同步开销,所以在大量线程并发执行的时候,相比传统的锁机制,可以提供更好的性能。

ThreadLocal介绍

ThreadLocal介绍 ThreadLocal是JAVA中用于解决线程安全问题的一种机制,它允许创建线程局部变量,即每个线程都有自己独立的变量副本,从而避免了线程间的资源共享和同步问题。 从内存结构图,我们可以看到 Thread类中,有个ThreadLocal.ThreadLocalMap的成员变量。 ThreadLocalMap内部维护了Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型对象值。

AQS

讲讲AQS 简单说AQS就是起到了一个抽象,封装的作用,将一些排队,入队,加锁,中断等方法提供出来,便于其它相关JUC锁的使用,具体加锁时机,入队时机等都需要实现类自己控制。 英文全称 是AbstractQueuedSynchronizer,AQS的核心是一个FIFO的双向队列,队列中的每个节点都代表一个线程。AQS提供了获取锁和释放锁的基本框架,具体的锁实现(如ReentrantLock、CountDownLatch等)需要继承AQS并实现其抽象方法。 它主要通过维护一个共享状态(state)和一个先进先出(FIFO) 的等待队列,来管理线程对共享资源的访问。 其中 state用volatile修饰,表示当前资源的状态,通常是一个整数值。AQS通过CAS操作来更新这个状态,以确保线程安全。 当线程尝试获取资源失败的时候,会被加入到AQS的等待队列中,这个队列是一个变体的CLH队列,采用双向链表结构,节点包含线程的引用,等待状态以及前驱和后继节点的指针。 AQS的常见实现类包括 ReentrantLock、CountDownLatch、Semaphore等,这些类都继承自AQS,并实现了其抽象方法。 AQS的核心机制 状态 AQS通过一个volatile类型的证书state表示同步状态。 子类可以通过 getState()、setState(int newState)、compareAndSetState(int expect, int update)等方法来获取和修改这个状态。\ 状态可以表示多种含义,例如在ReentrantLock中,state表示锁的重入次数;在CountDownLatch中,state表示计数器的值。 等待队列 AQS维护了一个FIFO的等待队列,用于管理等待获取同步状态的线程, 每个节点都是一个Node对象,代表一个等待的线程,节点之间通过next和prev指针连接。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 /** CLH Nodes */ abstract static class Node { volatile Node prev; // initially attached via casTail volatile Node next; // visibly nonnull when signallable Thread waiter; // visibly nonnull when enqueued volatile int status; // written by owner, atomic bit ops by others // methods for atomic operations final boolean casPrev(Node c, Node v) { // for cleanQueue return U.

HashMap原理

说说HashMap的原理 HashMap是基于哈希表的数据结构,用于存储键值对。 核心是将键的哈希值映射到数组索引位置,通过数组+链表+红黑树来解决哈希冲突。 HashMap使用键的hashCode()方法计算哈希值,通过(n-1) &hash确定元素在数组中的存储位置。 哈希值是经过一定的扰动处理的,防止哈希值分布不均,从而减少冲突, HashMap的默认初始容量为16,负载因子为0.75,也就是说,当存储的元素数量超过16 * 0.75 = 12个的时候,HashMap会触发扩容操作,容量x2并重新分配元素位置,这种扩容是比较耗时的操作,频繁扩容会影响性能。 通过源码深入了解HashMap 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // 默认初始容量 - 必须是 2 的幂次方。 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 即 16 // 最大容量,如果构造函数中通过参数隐式指定了更高的值,则使用此最大容量。 // 必须是小于等于 1 << 30 的 2 的幂次方。 // 由于你可以随时指定非常大(甚至超过了1亿)的值,为了防止内存溢出或数组长度无效,HashMap内部通过MAXIMUM_CAPACITY做了一个“保险”,来确保容量不会超过某个安全极限。 static final int MAXIMUM_CAPACITY = 1 << 30; // 构造函数中未指定时使用的负载因子。 static final float DEFAULT_LOAD_FACTOR = 0.

JUC笔记

JUC 进程 概述 进程:程序是静止的,进程实体的运行过程就是进程,是系统进行资源分配的基本单位 进程的特征:并发性、异步性、动态性、独立性、结构性 线程:线程是属于进程的,是一个基本的 CPU 执行单元,是程序执行流的最小单元。线程是进程中的一个实体,是系统独立调度的基本单位,线程本身不拥有系统资源,只拥有一点在运行中必不可少的资源,与同属一个进程的其他线程共享进程所拥有的全部资源 关系:一个进程可以包含多个线程,这就是多线程,比如看视频是进程,图画、声音、广告等就是多个线程 线程的作用:使多道程序更好的并发执行,提高资源利用率和系统吞吐量,增强操作系统的并发性能 并发并行: 并行:在同一时刻,有多个指令在多个 CPU 上同时执行 并发:在同一时刻,有多个指令在单个 CPU 上交替执行 同步异步: 需要等待结果返回,才能继续运行就是同步 不需要等待结果返回,就能继续运行就是异步 参考视频:https://www.bilibili.com/video/BV16J411h7Rd 笔记的整体结构依据视频编写,并随着学习的深入补充了很多知识 对比 线程进程对比: 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集 进程拥有共享的资源,如内存空间等,供其内部的线程共享 进程间通信较为复杂 同一台计算机的进程通信称为 IPC(Inter-process communication) 信号量:信号量是一个计数器,用于多进程对共享数据的访问,解决同步相关的问题并避免竞争条件 共享存储:多个进程可以访问同一块内存空间,需要使用信号量用来同步对共享存储的访问 管道通信:管道是用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件 pipe 文件,该文件同一时间只允许一个进程访问,所以只支持半双工通信 匿名管道(Pipes):用于具有亲缘关系的父子进程间或者兄弟进程之间的通信 命名管道(Names Pipes):以磁盘文件的方式存在,可以实现本机任意两个进程通信,遵循 FIFO 消息队列:内核中存储消息的链表,由消息队列标识符标识,能在不同进程之间提供全双工通信,对比管道: 匿名管道存在于内存中的文件;命名管道存在于实际的磁盘介质或者文件系统;消息队列存放在内核中,只有在内核重启(操作系统重启)或者显示地删除一个消息队列时,该消息队列才被真正删除 读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP 套接字:与其它通信机制不同的是,可用于不同机器间的互相通信 线程通信相对简单,因为线程之间共享进程内的内存,一个例子是多个线程可以访问同一个共享变量 Java 中的通信机制:volatile、等待/通知机制、join 方式、InheritableThreadLocal、MappedByteBuffer 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低 线程 创建线程 Thread Thread 创建线程方式:创建线程类,匿名内部类方式 start() 方法底层其实是给 CPU 注册当前线程,并且触发 run() 方法执行 线程的启动必须调用 start() 方法,如果线程直接调用 run() 方法,相当于变成了普通类的执行,此时主线程将只有执行该线程 建议线程先创建子线程,主线程的任务放在之后,否则主线程(main)永远是先执行完 Thread 构造器:

Mysql最左匹配原则

https://blog.csdn.net/sinat_41917109/article/details/88944290?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.channel_param 什么是最左匹配原则 最左列必须出现在查询条件中 可以选择性地查询后续列,但必须按顺序 为什么前缀能利用索引,但是后缀和中缀不能使用索引? 这是因为索引的工作方式决定了它如何与不同类型的 LIKE 查询进行配合。MySQL 的 B+ 树索引(常用于字符类型的列)按照字典顺序存储数据并进行查找。让我们一步一步深入理解为什么前缀匹配能利用索引,而后缀和中缀匹配则不能。 1. 前缀匹配可以利用索引: 假设我们有如下索引: 1 CREATE INDEX idx_location ON School(location); B+ 树索引的结构是有序的,它可以高效地根据 前缀 查找数据。当你使用 LIKE 'C%' 这样的查询时,MySQL 会直接利用索引扫描,以字典顺序从 C 开始查找所有以 C 开头的字符串,直到找到不以 C 开头的字符串。B+ 树非常适合这种范围查询。 具体来说: LIKE 'C%' 会查询所有以 C 开头的字符串。 B+ 树索引能高效地查找所有符合条件的值,并快速定位到起始位置,继而返回符合条件的记录。 索引利用: 查询时,MySQL 只需要根据 location 字段的索引定位到 C,然后顺着索引查找匹配的记录。这个过程是线性的,但由于数据有序,所以查找速度非常快。 2. 后缀匹配和中缀匹配无法使用索引: 后缀匹配: 假设查询是 LIKE '%ia',这个查询需要查找以 ia 结尾的所有字符串。由于 % 出现在字符串的开头,MySQL 无法通过索引的有序特性来帮助定位。具体原因是: B+ 树索引是按照顺序排列的,查询条件是 LIKE '%ia',意味着我们不知道字符串的开始部分是什么,所以 MySQL 需要从每一个字符串的末尾进行匹配。 索引不能直接“倒着”查找,它只能按照从左到右的顺序进行查找,因此必须遍历整个表来匹配 '%ia'。 由于没有固定的开始部分,MySQL 就无法通过索引来高效过滤数据,只能进行 全表扫描。 中缀匹配: 类似于后缀匹配,查询 LIKE '%li%' 查找包含 li 的所有字符串。这个查询也是无法利用索引的,因为:

Big库处理大数字

在日常的开发过程中,不可避免的需要使用到超过变量类型的数值计算,在 go 语言中,int64 类型的变量的储存范围是 -9223372036854775808 ~ 9223372036854775807,当我们需要计算的数值大于这个范围之后,计算出的结果就会出错,这时候就需要使用到 go 语言中专门为大数计算而存在的标准库:math/big 包里面的内容。 比如 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package main import ( "fmt" "math" ) func main() { a := math.MaxInt64 b := math.MaxInt64 c := a + b fmt.Printf("%d + %d = %d", a, b, c) } 可以看出两个最大值的相加结果异常,这是因为两个最大值相加的结果超出了 int64 能够存储的范围。 math/big 如果需要进行上面这样的大数计算,可以使用 go 语言自带的 math/big 包进行计算,big 包中包含了整形 int 和浮点型 float 的大数计算。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package main import ( "fmt" "math" "math/big" ) func main() { a := big.

B+树Go实现

B+树动画演示 https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 package main /* B+树动画演示 https://www.

Golang并发原语sync.Pool

Golang 基本同步原语 sync.Pool sync.Pool 是 Go 标准库 sync 包中的一个结构,用于管理一组可重用的对象池。通过复用对象,减少内存分配和垃圾回收的开销,从而提升程序的性能和效率。以下是对 sync.Pool 的详细讲解,包括其使用案例和使用场景。 sync.Pool 的大小是可伸缩的,高负载时会动态扩容,存放在池中的对象如果不活跃了会被自动清理。 1. sync.Pool 的功能和作用 sync.Pool 主要用于在多个 goroutine 之间高效地共享和重用临时对象。它通过保持一个池子,存储已经分配但当前不使用的对象。当需要新的对象时,首先从池子中获取,如果池子中没有可用的对象,则创建新的对象。使用完成后,将对象归还池子,以便后续复用。 sync.Pool 的主要优势在于减少内存分配和垃圾回收的开销,尤其在需要频繁创建和释放临时对象的场景下,能够显著提升程序的性能。 2.sync.Pool 核心方法 sync.Pool 提供了以下几个核心方法: New() 1 2 3 4 5 6 pool := &sync.Pool{ New: func() interface{} { // 返回一个新的对象 return new(Object) }, } 作用:初始化一个新的 sync.Pool 实例,New 是一个工厂函数,用于创建池子中对象的工厂方法。 场景:当池子中没有可用对象时,会调用 New 方法创建新的对象。 Get() 1 obj := pool.Get() 作用:从池子中获取一个可用的对象。如果池子中有对象,直接返回;如果没有,调用 New 方法创建新的对象。 返回值:返回一个 interface{} 类型的对象,需要进行类型断言。 Put() 1 pool.Put(obj) 作用:将一个对象归还到池子中,供后续复用。 注意:归还的对象应该处于初始状态,避免残留数据导致潜在的数据竞争或不一致问题。 Close() 1 pool.

Golang并发原语sync.Once

Golang 基本同步原语 sync.Onece sync.Once 是 Go 语言中一个简单但强大的同步原语,用于确保某个操作在并发场景下只执行一次。它常用于延迟初始化、单例模式、全局配置加载等场景 基本用法 sync.Once 的核心方法是 Do(f func()),传入的函数 f 只会执行一次,即使多个 goroutine 同时调用 Do。 内部实现原理 sync.Once 的源码非常简洁,其核心是通过一个 uint32 类型的原子标志位(done)和一个互斥锁(m)实现的: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package sync import ( "sync/atomic" ) type Once struct { done uint32 m Mutex } func (o *Once) Do(f func()) { if atomic.LoadUint32(&o.done) == 0 { o.
0%