Lazy loaded image
技术分享
后端知识笔记
00 min
2021-2-2
2024-11-25
type
status
date
slug
summary
tags
category
icon
password

1. 集合

Collection

List,有序、可重复
  • ArrayList,动态数组,适合查
  • LinkedList,双向链表,适合增、删
  • Vector,线程安全,遗留容器,现代可以使用CopyOnWriteArrayList代替
Set,无序、不可重复
  • HashSet
  • TreeSet
    • 自然排序:对象实现Comparable接口,重写compareTo方法
    • 比较器排序:使用TreeSet构造器,参数传入 new Comparator() 重写compare方法

Map

HashMap,数组 + 链表(使用拉链法,解决哈希碰撞)+ 红黑树(链表长度达到8或数组长度达到64)
  • 插入:put方法,计算key的hashcode,根据hashcode返回下标
    • 下标位置不存在元素
    • 下标位置存在元素,先对比hashcode是否相等
      • hashcode相等,再对比key是否equals
        • key也equals,新value覆盖旧value
        • key不equals,继续与Entry.next比较
      • hashcode不相等,继续与Entry.next比较
  • 扩容:resize方法,插入前先判断是否需要扩容,条件为(元素个数大于等于阈值&&所插入位置元素不为空)
    • 扩容为原来的2倍,元素逐个遍历迁移到新数组,计算新的hashcode(即rehash),返回新的下标
    • 经过rehash后,新下标要么在原位置,要么在原位置+原数组长度位置
  • 执行插入:createEntry方法,头插法,new 新 Entry 并指向旧 Entry头节点
遍历HashMap
  1. for-each遍历map.entrySet()
  1. for-each遍历map.keySet()获取所有key
  1. for-each遍历map.values()获取所有value
notion image

2. JVM

  • 内存结构:jvm栈、堆
  • 垃圾收集器
    • CMS:初始标记STW→并发标记→重新标记STW→并发清除
    • G1:初始标记→并发标记→最终标记STW→筛选回收STW
  • 类加载机制:加载→验证→准备→翻译→初始化
  • 双亲委派:先看是否已经加载过了,没有则父类加载器首先尝试加载,不能加载才由子类加载器加载
  • jvm调优:jsp、jmap、VisualVM、JProfile、Araths
  • 内存泄漏

3. JUC

volatile

  • 保证变量内存可见性
  • 不提供原子性
  • 禁止指令重排序
被volatile修饰的变量执行了写操作时,编译出的汇编代码出现了 lock 指令,该指令保证内存可见性,即
  1. 当前线程工作内存缓存行的数据立即会写到主内存
  1. 其他线程工作内存缓存该地址的缓存行立即失效

synchronized

基于JVM的原生锁,通过对对象头的操作实现同步,别名 监视器锁/对象锁/内置锁,是可重入锁、重量级锁、非公平锁
原理:
  1. 程序运行当前同步方法时,对应执行 monitorenter 指令,尝试获取对象锁,获取成功,计数器 +1
  1. 程序退出当前同步方法时,对应执行 monitorexit 指令,计数器 -1,当计数器为 0,释放锁
重入:
  1. 重入解决了当持有相同对象锁的同步方法之间互相调用时可能产生的死锁问题
  1. 计数器保证了一个线程可以重复获取多次相同的对象锁
  1. 每重复获取一次计数器 +1,退出内层同步方法计数器 -1,直到退出最外层同步方法,计数器减到 0, 释放锁
加锁方式:
  1. 同步代码块,锁为当前对象
  1. 修饰非静态方法,锁为当前对象
  1. 修饰静态方法,锁为当前class对象
JVM对锁的优化:
  1. 自旋锁,线程在指定时间内重复尝试获取锁,超时后线程被挂起
  1. 锁消除,jvm自动检测,对不可能存在共享数据竞争的锁进行消除
  1. 锁粗化,对于连续的同步方法,为避免反复加锁、解锁操作,自动增大锁作用域
  1. 锁膨胀,对象头中保存了线程锁状态,jvm根据规则自动切换
      • 偏向锁:偏向于第一个获取到锁的线程,无竞争时以后该线程都不再需要获取锁,直接进入同步方法
        • 使用一次cas操作把获得锁的线程ID保存到markword中的
          其他线程cas操作失败,说明有竞争,升级为轻量级锁
      • 轻量级锁:无竞争时,使用cas操作获取锁,再使用cas操作释放锁
        • 使用cas操作把markword复制到栈帧,原位置更改为指向锁记录的指针,释放锁时再用cas操作复制回原位置
          其他线程获取锁cas操作失败,说明有竞争,先自旋尝试再获取,获取失败,升级为重量级锁
      • 重量级锁:各线程实行完全的互斥同步,一个线程进入,其他线程阻塞等待

