当前位置: 首页 > news >正文

学习八股的随机思考

随时有八股思考都更新一下,理解的学一下八股。谢谢大家的阅读,有错请大家指出。

bean的生命周期

实际上只有四步

实例化 ----> 属性赋值 ---> 初始化 ---> 销毁

但是在实例化前后 初始化前后 会存在一些前置后置的处理,目的是增强bean在初始化完成后的功能,或者是增强bean在创建过程中的日志记录。

总体流程为 IOC容器启动--》创建beanFactory---》读取xml配置文件--》实例化bean---》属性注入--》初始化bean--》使用---》销毁

单例模式思考

bean被声明为单例的时候,在处理多次请求的时候在Spring容器里只实例化出一个bean。目的是为了我们全局使用的都是一个bean对象,不会存在每调用一次就会创建一个bean的情况,

目的是以下几点:1、减少内存占用,每次都创建bean,占用过多不必要的资源 2、减少JVM的垃圾回收,在创建过的的bean的时候,此时内存会进行垃圾回收,影响系统效率。 3、若容器中已经有对应的bean,这时候就直接获取,减少创建过程。

工厂模式使用思考

在spring创建bean中,首先我们要通过读取xml文件,或者commentScan方法读取注解,找到我们想要创建的bean的定义。这时候如果我们只是通过没扫描到一个bean就new的方法,扩展性不强,而且代码耦合性高,无法对外扩展。使用工厂模式,我们只需要传入bean的名称,或者bean 的类型,只由工厂对外提供创建方法,扫描到bean的时候,调用工厂的创建方法即可。

springboot自动装配粗略流程

springboot 对比spring优化地方在于,基础组件在导入时候会自动创建bean,但是器本质上还是读取文件,找到bean定义。

以自动装配redis为例:

1、容器启动 依靠SpringApplication 注解,开启自动装配功能

2、扫描所有的spring.factories 文件,里边定义了各种自动装配类的类全路径例如

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\

3、拿到RedisAutoConfiguration类之后会根据条件判断,是否导入了redis 依赖,导入依赖呢么就会绑定redis的属性文件,redis的属性文件又和application.properties文件的属性值绑定,因此自动创建出了redis

ThreadlLocal

ThreadLocal 用于实现线程本地存储,为每个线程提供独立的变量副本,具备线程隔离特性,可存储线程上下文信息,典型应用是存储每个请求携带的用户信息。

实现原理

ThreadLocal 的实现基于 Thread 类中的 ThreadLocalMap 成员变量。每个 Thread 对象都有自己的 ThreadLocalMap,该 ThreadLocalMap 中存储数据的键(key)是 ThreadLocal 对象。

  • set 方法:调用 ThreadLocalset 方法时,会获取当前线程的 ThreadLocalMap,并将 ThreadLocal 实例作为键,要设置的值作为值存储在 ThreadLocalMap 中。
  • get 方法:调用 get 方法时,同样会获取当前线程的 ThreadLocalMap,并根据 ThreadLocal 实例作为键来获取对应的值。

内存泄漏问题

弱引用设计目的

ThreadLocalMap 的键是弱引用(WeakReference),值是强引用。弱引用是一种较弱的引用关系,当一个对象只被弱引用所引用时,在垃圾回收时,无论当前内存是否充足,该对象占用的内存都会被回收。这样设计的主要目的是,当外部没有对 ThreadLocal 实例的强引用时,让 ThreadLocal 实例可以被垃圾回收。

内存泄漏原因

如果 ThreadLocal 对象被回收(例如将其定义为局部变量,使用完后局部变量作用域结束),但线程仍然存活,ThreadLocalMap 中的值不会被回收,从而导致内存泄漏。具体过程如下:
ThreadLocal 实例没有外部强引用时,由于它在 ThreadLocalMap 中是以弱引用的形式存在,在垃圾回收时,ThreadLocal 实例会被回收,此时 ThreadLocalMap 中的键就会变成 null。但 ThreadLocalMap 中的值(value)是通过强引用存储的,只要当前线程还存在,ThreadLocalMap 就不会被销毁,这些值就不会被回收,即使这些值已经没有实际使用意义。

线程池场景下的内存泄漏隐患

