面经
昆山java后端小厂面经
比我第一次面试还要崩溃,10分钟结束,面完红温,感觉我是如此渺小。
项目相关
- 这两个项目是你自己做的?创意从哪里来?
- 这两个项目是不是用AI辅助做的?是你自己设计,AI负责实现吗?
- 讲一下你做的最出彩的事情,完整的技术实现路径。
- 这个秒杀功能是不是看视频照着实现的?
- 有没有脱离视频教程,自己独立完成的练习/实践?
Redis
HyperLogLog是干什么的?
嗯,支持百万级的UV,可以做访问统计。
从哪里了解到HyperLogLog?它的底层实现原理是什么?
在学习 Redis 的时候,了解到的底层实现原理是有一个复杂的函数计算吧,跟哈希有关
HyperLogLog(简称 HLL)是基数估算的概率型数据结构,用来统计集合中不重复元素的个数,不存储原始数据,只存少量统计寄存器。
了解Redis分布式锁吗?讲原理、怎么配置。
了解 Redis 分布式锁,是为了解决传统锁在分布式系统下不能
在每个微服务之间(多个进程之间)生效的问题实现的话主要有两种: 一种就是传统的Redis+setnx+TTL+Lua脚本加上随机uuid。 另一种就是用分装好的Redisson框架来实现先说传统的Redis。嗯,他用的是setnx这个命令setnx是不存在才设置它这个命令具有原子性操作,可以让这个判断锁和锁和获取锁之间保证原子性,不被其他客户端抢占,TTL的话是为了给锁设置自动过期时间防止死锁,uuid防止锁误删,Lua脚本用于释放锁时的原子性,防止业务时间大于自动删除时间,导致自动删锁时业务还没执行完,再手动删锁时删掉别的线程持有锁
Redisson的话,它可以实现了一个重试机制和一个看门狗机制。Redisson的try lock里面有3个参数: 一个是wait time, 一个是lease time, 一个是时间单位。 Wait time就是线程等待这个锁的时间,在这个时间内可以进行重试,如果超出这个时间就不再竞争。 Lease time就是每个线程持有锁的时间,如果不传入参数的话,它会有一个看门狗的自动续期机制,默认是30秒如果30秒到了的时候。业务还没有执行完,会自动给他设置回30s
分布式锁的随机TTL了解吗?
了解,设置随机 TTL 主要是为了解决缓存雪崩的问题,防止大量的缓存在同一时间过期,导致大量请求都打到数据库,给数据库造成压力
Redis缓存过期/内存淘汰策略有哪些?
Redis 的过期策略有两种:
- 惰性删除:会在你下一次访问该缓存的时候,才会真正删除。
- 定时删除:设置定时时间,然后进行删除。
Redis 的删除策略有两种:
惰性删除:
当 key 过期时,它不会立即删除,而是在下一次访问这个 key 的时候对它进行判断,看它是否过期。如果过期的话再删除它,如果没有过期的话就返回正常数据。定期删除:
这是 Redis 的后台定时任务,默认是每 10 秒执行一次。它会从过期字典中随机抽取 20 个 key,删除里面过期的 key。如果过期的 key 占比超过 25%,会继续抽样。
Redis 的过期删除策略主要有惰性删除和定时删除。
惰性删除:
是指当设置了 TTL 的 Key 过期时,Redis 不会立即删除,而是等到下一次访问这个 Key 时,再对它进行判断。如果未过期,就返回原来的值;如果已过期,就把值删掉并返回空值。
这种策略会出现一种情况:如果有大量的 Key 过期了,但没有被下一次访问,就会导致这些过期的 Key 残留在内存里。定时删除:
为了防止大量过期的 Key 堆积,Redis 还有一个定时删除策略。它会定期在 Redis 中抽检一部分 Key,判断是否过期,如果过期就进行删除。防止大量的过期key堆积为什么不给所有的 Key 都定时删除,而是要抽检?就是因为如果给每一个 Key 都维护的话,会浪费大量的 CPU 性能。Redis 是高性能数据库,这会影响它的性能
Java基础 & 数据结构
数据结构和算法掌握得怎么样?
了解切面(AOP)吗?
AOP 是面向切面编程,就是把重复的方法抽取出来作为一个切面在不改变源代码的情况下,横向添加新的功能。比如说登录校验呀,还有日志之类的可以减少重复代码,降低代码之间的耦合度。实现 AOP 主要是通过动态代理的方式,动态代理又有 JDK 动态代理和 CGLib 动态代理两种。JDK动态代理,它依赖接口,通过创建一个实现了原来接口的代理对象,在代理对象中对方法进行增强。
CGLIB 是给这个类创建一个子类对象,在子类对象中进行方法增强。
AOP有几种通知类型?环形切面的三个方法是什么?
了解Java泛型吗?泛型中T、?、extends是什么?
Docker & Linux运维
- Docker有哪些常用指令?
反问环节
- 你有什么想问我的?
浩鲸科技Java面经
Synchronized锁升级
3个 nice 的锁升级主要是偏向锁、轻量级锁和重量级锁。
先说轻量级锁:Java 对象的对象头里面有个 Markword,存放的是锁的状态。当用 synchronized 给这个对象加锁的时候,线程的栈帧中会创建出一个 Lock Record。Local Record 会与对象头中的 Mark Word 进行交换:
- 把 Mark Word 的指针指向 Local Record。
- 在 Local Record 中复制一份 Mark Word 原来的内容。
- 通过 CAS 操作,预期值是复制的 Mark Word,修改值是指向 Local Record 的指针,将对象的 Mark Word 指向 Local Record。
- Local Record 的后两位,由 01 的无锁状态变为 00 的轻量级锁状态。
如果 CAS 失败,就说明发生了锁竞争。
锁竞争处理流程:
(a) 判断锁持有者:通过向上搜索 Java 栈,查看是否有 LockRecord,判断当前锁是否由当前线程持有。
(b) 递归加锁:如果是当前线程持有,就将可重入次数加 1。
(c) 自旋与锁膨胀:如果不是当前线程持有,会先进行自旋。如果自旋了很多次还是没有获得到锁,就会发生锁膨胀,从操作系统申请 Monitor 锁,将 Monitor 的 owner 改为当前线程,并将竞争锁的线程放入等待队列。
(d) 锁释放与唤醒:前面的线程执行完之后,将 owner 改为 null,再由等待队列里的线程来进行竞争。轻量级锁释放:
释放轻量级锁时,也是通过 CAS 进行一个反操作,目标是将原来的 Mark Word 里的锁标志由 01 改为 00。偏向锁优化:
如果是同一个线程多次申请这个锁,会将这个锁变为偏向锁。这是为了防止同一个线程多次申请锁而造成不必要的消耗。
synchronized 锁升级顺序:偏向锁、轻量级锁、重量级锁,单向升级不可降级。 轻量级锁加锁时,线程栈帧创建 LockRecord 保存对象原始 MarkWord 副本;通过 CAS 将对象 MarkWord 替换成指向 LockRecord 的指针,标记位变为 00。CAS 失败发生竞争,如果是重入则新增 LockRecord;不是本线程则自适应自旋,自旋失败膨胀重量级锁,创建 ObjectMonitor,竞争线程进入 EntryList 阻塞队列。释放锁通过 CAS 把备份 MarkWord 恢复回对象头。 偏向锁针对单线程反复获取锁的场景,MarkWord 记录线程 ID,省去 CAS 开销;出现其他线程竞争,触发撤销,升级轻量级锁。 重量级锁依靠 ObjectMonitor,线程会内核阻塞,开销较大。
Synchronized与ReentrantLock的区别
Synchronized是非公平锁,Retry lock可以是公平锁,也可以是非公平锁,synchronized不支持响应中断,而retry lock支持响应中断,synchronized不支持多个条件,它必须和wait和notify notify方法一起使用,而retry lock可以和多个condition来完成复杂的线程操作,比如分组唤醒synchronized的底层是基于互斥锁monitor retry lock底层是基于qos框架,PY的加锁会自动加锁,look是手动加速,日产look可以提供更细的力度,性能比second的更高
线程池的工作流程
先看核心线程数:
如果核心线程没满,就用核心线程来执行。
如果核心线程满了,就去看最大线程数:
(a) 如果最大线程数范围内的线程没满,就创建工作线程来执行。
(b) 如果最大线程数也满了,就去看等待队列有没有满:
(i) 如果等待队列没满,就先将线程放到等待队列中。
(ii) 如果等待队列满了,就执行拒绝策略。
顺序错了!!核心 -> 等待队列 -> 最大线程数 ->拒绝策略
线程池提交任务的流程如下:
- 判断核心线程数:如果核心线程没有满,就创建核心线程执行任务。
- 进入阻塞队列:如果核心线程满了,将任务加入阻塞队列。
- 判断最大线程数:如果阻塞队列满了,判断是否达到了最大线程数。如果没有达到,就创建非核心线程执行任务。
- 执行拒绝策略:如果队列满了,并且已经达到最大线程数,就执行拒绝策略。
线程池的拒绝策略
线程池的拒绝策略:
CallerRunsPolicy:用当前线程去执行任务。
AbortPolicy:直接抛出线程池满的异常。
静默处理:不做反应,静默处理线程池满的异常。
停掉最老的线程,然后执行新的任务。
AbortPolicy 默认,直接抛出拒绝异常;
CallerRunsPolicy,让提交任务的调用线程运行任务;
DiscardPolicy 静默丢弃新任务,不抛异常;
DiscardOldestPolicy 丢弃阻塞队列中最老的任务,再尝试执行新任务。
MySQL的ACID
MySQL 的 ACID 分别是原子性、一致性、隔离性、持久性:
原子性:要保证操作之间不能被其他操作插入。
一致性:要保证修改前后的总量是不变的。比如说库存出库,不管怎样,出库量加剩余量都要是一定的。
隔离性:多个数据表之间相互隔离,互不影响。
持久性:数据修改完之后要持久化保存,确定是修改了的。
ACID 分别是原子性、一致性、隔离性、持久性。
原子性:事务里面的操作,要么全部成功,要么全部回滚失败,依靠 undo log 实现。
一致性:事务执行前后业务数据符合约束,是最终目标,由原子性、隔离性、持久性共同保证。
隔离性:多个并发事务之间相互隔离,避免并发带来脏读、不可重复读、幻读问题,由锁和 MVCC 实现。
持久性:事务一旦提交,修改永久生效,即使宕机数据也不会丢失,依靠 redo log 实现。
原子性是什么、如何实现
乐观锁的优缺点、CAS
MySQL的事务分级
MySQL的事务分级分为读未提交、读已提交、可重复读和串行化
可重复读解决了哪些问题
可重复读解决了脏读和不可重复读的问题,并且可以通过 MVCC 和间隙锁来实现解决幻读的问题
JVM中对象何时被回收?
JVM 使用可达性分析算法判断对象是否可以回收。以 GC Roots 为起点遍历引用链,如果对象没有 GC Roots 的强引用链,则该对象具备回收条件。
不可达后会被立刻回收吗?
具备回收条件不等于马上回收,需要等待 GC 触发(YoungGC/FullGC)。对象可以通过 finalize 方法完成一次自救复活,但不推荐使用。
不同引用类型回收时机不同:强引用不会回收;软引用内存不足回收;弱引用只要 GC 就回收;虚引用只用于回收通知。调用 System.gc 只是提醒,不能强制回收。
类加载过程有哪几个阶段?
5
双亲委派模型原理是什么?
双亲委派机制是指在类加载时,子类加载器如果收到类加载请求,会先将请求传递给父类加载器。如果父类加载器无法实现,再由子类加载器来进行加载。
例如,应用程序类加载器会先将请求传给扩展类加载器,扩展类加载器再传给启动类加载器。首先看启动类加载器能否实现,如果不能,再由扩展类加载器实现;若仍不能,最后再由应用程序类加载器实现。
双亲委派机制的好处主要有两点:
- 避免核心类被修改:核心类都存在启动类加载器中。该机制可以防止在应用程序类加载器中重写启动类的方法,从而避免核心方法被篡改(例如,可以避免重写 String 类)。
- 避免重复加载:让父类加载后,子类在调用时就可以复用父类加载的类,无需自己多次重新加载。
MyBatis中绑定SQL入参有哪几种方式?
如何避免MyBatis结果字段与实体属性不一致?
驼峰
sqlas别名
resultmap
mybatisplus@TableField
靖安科技Java全栈开发秋招一面面经
9.14投递
9.16笔试
9.17一面
一面(不到1小时):
1、自我介绍
2、你现在是处于离职状态吗
3、上家公司离职原因
4、HashMap底层原理
链表+数组+红黑树
5、为什么jdk1.8之后数据结构加了红黑树
链表过长时查找效率低
6、HashMap是线程安全的吗,如果多线程同时put一个数据的话会发生什么
不是线程安全的,如果多线程同时 put 一个数据的话,在 JDK 1.7 之前由于它是头插法,可能会造成死链;在 1.8 之后改为尾插法,可能会造成数据覆盖
7、HashMap为什么不是线程安全的
HashMap在多线程操作的情况下,由于它内部没有加锁,所以不是线程安全的
8、为什么ConcurrentHashMap可以保证线程安全
Current hash map是在hash map的基础上加锁,JDK1.7的时候,使用的是分段锁,就是将整个哈希表分为多个分段,然后对分段进行加锁,每个分段里的节点就对应这个分段的锁,1.8之后,使用的是的加上CS来进行的加锁,然后它是对哈希表的每个统节点都进行了加锁,使这个锁的力度更小,主要原因就是JVM虚拟机对S的锁进行了进一步的优化
1 | ConcurrentHashMap 在 HashMap 基础上,依靠锁与 CAS 机制保证线程安全。 JDK1.7 采用 Segment 分段锁,将哈希表划分为多个分段,对每个分段加锁,锁作用于该分段内全部节点,不同分段之间可以并发访问。 JDK1.8 废弃 Segment 分段锁,采用**CAS + synchronized**,对哈希桶的头节点加锁,仅锁定当前操作的桶,锁粒度变得更小。得益于 JVM 对 synchronized 锁(偏向锁、轻量级锁)的优化,加锁开销较低。同时使用 LongAdder 统计元素数量,进一步提升并发性能。 |
9、说一下什么是CAS
CAS就是compare and swap, 属于乐观锁。它主要有三个值嘛,一个值是原来的值,一个只是预期值,一个是只是修改后的值。通过比较原来的值和预期值是否相同,如果相同的话,就把原来的值改为修改后的值。然后他可能会出现三个问题吧,第一个问题就是ABA问题,就是假如将这个比较这个原来值的时候,在比较的时候它还是A,然后在修改之前它也还是A,但是在这个比较和修改过程中,可能会被别的线程修改为B,但是他最终还是A。这个可能不会影响结果,但是也无法捕捉到中间这个过程的变化,然后其次就是有CAS,它是乐观锁,它就会不断的进行自旋,然后自权就可能会造成一些资源浪费,性能性能消耗。然后第三个问题就是CAS它只能比较单个变量的值,如果有多个变量,他就不能实现了
1 | CAS 即 Compare And Swap,比较并交换,属于乐观锁。包含内存实际值、预期值、修改后的新值。比较内存实际值与预期值,如果相等,则把内存值更新为新值;不相等则更新失败。 |
10、synchronized和Reentrantlock区别
Second的锁是非公平锁,而return lock锁它可以是公平锁也可以是非公平锁。Second的它通过monitor互斥锁来实现,而return lock它是通过aqs来实现,aqs它就是根据一个volatile修饰的state和一个非否双向队列来维护的,通过这个state来记录和重入次数,通过链表来实现等待队列,然后synchronized锁是隐式锁,在线程进入虚拟机后自动加锁。而Return lock它是显示锁,必须要手动调用lock方法来进行加锁。Second的它不支持绑定多个条件啊,必须用wait notify notify all方法啊,只能支持单个条件的啊,加锁和唤醒,而return lock可以和condition条件那个组合,然后有多种唤醒情况,可以实现分组唤醒,return lock的锁粒度也更小,性能开销也更小。最后还有second的锁,它不支持中断响应,而return look它有个有个加速的可以支持终端响应,就是线程在等待锁的期间收到了中断命令,可以退出锁的等待
11、Reentrantlock里有公平锁和非公平锁,他们区别是什么
公平锁和非公平锁区别就是在等待队列中竞争锁。公平锁它就是可以按照一定的规则来进行获取锁,比如说先到先得。非公平锁就是每一次锁释放之后,在等待队列里的所有线程都要去随机竞争,锁就是非公平的
1 | **公平锁**:遵循先到先得。线程按进入等待队列的先后顺序获取锁,锁释放后优先唤醒队列头部等待的线程,不允许插队。 |
12、线程池的主要参数
线程池的主要参数主要有7个:
第一是核心线程数
第二是最大线程数
第三是空闲线程存活时间
第四是嗯时间单位
第五是工作队列
第六是线程创建工厂
第七是拒绝策略
13、提交任务时,假如核心线程数为10,最大线程数100,队列1000,线程变化是什么样的,假如超过最大线程数,有哪些拒绝策略可以选择
提交任务时,先看当前核心线程有没有达到核心线程数。如果没有达到的话,就创建核心线程来执行任务。如果达到的话,就将任务放到等待队列中。如果等待队列满了的话,就会看这个核心线程和非核心线程有没有达到最大线程数。如果没有达到的话,就创建非核心线程来执行任务。如果满了的话,就会执行拒绝策略
14、如果队列没有限制大小会出现什么问题
如果队列没有限制大小的话,就会让这个任务无限地排在队列中,可能会造成内存溢出。然后同时会让这个核心线程一直运,一直这个执行全部的任务都交给核心线程,给这个核心线程造成压力,然后利用率也低
1 | 1. **容易发生 OOM 内存溢出**:大量任务不断提交,任务会持续存入队列,队列无限膨胀,占用大量堆内存,最终内存溢出。 |
15、介绍一下ThreadLocal是干什么用的
Trade local是给每个线程创建一个独立的私有站,在trade local里的存储的数据只有当前线程私有,不会共享给其他线程
1 | ThreadLocal 叫做线程本地变量,用于实现**线程间的数据隔离**。 |
16、ThreadLocal底层原理
Threadlocal底层是一个threadlocalmap,存放entry对象,key也就是创建的threadlocal的对象的弱引用。Value就是我们存放数据的强引用。存放弱引用是为了防止,那个当把trade lock trade lock复制为null时,这个如果是强引用的话,它就不会被GC,如果是弱引用的话,当这个trade lock为no时会被JC掉,可以,减缓内存泄露
17、在多线程里加锁的方式有哪些
可以加synchronized锁,reentrantlock锁,还有读写锁,还有乐观锁、悲观锁以及自旋锁
读写锁是Linux接口中的一个read write lock锁,它只支持一个线程写入,支持多个线程读取。说读多写少的情况,可以避免多线程同时写入造成的并发问题
乐观锁就是通过版本号来维持多个线程,在修改数据的时候,通过这个版本号,然后修改不同版本的数据,也可以确认就是线程修改的是它要修改的那个版本的那个数据,就不会因为别的线程修改数据而导致并发问题
18、如果用Redis实现一个分布式锁你会怎么实现
用Redis实现分布式锁,主要用到Redis的setnx. Setnx就是不存在时才创建,就可以确保这个多个线程在修改数据修改同一个数据的时候,可以用setnx保证只有一个线程可以修改数据,然后同时要给这个Redis设置TTL时间防止死锁。然后要用uuid防止那个业务防止这个误删别的线程的锁,就是当这个业务时间大于TTL时间时,可能业务还没有执行完,但是就TTL了,然后锁被别的线程持有,然后当业务执行完的时候,它会删掉别的线程持有的锁。然后在这个判断uuid是否为当前线程时,当前线程持有这个锁时,为了让这个判断和这个删除是一个原子操作,还可以用到Lua脚本
19、Redission底层原理以及怎么续期的
Redisson的底层就是封装了Redis的这个setnx+Lua脚本,它传入3个它的try lock方法要传入3个参数,一个是wait time, 一个list time, 一个单位时间。时间单位如果不是啊,它这个wait time就是当前线程等待的时间啊,就是竞争所等待的时间,如果超过这个时间,在这个时间范围内它就会竞争不到锁,就会进行重试,超过这个时间它就放弃,然后list time就是它这个设置的这个持有锁的有效期,如果设置值的话就是30秒,它就会在30秒后释放,如果不设置值的话,它就会其中有有一个看门狗机制,就是假如它默认是30秒,然后到了30秒之后,这个看门狗机制会检查当前线程的任这个是否还持有这个锁。如果还持有的话,就会给这个线程进行续期,续期到这个30秒
1 | 默认锁 30s,看门狗**每 10 秒(30/3)就主动检查一次**,提前续期,不是等到快过期才处理。 |
20、Redis基础数据类型和使用场景
string 分布式锁
set 无序不可重复 运算 共同好友,去重,黑名单
list 有序可重复 消息队列
hash 购物车
zset 排行榜,排序
21、介绍下缓存穿透、缓存击穿、缓存雪崩以及他们的解决方案
缓存穿透就是每次都访问数据库和Redis中都不存在数据,这样它每次访问的时候Redis中不存在,然后就直接打到数据库,然后数据库中也不存在啊,就会造成数据库压力。解决方法有两个,第一个就是给控制也设置缓存,这样就算那个他第二次查询这个不存在的数据,就会在Redis这里给给它拦截,就不会再打到MYSQL里。然后但是它不能解决这个随机穿透的问题。要解决随机穿透的问题可以用布隆过滤器。布隆过滤器它底层是一个位图吧,就是通过多个哈希函数,然后计算出这个桶的下标,然后在过滤的时候它就会再次通过这个相同的哈希函数来进行计算,如果它全部唯一的话,说明它可能存在,如果有一个不为一,说明它肯定不存在,因为它可能会发生哈气冲突的原因,导致导致这个算出来的同下标相同
缓存击穿就是,当这个弱点数据在TTL时间过后,那个过期之后它会重建嘛,就是在这个重建的过程中可能会有大量的数据来进行访问,然后这个时候它就可能会全部都打到数据库,然后对数据库造成压力啊,为了解决这个就可以用到分布式锁嘛,就是在确保,这个只有一个线程对,就是在重建的这个过程中只能用一个线程访问MYSQL,然后其他线程等待,等到重建完Redis之后,再来访问
缓存雪崩就是大量的这个K设置了相同的过期时间,然后导致这个相同的一段时间内大量的这个K都过期了,然后同时这个访问就都打到了,这个MYSQL会对它造成较大的压力。这个的解决方法也比较简单,就把这些K的过期时间设置不一样嘛,可以在过期时间中加上随机值
22、Redis集群部署的时候,集群模式有哪些
Redis集群分为主从复制、哨兵模式和Redis cluster集群。主从复制是一主多从,一个主机用来写数据,多个从机用来读数据。但是如果主机宕了宕机了的话,它无法,它需要手动那个切换,然后哨兵模式是在主从复制的基础上,用哨兵来检测主机状态。如果主机宕机的话,会自动挑选一个从机,把它作为新的主机,但是它不能水平扩展。第三个是cluster集群,它可以有多个主机来进行写操作,然后也支持水平扩容
23、哨兵作用是什么
哨兵就是在主机和从机之间,如果主机它会创建一个守护线程嘛,就是如果主机宕机了的话,它会挑选一个从机,然后来作为主机来进行写操作
1 | ``` |
- MVCC(多版本并发控制):依靠
undo log版本链 + 读视图ReadView。查询时用 ReadView 判断数据版本是否可见,读取 undo log 中的历史快照版本。普通 select 快照读,依靠 MVCC 解决快照读场景下的幻读。 - 临键锁 Next-Key Lock:行锁 + 间隙锁,锁住记录以及记录左右区间,阻止其他事务向区间插入数据,用来解决当前读的幻读。
1 |
|
MySQL 主从复制依靠 binlog 二进制日志与 relay log 中继日志。
- 主库执行写事务,提交后将数据变更记录到 binlog。
- 从库 IO 线程连接主库,拉取主库 binlog,写入本地 relay log 中继日志。
- 从库 SQL 线程读取 relay log,解析并回放日志事件,在从库执行,实现和主库的数据同步。
默认是异步复制,主库写完 binlog 直接返回,不等从库同步完成。
从机两个线程!!一个读二进制日志,写入中继日志,另一个读中继日志,解析
1 |
|
#{}底层会使用 JDBC 的 PreparedStatement 预编译机制,参数会被当作占位符,先编译 SQL 模板,之后传入参数,参数不会参与 SQL 编译,避免 SQL 注入。${}是直接字符串拼接,参数直接拼进 SQL 语句,存在 SQL 注入风险。
1 |
|
❌错误点:
1. 不是创建工作线程,是 JVM 内存模型:**每个线程拥有自己的工作内存(缓存副本),不是新创建线程**。
2. 不是单纯 JVM 刷主内存,底层依赖**CPU MESI 缓存一致性协议**。
3. 不是强制其他线程主动去读主存;volatile 写会让其他 CPU 的缓存行直接失效,其他线程读的时候发现缓存无效,被迫从主内存加载。
1. Store‑Store 屏障,放在 volatile 写之前,阻止前面普通写重排到 volatile 写之后;
2. Store‑Load 屏障,放在 volatile 写之后,阻止 volatile 写和后面的读写重排序;
3. Load‑Load 屏障,放在 volatile 读之前,阻止前面普通读重排到 volatile 读之后;
4. Load‑Store 屏障,放在 volatile 读之后,阻止 volatile 读和后面普通写重排序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
12. Spring Boot 在项目中主要是当微服务用,还是普通 Java 分模块开发?
用的是微服务用的Spring Cloud阿里巴巴多个把整个程序拆分为多个微服务,微服务之间通过OpenFeign来进行调用配置和这个服务配置和注册到Nacos中。然后有的服务之间通过RabbitMQ中间件来进行消息转发。将来异步调流量削峰
13. Spring Boot 自动装配机制你清楚吗?
Spring Boot自动装配主要是通过Spring Boot application这个复合注解里的enable auto configuration, enable auto configuration注解里面通过import注解导入了一个叫做auto configuration import selector的类,它会自动扫描mate INF包下的配置文件,在SpringBoot3之前它叫做spring.factories SpringBoot3之后叫做auto configuration imports, 然后会通过条件注解,比如说conditional On class对这个配置文件里的配置类进行一个**去重**、筛选、排序之类的,然后spring容器就会把这些筛选后的配置类进行初始化,加载到并容器中
14. Bean 的生命周期能介绍一下吗?
首先呢,会创建这个上下文对象,还有这个变容器之类的,创建变容器的时候,它会然后先通过XML或者注解的方式来读取这个病的定义。然后将这个病定义封装为B对象,封装到这个factory对象里,然后要对并进行实例化,开车来给他,然后是要进行一个依赖注入吗?通过这个set或者do的变量进行赋值,然后下一步就是回调avail。如果他实现了这个b name, 最后的话要给他传入内幕然后呢,factory报表的话啊,对不对,不要传入这个上下文对象,然后有是这个前置方法,如果他实现了processor接口的话,要执行一个前置方法叫做process or in a lazy处理完之后,要是通过这个post construct Julia或者是XML里init method这个方法呀,而对他进行出手,他要是有实现一个叫inner light接口的话,要给他就是InitializingBean的afterPropertiesSet方法;然后这个初始化方法执行完之后,这个后置处理也是这个猜猜里面的一个叫做process after ization方法。然后完了这个对象就这个病对象就使用完之后要对它进行销毁。通过这个**@PreDestry**注解或者是AML里这个stream method定义的方法,如果要是有这个disable的话,也要实现这个接口里的这个distry方法
首先,它会初始化容器会创建一个方向,In fact对象网页初始化并bay注解或者XML方式来读取这个病的定义
然后将它注册到并FAY对象里,然后第二步就是使通过反射来对这个病对象来实例化,然后实例化之后要进行一个依赖注入,通过set或者autowired对这个成员变量进行赋值;复制完之后要有一个avail回调,分别有3个接口,分别是这个be name aware,要传入这个名的内幕,beanfactoryaware要传入beanfactory对象,还有application context aware要传入这个上下文对象,然后就是要有一个前置的一个,如果它实现了postprocessor接口的话,在这个初始化方法之前,要执行一个post process before initialization, 然后要完成之后就要执行这个初始化,通过post construct或者XML里的init method定义的方法看来对他能对并进行一个初始化。如果他实现了In the lazy bean接口的话,要执行里面的这个after properties set方法,完了之后就是一个后置处理,和前面前置处理一样,也是post processor哦,实现的是里面的post the process after in, 这个完了之后并实例就可以被这个用完之后要进行销毁。下个月是通过pre出去或者这个XML里这个destroy method定义的方法。来进行销毁,不过也是有这个DisabNBean接口,要执行这个接口里的destory方法
```
如何设计高并发秒杀活动,保证不超卖?
来说一下秒杀场景要注意的点,首先秒杀场景要解决的两个最重要的点,一个点就是秒杀时会有大量的请求来,这个时候要进行一些负载均衡来,这个减小压力,然后另一个点就是要防止这个订单超卖,库存扣减问题,然后先说这个减小压力这一块吧,首先在前端界面要对这个,要把这个秒杀界面作为一个静态界面存到CDN中,这样秒杀活动开始的时候,用户就这个加载静态界面,让这个服务端专心去处理这个秒杀请求,然后第二就是通过这个用户在这个秒杀活动开始之前,把这个按键设置为不可点击,防止这个到这个秒杀时间就用户发出发出这些无效请求。然后在这个前端解决超卖问题的话,可以把这个按键在点击完之后设置成不可点击,就从前端界面先啊,防止这个用户点不生成订单之类的问题,然后这是前端界面,前端界面完了之后,到这个Nginx用这个词,它要做一个负载均衡,把这个前端大量分到多个Nginx服务器上,然后可以进行一个流量削峰。除此之外,Nginx还可以在网关这一层进行一些过滤,拉黑呀,然后限制IP这个操作,现有这个每秒请求量和这个每个IP的这个总都可以进行一个限制,防止恶意的刷单之类的,然后Nginx完了之后就是要到到缓存界缓存缓存首先就是要把这个秒杀开,活动开始之前要把这个预估从这个MYSQL数据库复制到Redis中,提前复制好,这样秒杀活动开始的时候就可以直接从Redis中然后。除此之外,在Redis上还要做一个防止超卖的一个解决防止超卖的问题,可以用这个令牌机制吧,就是给每个这个订单请求这个设置一个唯一的流水号,然后到Redis中,然后每次生成订单的时候都要查询一下这个Redis是否是否有这个流水号。每次这个请求业务的时候要查询一下有没有这个流水号,如果有的话就重复啊,这个重复请求就拒绝掉,然后在这个扣减库存的时候,它这个要首先要判断这个库存是否大于零,然后再对这个库存进行更更新嘛,然后这两个操作它是两个操作,所以需要用到这个Lua脚本来保证它这个操作的一个原子性。除此之外,也可以用分布式锁来实现啊,这个防止防虫,但是分布式锁它会让这个请求串行化,而且可能也要有一些那个IO操作性能较低,所以直接使用Lua脚本,然后从这个缓存到数据库中间可以用消息中间件用RabbitMQ来进行一个转发,同样它也是首先就是有一个异步转发的功能,把这个实现这个生成订单和这个修改数据库的这个功能,就是请求后端,后端这个实现和这个修改数据库分为这个两个部分,然后在这个后端,后端这个操作完成之后,就可以先返回前端,这个正在修改,然后然后由另一个这个异步操作来来修改这个数据库内容。然后同时也可以对这个流量进行一个限制,可以有这个削峰填谷的这个可以在这个,高并发时期把这些修改数据库的操作先暂留,然后存到到这个流量低谷的时候再进行这个修改,这样的话它就不,它就会有一个这个不会立即同步的 情况出现,所以要注意保证数据一致性,要用延时双删和canal监听来保证数据一致性,然后也要设置定时任务来定时比较redis和mysql数据库的库存是否一致,如果不一致要有兜底回补方案。最后在mysql数据库也要有兜底防超卖,比如设置版本号或者乐观锁来保证只有一个线程修改数据库,再用唯一索引确保只能添加一个订单
秒杀场景下,如何兼顾性能与防超卖?
Lua 脚本中的 Key 如何设计?
可以用userid+商品url
Redis 不可用时,系统有自愈能力吗?
不可用,首先先考虑它有没有这个哨兵模式或者集群
然后,如果有这个哨兵模式或集群的话,可以让它这个通过哨兵模式来重新选择主节点这个就是Redis内部的一个自愈,如果他这个主主节点和从节点全部挂掉的话,然后可以把这个请求再进一步限流,限流到这个MYSQL数据库可以接受的范围之内,然后让他去查询MYSQL数据库,当然这个就可能会对这个数据库造成很大压力嘛,哦,如果要是这个实在承受不了,可以对他进行进行一个熔断处理,就前端返回一个当前活动火爆这种,然后再重试这种,然后让如何保障 Redis 高可用?
Redis高可用性有三种,第一种是组同复制,然后哨兵模式和cluster集群,然后复制就是读写分离,将这个主机作为写操作,然后多个从机来进行操作,防止这个读操作对这个写造成的影响,提高这个主机的性能,然后它有一个不足,就是当这个主节点挂掉之后,从节点需要这个手动,再次这个从这个从节点选择一个从节点作为主节点,然后这个时候可以用这个,哨兵模式它有这个哨兵节点,可以选择一个leader,然后用这个leader来选择一个,当这个主节点挂机之后,由leader从从节点选择一个作为这个主节点,然后还有这个cluster集群,Cluster集群是将这个数据将这个K分为16384个这个166384个槽,然后在这个把这164816384个槽分给这个主节点,然后,主节点越多,每个节点分的槽就越少。然后,如果要查找K的时候,直接查找这个,算出这个K对应的还有槽位,然后再根据这个槽位找到对应的节点地址,直接访问这个节点即可,这个cluster集群它有这个高可用性、高性能和易拓展可以进行这个分片,然后要查找数据,直接访问这个对应哈希槽的这个节点即可。然后拓展的时候,它也可以直接动态添加节点
稳定性设计方面有什么经验?