ReentrantLock

AQS(AbstractQueueSynchronizer,java.util.concurrent.locks包下)框架,维护一个双向链表结构的队列
其中lock的实现通过调用AQS框架实现,AQS内部定义了一个 volatile int state 变量
  • state = 0 时,可以获取锁,然后修改 state = 1
  • state = 1 时,其他线程获取锁失败,被加入队列队尾中,等待解锁后再获取
基于Lock接口的实现类,可以实现比Synchronized更细致的操作,必须手动在finally块中unlock锁
  • 可中断正在等待线程
  • 获得锁的线程被中断时能响应,抛出异常
  • 可实现公平锁,在构造器中传入参数true即可开启

同步工具类/同步器

闭锁:CountDownLatch,可以阻塞多个线程直到某些事件发生
信号量:Semaphore,可以控制同时访问某个特定资源的操作数量(如实现固定数量的线程池)
栅栏:CyclicBarrier,可以阻塞多个线程直到所有线程都到达栅栏位置

ThreadLocal

把共享变量的副本存到每个线程中的TreadLocalMap类型的Entry数组中
  • 一个线程可以new多个ThreadLocal对象,每new一个计算下标保存到Entry数组中
  • 每个Entry的key为对应的ThreadLocal(弱引用),value为所set的变量副本(强引用)
  • 通过get、set方法获取与存放
  • 手动ThreadLocalMap.remove,线程长期存活(如使用线程池)且包含ThreadLocal属性的对象长期未被回收,可能产生的内存泄漏
  • 可应用于存储session或存储jdbc
  • 为什么存JDBC?因为当存在某个线程需要频繁的建立关闭JDBC时,服务器压力增大,存到ThreadLocal里就好

Executor

  • newFixedThreadPool:固定数量
  • newCachedThreadPool:可缓存的
  • newScheduledThreadPool:固定数量,定时执行
  • newSingleThreadExecutor:单个线程
创建:Executors.newXXXThreadPool(num)
执行:.execute(Runnable)

守护线程是什么?

除了主线程外的其他线程都是守护线程,如垃圾回收器及其他执行辅助工作的线程

sleep() 和 wait() 区别

sleep让出cpu资源,不释放锁,到时间自动恢复
wait直接释放锁,进入等待状态,调用notify重新获得竞争锁的权力

乐观锁和悲观锁

是一种思想,乐观锁认为共享数据不会被修改,悲观锁认为共享数据会被修改
乐观锁:CAS操作+版本号,需要自己通过在表中添加version字段手动实现
悲观锁:数据库有自己的实现方式,即 共享锁(读锁)和 排它锁(写锁)
  • 共享锁,共享数据可以被其他事务读取但不能修改
  • 排它锁,共享数据只能被当前事务访问,直到事务结束才释放锁

死锁

线程互相抢夺对方占用着的资源互不释放导致死锁
典型示例:线程A获取了obj1作为对象锁,线程B获取了obj2作为对象锁,之后线程A又需要获取obj2锁,线程B又需要获取obj1锁
避免死锁:
  1. 线程按规定顺序获取锁
  1. 给线程获取锁添加超时时间,超时则放弃自身所有已获得的锁让给其他线程
  1. 其他,如使用JUC包的类、降低锁粒度、减少同步代码块数量

分布式锁

一个服务部署到多个服务器上时,要保证这个变量在所有的服务器上都保持一个状态,就需要分布式锁

4. SSM、SpringBoot

简述IOC

IOC即控制反转
控制:控制对象的生成
反转:由主动new对象转为被动获取容器提供的对象实例
核心容器有:Beans、Core、Context、Expression
基础容器BeanFactory提供了管理bean的各种方法
ApplicationContext扩展了BeanFactory的功能
Spring Bean定义了5个作用域(scope):singleton(默认)、prototype、request、session、global session
  1. 通过无参构造器创建bean实例
  1. 调用set方法为bean的属性设置值或引用
  1. 把bean实例传到后置处理器(实现BeanPostProcessor接口)调用postProcessBeforeInitialization
  1. 调用bean的初始化方法
  1. 把bean实例传到后置处理器(实现BeanPostProcessor接口)调用postProcessAfterInitialization
  1. 获取到bean对象
  1. 关闭容器时销毁bean
  • 手动注入利用实体类里定义的set方法、构造方法
  • 自动注入使用autowire="byName" 或 "byType"
  • 基于注解注入使用@Autowired(根据类型)、@Qualifier(根据名称)、@Resource(根据名称或类型)