线程池的核心特性是线程复用。当任务提交到线程池,线程池会获取空闲线程执行任务,任务执行完毕后,线程不会被销毁,而是放回线程池等待下一个任务。
在某个任务中使用 ThreadLocal 存储数据,任务执行结束后,由于线程复用,ThreadLocalMap 仍存在于线程中。若此时 ThreadLocal 对象的强引用被释放(如局部变量作用域结束),ThreadLocal 对象只能通过 ThreadLocalMap 中的弱引用(Entrykey)引用,下次垃圾回收时,ThreadLocal 对象会被回收,导致 Entry 中的 key 变为 null
由于线程未被销毁,ThreadLocalMap 一直存在,Entry 中的 value 不会被回收(除非手动清除)。随着线程不断复用并使用 ThreadLocal,内存中会积累大量无法回收的 value,进而引发内存泄漏。

在一个Thread中他的ThreadLocalMap只有一个,但是ThreadLocal可以有多个。

  public static final ThreadLocal<String> tl1 = new ThreadLocal<>();

    public static final ThreadLocal<Long> tl2 = new ThreadLocal<>();
    public static final ThreadLocal<Integer> tl3 = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(()->{
           tl1.set("1");
           tl2.set(2L);
           tl3.set(3);
            System.out.println(tl1.get());
            System.out.println(tl2.get());
            System.out.println(tl3.get());
        }).start();

    }

Map

1.8之前数组+链表 1.8 之后数组+链表+红黑树变形

1、为什么长度为2的次方

由于在放入元素时候是利用key的hash值做取余操作,但是计算机运算是利用位运算来实现取余操作,而位运算都是以2为底的运算

2、为什么计算hash要用hashcode

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

如果使用直接使用hashCode对数组大小取余,那么相当于参与运算的只有hashCode的低位,高位是没有起到任何作用的,所以我们的思路就是让 hashCode取值出的高位也参与运算,进一步降低hash碰撞的概率,使得数据分布更平均,使得到的 hash 值更加散列,尽可能减少哈希冲突,提升性能

3、put操作流程

①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;

②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;

④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值

对,否则转向⑤;

⑤.遍历table[i],并记录遍历长度,如果遍历过程中发现key值相同的,则直接覆盖value,没有相同的key则在链表尾部插入结点,插入后判断该链表长度是否大等于8,大等于则考虑树化,如果数组的元素个数小于64,则只是将数组resize,大等于才树化该链表;

⑥.插入成功后,判断数组中的键值对数量size是否超过了阈值threshold,如果超过,进行扩容

4、get

首先根据 hash 方法获取到 key 的 hash 值

然后通过 hash & (length - 1) 的方式获取到 key 所对应的Node数组下标 ( length对应数组长度 )

首先判断此结点是否为空,是否就是要找的值,是则返回空,否则判断第二个结点是否为空,是则返回空,不是则判断此时数据结构是链表还是红黑树

链表结构进行顺序遍历查找操作,每次用 == 符号 和 equals( ) 方法来判断 key 是否相同,满足条件则直接返回该结点。链表遍历完都没有找到则返回空。

红黑树结构执行相应的 getTreeNode( ) 查找操作。

5、为什么链表长度为8 才转为红黑树

通常使用链表来处理key冲突的情况,但是随着数据增长,链表会长,所以为了保持查询效率,长度为8时候转为红黑树,退回6时候,在转为链表。选8的原因是,数据分布经过hash函数的计算后,不会出现很大程度上的堆积,并且我们用map时候也不会存非常多数据,所以这种情况概率很小。

6、扩容

1.7扩容 1、当前存储的数量大于等于阈值 2、发生hash碰撞

就是hashmap在存值的时候(默认大小为16,负载因子0.75,阈值12),可能达到最后存满16个值的时候,再存入第17个值才会发生扩容现象,因为前16个值,每个值在底层数组中分别占据一个位置,并没有发生hash碰撞。

