ThreadLocal :
- ThreadLocal 并不解决线程间共享数据的问题
- ThreadLocal 通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
- 每个线程持有一个 Map(ThreadLocalMap)并维护了 ThreadLocal 对象与具体实例的映射,该Map 由于只被持有它的线程访问,故不存在线程安全以及锁的问题。
- ThreadLocalMap 的 Entry 对ThreadLocal 的引用为弱引用,避免了ThreadLocal 对象无法被回收的问题
- ThreadLocalMap 的 set方法通过调用 replaceStaleEntry 方法回收键为null 的 Entry 对象的值(即具体实例)以及Entry 对象本身,从而防止内存泄漏
- ThreadLocal 使用于变量在线程间隔离且在方法间共享的场景
ReentrantLock分为公平锁和非公平锁,哪底层是如何实现的?
首先底层实现都是使用AQS来进行排队,区别在于使用lock()方法加锁时:
- 公平锁:会先检查AQS队列中是否存在线程排队,如果线程排队,则当前线程也排队
- 非公平锁:则不会检查是否有线程排队,而是直接竞争锁
一旦没竞争到锁,都会进行排队
Sychronized的锁升级过程
- 1.偏向锁:在锁对象的对象头记录下当前获取到该锁的线程id,下次可直接获取
- 2.轻量级锁:由偏向锁升级而来,当第二个线程来竞争锁,偏向锁会升级为轻量级锁,底层通过自旋实现,不会阻塞线程
- 3.自旋次数过多仍然没获取到锁,则会升级为重量级锁,会导致线程阻塞
- 4.自旋锁:就是线程在获取锁的过程中,是线程通过CAS获取预期的一个标记,
线程安全的理解:
说一下hashmap的put方法:
hashmap的put方法的大体流程:
- 1.根据key 通过哈希算法与运算得到下标
- 2.如果数组下标的位置元素为空,则将key和value封装为entry对象(jdk1.8为node对象)并放入该位置
- 3.如果数组下标位置元素不为空,则要分情况讨
- 如果是jdk1.7,则先判断是否需要扩容,如果要扩容就进行扩容,不用扩容就生成entry对象,并使用头插法添加到当前位置的链表中
- 如果是jdk1.8,则先判断当前位置上的node类型,看是红黑树node,还是链表node
- 1.如果是红黑树node,则将key和value封装为一个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value
- 2.如果此位置上的node对象是链表节点,则将key和value封装为一个链表node并通过尾插法插入到链表的最后位置去,因为是尾插法,需遍历链表,在遍历链表判断是否存在当前key,如果存在更新value,遍历完后,将新node插入链表中,插入后,判断链表节点个数,如果大于等于8,则会将链表转化为红黑树
- 3.将key和value封装为node插入链表或红黑树中后,再判断是否需要进行扩容,如果需要就扩容,如果不需要就结束put方法
1.线程池
线程池是一组维护线程的池子,可以在需要时重复使用线程,而不是为每个任务创建新线程。它的目的是提高多线程应用程序的性能、可管理性和可扩展性。使用线程池可以减少线程的创建和销毁开销,避免资源浪费
线程池的7大核心参数:
- 核心线程数:线程池中能够同时执行的线程数量
- 最大线程数:线程池中允许的最大线程数量
1、当线程池中线程数量小于 corePoolSize 则创建线程,并处理请求。
2、当线程池中线程数量大于等于 corePoolSize 时,则把请求放入 workQueue 中,随着线程池中的
核心线程们不断执行任务,只要线程池中有空闲的核心线程,线程池就从 workQueue 中取任务并
处理。
3 、当 workQueue 已存满,放不下新任务时则新建非核心线程入池,并处理请求直到线程数目达
到 maximumPoolSize(最大线程数量设置值)。
4、如果线程池中线程数大于 maximumPoolSize 则使用 RejectedExecutionHandler 来进行任务
拒绝处理。
- 线程空闲时间:
- 时间单位(秒、毫秒)
- 任务队列
- LinkedBlockingQueue基于链表结构的可选有界阻塞队列,按照先进先出的顺序进行任务调度。如果不指定容量大小,则默认为 Integer.MAX_VALUE。
- PriorityBlockingQueue基于优先级的无界阻塞队列可以按照自定义的顺序执行任务
- SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作。在这种队列中,每个插入操作都会阻塞,直到有其他线程来获取数据。
- ArrayBlockingQueue:基于数组结构的有界阻塞队列,按照先进先出(FIFO)的顺序进行任务调度。
- 线程工厂
- 拒绝策略
- AbortPolicy(默认):抛出RejectedExecutionException异常,表示拒绝执行任务
- CallerRunsPolicy:由调用线程执行被拒绝的任务
- DiscardPolicy:静默丢弃被拒绝的任务
- DiscardOldestPolicy:丢弃队列中等待时间最长的任务,然后重新尝试执行新任务
2.mysql索引的底层数据结构原理
聚集索引(主键索引),次要索引,覆盖索引,复合索引,前缀索引,唯一索引在MySQL5.7和 8.0版本默认都是使用B+Tree索引,除此之外还有 Hash索引。至于MySQL5.7之前版本,这里就不过多探究##
Hash哈希,只适合等值查询,不适合范围查询。一般二叉树,可能会特殊化为一个链表,相当于全表扫描。红黑树,是一种特化的平衡二叉树,MySQL 数据量很大的时候,索引的体积也会很大,内存放不下的而从磁盘读取,树的层次太高的话,读取磁盘的次数就多了。B-Tree,叶子节点和非叶子节点都保存数据,相同的数据量,B+树更矮壮,也是就说,相同的数据量,B+树数据结构,查询磁盘的次数会更少。
B-树和B+树的区别
B-树内部节点是保存数据的;而B+树内部节点是不保存数据的,只作索引作用,它的叶子节点才保存数据。B+树相邻的叶子节点之间是通过链表指针连起来的,B-树却不是。查找过程中,B-树在找到具体的数值以后就结束,而B+树则需要通过索引找到叶子结点中的数据才结束B-树中任何一个关键字出现且只出现在一个结点中,而B+树可以出现多次。
B+ 树其实和 B 树是非常相似的,我们首先看看相同点。
- 根节点至少一个元素
- 非根节点元素范围:m/2 <= k <= m-1
不同点。
- B+ 树有两种类型的节点:内部结点(也称索引结点)和叶子结点。内部节点就是非叶子节点,内部节点不存储数据,只存储索引,数据都存储在叶子节点。
- 内部结点中的key都按照从小到大的顺序排列,对于内部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的记录也按照key的大小排列。
- 每个叶子结点都存有相邻叶子结点的指针,叶子结点本身依关键字的大小自小而大顺序链接。
- 父节点存有右孩子的第一个元素的索引。
B+ 树相对于 B 树有一些自己的优势,可以归结为下面几点。
- 单一节点存储的元素更多,使得查询的 IO 次数更少,所以也就使得它更适合做为数据库 MySQL 的底层数据结构了。
- 所有的查询都要查找到叶子节点,查询性能是稳定的,而B树,每个节点都可以查找到数据,所以不稳定。
- 所有的叶子节点形成了一个有序链表,更加便于查找。
B+Tree
(1). 特点
B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。
A. 非叶子节点不存储data,只存储索引,可以存放更多索引。
B. 叶子节点不存储指针。
C. 顺序访问指针,提高区间访问性能。
D. 非叶子节点中的索引最终还是会在叶子节点上存储一份,也就是叶子节点会包含非叶子节点上的所有索引。
E. 一个父节点,它的左侧子节点都小于父节点的值,右侧的子节点都大于等于父节点的值。
F. 每一层节点从左往右都是递增排列,无论是数值型还是字符型。
注:MySQL索引默认的存储结构使用的就是B+Tree。
一张B+Tree的表最多存放 1170 * 1170 * 16 = 21902400 ≈ 2千万。所以,通过分析,我们可以得出,B+Tree结构的表可以容纳千万数据量的查询。而且一般来说,MySQL会把 B+Tree 根节点放在内存中,那只需要两次磁盘IO(第二层1次,第三层1次)就行
3.spring的事务隔离级别:
1.READ_UNCOMMITTED(读未提交)
Spring事务最弱的隔离级别。一个事务可以读取到另一个事务未提交的事务记录。容易出现脏读、不可重复读、幻读的问题。
3 READ_COMMITTED(读已提交)oracle默认
一个事务只能读取到已经提交的记录,不能读取未提交的记录。可以解决脏读问题,但仍出现不可重复读、幻读的问题。
4 REPEATBLE_READ(可重复读)msql默认
一个事务可以多次从数据库读取某条记录,而且多次读取的那条记录都是一致的、相同的。可以避免脏读、不可重复读的问题,但仍可能出现幻读的问题。
5 SERIALIZABLE(可串行化)
Spring最强的隔离级别,一般不推荐使用。
脏读、不可重复读、幻读
- 脏读:读到了其他事务还没有提交的数据。
- 幻读:读取到的条数不同
- 不可重复读:读去到的内容结果不同
4.spring的7种事务传播机制
- 1. REQUIRED:如果当前存在事务,则加入该事务,如果不存在事务,则创建一个事务。
- 2. SUPPORTS:如果当前存在事务,则加入该事务,如果不存在事务,则以非事务的方式执行。
- 3. MANDATORY:如果当前存在事务,则加入到当前事务中;如果当前没有事务,则抛出异常。
- 4. REQUIRES_NEW:总是创建一个新的事务,如果当前存在事务,则挂起当前事务。
- 5. NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则挂起当前事务。
- 6.NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
- 7. NESTED:如果当前存在事务,则在当前事务中创建一个新的嵌套事务;如果当前没有事务,则创建一个新的事务。
5.innodb如何解决幻读:
- 幻读:指在同一个事务中,前后两次查询相同的范围时,得到的结果不一致
在RR的隔离级别下,Innodb使用MVCC和next-key locks解决幻读,MVCC解决的是普通读(快照读)的幻读,next-key locks解决的是当前读情况下的幻读。
在RC的模式下,MVCC解决不了幻读和不可重复读,因为每次读都会读它自己刷新的快照版本,简单来说就是另一个事务提交,他就刷新一次,去读最新的
InnoDB 引入了间隙锁和 next-key Lock 机制来解决幻读问题
间隙锁:锁定一段范围内的索引记录
next-key Lock 相当于间隙锁和记录锁的合集,记录锁锁定存在的记录行,
间隙锁锁住 记录行之间的间隙,而 next-key Lock 锁住的是两者之和
6.binlog和redlog的区别
binlog用于记录数据库执行的写入性操作(不包括查询)信息,以二进制的形式保存在磁盘中。binlog是mysql的逻辑日志,并且由Server层进行记录,使用任何存储引擎的mysql数据库都会记录binlog日志。
redo log包括两部分:一个是内存中的日志缓冲(redo log buffer),另一个是磁盘上的日志文件(redo log file)。mysql每执行一条DML语句,先将记录写入redo log buffer,后续某个时间点再一次性将多个操作记录写到redo log file。
7.mysql事务的实现原理
https://blog.csdn.net/qq_33472553/article/details/129311508
redoLog,保持继续执行恢复没有写入的功能日志,物理日志,innoDB独有。
undoLog,回滚操作,生成与执行相反的逻辑日志,且提前会有快照放在undoLog里面,有备份。
BinLog,二进制日志,mysql自身日志,主要是主从复制,数据恢复(使用mysqlbinlog)。
事务日志(Transaction Log): MySQL的事务日志是一个关键组成部分,它记录了所有对数据库进行修改的操作,包括事务的开始、提交、回滚等。通过事务日志,MySQL能够在发生故障时,通过重做(redo)和撤销(undo)日志来保证事务的原子性和一致性。
锁机制: 在并发环境下,为了维护事务的隔离性,MySQL使用了各种锁机制。行锁和表锁是两种基本的锁类型,它们用于控制对数据的访问。InnoDB引擎支持多粒度的锁,使得在不同的并发情境下能够更有效地管理锁。
多版本并发控制(MVCC): MVCC是通过在每行记录上保存数据的不同版本来实现的。每个事务在读取数据时,能够看到一个一致性的快照,而不受其他并发事务的影响。这有助于提高并发性能,减少读写冲突。
事务隔离级别: MySQL支持四种事务隔离级别,即读未提交、读已提交、可重复读和串行化。通过设置合适的隔离级别,可以在事务并发执行时平衡性能和一致性的要求。
原文链接:https://blog.csdn.net/Alaskan_Husky/article/details/134820793
jdk每个版本更新内容:
类加载过程及加载机制
类加载过程:加载-》 验证-〉 准备-》 解析-〉 初始化
8.JVM的内存模型
方法区:是各个线程共享的内存区域,在虚拟机启动时创建
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据
当方法无法满足内存分配需求时,将抛出oom异常
堆:java虚拟机所管理内存最大的一块,在虚拟机启动时被创建,,被所有线程共享
java对象实例及数组都在堆上分配
虚拟机栈:是一个线程执行的区域,保存着一个线程中方法的调用状态,
一个java线程的运行状态由一个虚拟机栈来保存,所以虚拟栈是线程私有的
每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。
调用一个方法,就会向栈中压入一个栈帧,一个方法调用完成,就会把该栈帧从栈中弹出
本地方法栈:
如果当前线程执行的方法是 Native类型的,这些方法就会在本地方法栈中执行
如果是java方法执行时,调用Native的方法哪?
- 栈指向堆:Object ob = new Object(),即是典型的栈中元素指向堆中的对象
- 方法区指向堆:private static Object ob = new Object() 方法区存储静态变量。常量等数据
- 堆指向方法区:怎么知道对象由那个类创建的哪?
pc程序计数器:
- 程序计数器:当前线程所执行的字节码的行号指示器
- 方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时便衣后的代码
- 本地方法栈:是为虚拟机调用native方法服务的
- 虚拟机栈:存储局部变量表、操作数栈、动态链接、方法出口等信息
- 堆:线程共享,几乎所有的对象实例都在这里分配内存
什么是内存溢出?与内存泄漏区别是什么?
内存溢出
是指程序运行过程中申请的内存大于系统能够提供的内存,导致无法申请到足够的内存
于是就发生了内存溢出 出现oom
- pergenspace:持久带溢出
- java heap space 堆溢出
- stackOverflowError 和OutOfMemoryError 虚拟机栈和本地方法栈溢出
内存泄漏
是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏危害可忽略,
但内存泄漏堆积后果很严重,无论多少内存迟早会被占光
内存泄漏最终会导致内存溢出
避免内存泄漏的几点意见:
- 1.尽早释放无用对象的引用
- 2.避免在循环中创建对象
- 3.使用字符串处理时避免使用string,应使用stringbuffer
- 4.尽量少使用静态变量,因为静态变量存放永久代,不参与垃圾回收
java垃圾回收机制:
jvm中有垃圾回收线程,低优先级,正常情况不会执行,只有虚拟机空闲或堆内存不足,才会触发执行,扫描没有被引用的对象,添加到回收的集合中,进行回收
Java 中都有哪些引用类型?
- 强引用:发生 gc 的时候不会被回收。
- 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
- 弱引用:有用但不是必须的对象,在下一次GC时会被回收。
- 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知
怎么判断对象是否可以被回收:
- 引用计数法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用 被释放时计数 -1,
当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用 的问题 - 可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。 当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的
jvm有哪些垃圾回收算法:
- 标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清 除垃圾碎片。
- 复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的 对象复制到另一块 上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不 高,只有原来的一半。
- 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清 除掉端边界以外的 内存。
- 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年 代,新生代基本采用 复制算法,老年代采用标记整理算法。
jvm有哪些垃圾回收器
- Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点 是简单高效;
- ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程 版本,在多核CPU环境下有着比Serial更好的表现;
- Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效 利用 CPU。吞吐量
= 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高 效率的利用CPU时间,尽快完成程
序的运算任务,适合后台应用等对交互相应要求不 高的场景; - Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年 代版本;
- Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先, Parallel Scavenge收集器的老年代版本;适用于后台服务(大数据)
- CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集 器,以获取最短回收
停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最 短GC回收停顿时间。
适用于大型互联网用户请求量大,频率高的场景,订单商品接口
- G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是 JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会 产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代 或老年代。
- zgc: 只能在64位linux上使用,目前用的比较少
- 可以达到10ms以内的停顿时间要求
- 支持tb级别的内存
- 堆内存变大后停顿时间还是在10ms以内
- 特征:着色指针、读屏障
在JDK8中,HashMap的数据结构在链表长度超过8的时候会进行链表转红黑树的操作。这里为什么是8而不是7或6呢,主要是为了在性能和内存占用之间进行权衡。
当链表长度达到8时,代表着哈希冲突的概率较高,如果继续使用链表的话会导致查找效率变低,这时候转换成红黑树可以保证查找的平均时间复杂度是O(log n),性能比链表高。
另一方面,当链表长度超过8时,转换成红黑树可以节省内存空间。因为红黑树作为一种自平衡二叉搜索树,它的节点是复杂的,转换成红黑树会增加一些额外的内存开销。因此,选择在链表长度达到8时转换成红黑树,可以在维持一定的性能的同时,尽量减少额外的内存开销。
综上所述,8作为链表长度的阈值,在性能和内存开销之间找到了一个比较合适的平衡点。
springboot的自动装配流程:
main方法中SpringApplication.run(HelloBoot.class,args)的执行流程中有refreshContext(context)
refreshContext(context)会解析 基于EnableAutoConfiguration里面的import注解,然后import注解里倒入了一个类,实现了importSelect接口的类,然后它会去加载所有的META-INF下面的spring.factories.
spring中还有哪些方式把bean对象交给IOC容器里面
- xml
- Bean + Configuration注解
- Component+ CompontScan注解
- Import注解:倒入配置类或普通类
- FactoryBean工厂bean
- 使用 ImportBeanDefinitionRegistrar 向容器中注入Bean
- 实现 ImportSelector 接口