简述AOP

常用应用场景:日志处理、声明式事务、异常处理
本质:动态代理
无接口时使用 jdk动态代理 Proxy.newProxyInstance(类加载器,接口,InvocationHandler)
有接口时使用 cglib动态代理 Enhancer.create(被代理类,Callback(MehtodInterceptor))
核心:编写切面类,使用注解 @Aspect 并@Component注册为组件
切面类内:
  1. 使用 @pointcut(切点表达式) 定义切点
  1. 切点表达式映射连接点(即被拦截的方法)
  1. 使用通知(@Before、@After、@AfterReturning、@AfterThrowing)确定切入位置
  1. @Around类似动态代理的写法,方法参数ProceedingJoinPoint代表连接点,调用.proceed()执行被拦截方法

简述MVC

  1. 浏览器发送请求到DispatcherServlet
  1. DispatcherServlet寻找处理器HandlerMapping
  1. DispatcherServlet调用Controller
  1. Controller调用Service
  1. 返回ModelAndView给DispatcherServlet
  1. DispatcherServlet调用ViewResolver视图解析器
  1. 解析到的View视图响应到浏览器

手写JDBC

Mybatis 中 #{} 和 ${} 的区别

#{}:替代sql中的?,这样传的参数会预编译,防止sql注入
${}:这样传的参数不会预编译,用来代替sql中的?会直接和sql拼串,可能发生sql注入,但可以传其他sql中非占位符参数,如表名
#{自定义引用名}:传递的值是指定方法中所传入的参数
  1. 如果方法参数是普通类型的类变量,其引用符号的名称必须在方法中用@Param("自定义引用名")给参数声明
  1. 如果方法参数是对象类型的entity实例,用其内部属性映射表中字段,则#{形参名.属性名}即可
#{参数索引}:如果方法中没用@Param定义参数引用名,则只能按顺序写参数的索引位置,从0开始,格式为param0,param1...

简述Springboot自动配置原理

@SpringbootApplication注解里开启了@EnableAutoConfiguration,调用AutoConfigurationImportSelector选择器,里面的方法会扫描所有依赖的jar包类路径下"META-INF/spring.factories"文件,把里面配置的所有EnableAutoConfiguration值都添加到容器,这些自动配置类有对应自己的Properties类,里面可以查看已经自动配置的属性
如Mybatis-plus的jar包
notion image
notion image
notion image

5. 设计模式

手写单例模式

手写工厂模式

6. MySQL

基本的SQL语句

简述索引使用原则

应该建索引
  1. 频繁作为查询条件的字段
  1. 作为其他表查询条件的外键字段
不应该建索引
  1. 频繁增删改的字段
  1. 条件语句中用不到的字段
  1. 表记录很少时
  1. 重复数据很高的字段
索引:是一种数据结构,常见hash索引和b+树索引,innoDB引擎的索引使用b+树
主键索引:使用主键作为聚簇索引(建表自动创建),即按表的主键建立b+树,叶节点存放表的完整行记录
辅助索引:即手动建立的索引,叶节点除了保存字段数据,还包括对应的主键
回表:辅助索引查不到想要的数据,就会到主键索引里去查
覆盖索引:只通过辅助索引就查到想要的数据,Using index
索引优化
  1. 最佳左前缀,即最左侧字段优先使用索引
  1. 不要使用索引字段进行任何计算、函数操作
  1. 尽量使用覆盖索引,即要查询的字段最好都已被创建为索引字段
索引失效:使用不等于、IS NULL、IS NOT NULL时;like模糊匹配头部值;索引字段参与函数计算;OR连接

数据库三范式是什么?

第一范式:数据库表中每个字段都必须是不可分割的原子数据项
第二范式:首先满足第一范式,其次非主键列必须完全依赖于主键列,对于联合主键,不能只依赖主键的一部分
第三范式:首先满足第二范式,其次非主键列必须直接依赖于主键列,不能存在传递函数依赖

简述ACID

  • 原子性Atomicity:事务的所有操作要么全部执行,要么全部不执行,执行期间出错会回滚rollback到初始状态
  • 一致性Consistency:事务结束后,数据库的完整性不被破坏
  • 隔离性Isolation:多个事务并发时,互不干涉,SQL有四种隔离级别
  • 持久性Durability:事务结束后,对数据库的影响就是永久性的