当然也有可能存储更多值(超多16个值,最多可以存26个值)都还没有扩容。原理:前11个值全部hash碰撞,存到数组的同一个位置(这时元素个数小于阈值12,不会扩容),后面所有存入的15个值全部分散到数组剩下的15个位置(这时元素个数大于等于阈值,但是每次存入的元素并没有发生hash碰撞,所以不会扩容),前面11+15=26,所以在存入第27个值的时候才同时满足上面两个条件,这时候才会发生扩容现象

先扩容后添加

1.8扩容:先添加后扩容 1、当前存储的数量大于等于阈值 2、当某个链表长度>=8,但是数组存储的结点数size() < 64时

扩容是同步的

concurrenthashmap

concurrenthashmap是线程安全的,过原子操作和局部加锁的方法保证了多线程的线程安全。

hashtable 是线程安全的,但只是在put方法上加上synchronized,但是也因此性能低下。

1.7时

JDK 1.7 中它使用的是分段数组加链表的形式实现的,而数组又分为:大数组 Segment 和小数组 HashEntry。分段就是一个分片,标识hash值的范围

基于ReentrantLock 加分段锁

1.8

使用CAS+synchronized 。

查到空节点使用CAS去插入数据,不为空则使用 synchronize 加锁,遍历链表中的数据,替换或新增节点到链表中

List

ArrayList

数组实现,线程不安全的。底层是数组,初始大小默认10。添加元素时,若元素个数加1大于当前数组长度则扩容,扩容后大小约为原数组的1.5倍。还分析了按约1.5倍扩容的原因,是为避免频繁扩容和空间浪费,且能利用移位操作。

 int newCapacity = oldCapacity + (oldCapacity >> 1)

LinkedList

双向链表,不需要连续的空间

CopyOnWriteArrayList

是通过数组实现的,但确是线程安全容器。CopyOnWriteArrayList的增删改都需要获得锁,并且锁只有一把,而读操作不需要获得锁,支持并发。在增删改的过程中都会创建一个新的数组,操作完成之后再赋给原来的引用,这是为了保证get的时候都能获取到元素,如果在增删改过程直接修改原来的数组,那么可能会造成执行读操作获取不到数据。因此在增删改的过程中都会先创建一个数组。待操作完成之后再将新数组的引用赋值给array。

使用每个方法内用synchronized、和Volatile 修饰数组

    public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }

final transient Object lock = new Object();

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

因为在写操作中,都使用了synchronized,会影响效率,所以使用写时复制技术,写操作是原子操作,array是volatile的,在读的时候,只要写好了,就可以读最新值,没写好,也不会影响读操作的进行。

相关文章:

  • Scratch037-(钢琴)
  • 数据库9(实验过程中补充学习)
  • 负氧离子是怎样产生的?
  • 百度网盘安卓版下载速度与储存体验分析
  • 2025年机电一体化、机器人与人工智能国际学术会议(MRAI 2025)
  • 解决在linux下运行rust/tauri项目出现窗口有内容,但是渲染出来成纯黑问题
  • 多语言编写的图片爬虫教程
  • Jmeter接口性能测试方案
  • (一)机器人仿真平台pybullet基础学习(操作记录)
  • yolov11设置n、m、s、l、x对应的模型大小
  • 服务器风扇故障导致过热问题的解决方案
  • 力扣面试150题—旋转图像和矩阵置零
  • Alembic 和 fbx存储结构和存储动画对比
  • 48、Spring Boot 详细讲义(五)
  • 最新扣子实战教程,利用扣子平台通过在线表格记录,批量生图,再也不要一条条的粘贴提示词了
  • 如何查看网页或任意文档中的颜色数值
  • 如何用DeepSeek大模型提升MySQL DBA工作效率?实战案例解析
  • 英飞凌TLE9891 +TLE5501 有感油泵FOC控制方案
  • truss开源程序在生产中提供 AI/ML 模型的最简单方法
  • v837s-ov5640摄像头调试
  • 运油-20亮相中埃空军联训
  • ETF市场规模首破4万亿,月内ETF基金净流入超3000亿
  • 白宫慌了!将设工作组紧急处理对中国加征关税危机
  • 再现逆转!蒯曼击败伊藤美诚晋级澳门世界杯女单决赛
  • 徐州沛县一村委会因无资质处理固废,被环保部门罚款19万元
  • 疼痛管理“童”样重要,解读围术期疼痛管理