简述四种隔离级别

  • READ_UNCOMMITTED:什么都不解决
  • READ_COMMITTED:解决脏读
  • REPEATABLE_READ:解决脏读、不可重复读
  • SERIALIZABLE:解决脏读、不可重复读、幻读
脏读:一个事务读取到另一个事务修改后还未提交的数据
不可重复读:一个事务多次读取同一数据却返回不同的结果(原数据被另一个事务update了)
幻读:一个事务多次相同的sql查询返回不同的记录(原表被另一个事务insert了新的行)

简述Mysql常用引擎

MyISAM 表锁(修改任何一条记录都会锁整个表,造成并发线程阻塞) 适合读多 不支持事务、外键 保存记录数
  • 加读锁:当前线程只能查询加锁的表,其他线程写入加锁的表时会阻塞直到读锁被释放
  • 加写锁:当前线程只能查询和修改加锁的表,其他线程查询和修改加锁的表时会阻塞直到写锁被释放
InnoDB 行锁(修改同一条记录时,并发线程才会阻塞,当发生索引失效时行锁会变成表锁) 适合写多 支持事务、外键 不保存记录数

@Transactional失效

  1. 应用于非public方法,因为invoke方法或intercept方法会调用
  1. 同一个类的方法互相调用,不会产生代理对象
  1. try-catch,出现异常本应rollback,结果被catch了抛出,然后正常commit了
  1. MyISAM不支持事务
  1. propagation属性设置错误
  1. rollbackFor属性设置错误

7. Redis

基本知识

  • 是一个非关系型数据库,默认16个库,从0号开始
  • 单线程避免上下文切换,纯内存访问,非阻塞式I/O
  • 使用场景:缓存、存session、存频繁读取的数据、分布式锁
  • 5种数据类型:String、Hash、List、Set、Zset
  • 3种java客户端:Redisson(适合分布式)、Jedis(api全,和redis指令对应)、Lettuce(适用于分布式缓存)
  • 2种持久化方式:
    • RDB:fork子进程将数据写入临时文件,可以bgsave手动触发,二进制文件体积小
    • AOF:append写命令,一般设为每秒同步一次,文件体积大,但可读性强,恢复效率低
  • 支持事务,但不保证原子性,MULTI(开启事务)、DISCARD(抛弃事务)、EXEC(执行事务)、WATCH(加锁)

key过期时淘汰策略

key设置过期时间:Expire KEY_NAME TIME_IN_SECONDS
redis采用 定期删除 + 惰性删除
  • 定期删除:从0号库开始遍历检查每个库(默认1s执行10次)
    • 随机获取一个设置了过期时间的key进行检查,过期则删除,循环20次
  • 惰性删除:get或setnx操作时先检查key是否过期,过期则删除

内存满时key淘汰机制

LRU(Least Recently Used):最近最少使用,可实现缓存淘汰机制,即优先淘汰最久未使用的数据
  • volatile-lru:从已设置过期时间的key中挑最少使用的淘汰
  • volatile-ttl:从已设置过期时间的key中挑将要过期的淘汰
  • volatile-random:从已设置过期时间的key中随机淘汰
  • allkeys-lru:所有key挑最少使用的淘汰
  • allkeys-random:所有key随机淘汰
  • no-enviction:禁止淘汰数据

缓存一致性

  • 缓存雪崩:同一时间大量key过期
    • 加锁
    • 使key的过期时间尽量分散
  • 缓存穿透:访问数据库中不存在的数据
    • 布隆过滤器:计算k个哈希函数,所有结果为1,则可能存在,存在结果为0,则一定不存在
  • 缓存击穿:高并发访问同一key,key过期
与MySQL的一致性问题:延迟双删策略
  1. 先删除缓存
  1. 再更新MySQL
  1. 休眠xx毫秒(根据业务调整)
  1. 再删除缓存

分布式锁

####

8. RabbitMQ

基本知识

  • 特点:异步处理、削峰、解耦
  • 使用场景:将比较耗时且不需要即时返回结果的操作作为消息放入消息队列
  • 交换机绑定类型:Direct(直连)、Fanout(广播)、Topic(模糊匹配)

9. 常见算法题

必背api

反转字符串

10. Linux命令

docker命令

docker-zookeeper
上一篇
Docker配置
下一篇
OnJava笔记