Batj面试 总结

Posted by NotGeek on March 1, 2018

01 – 异常相关

相关的类

Throwable

Exception

  • 受检异常
  • IOException
  • SqlException
  • RuntimeException

Error

使用相关

Error

​ 通常是硬件的错误,与代码无关,不应该捕获

Exception

  • RuntimeException

    是由程序逻辑导致的

  • CheckedException

    程序在运行前无法预料到,为了程序的健壮性,运行时必须捕获

处理方式

try .... catch
throws

日常使用方式

  • 异常必须处理
  • 异常是逻辑的补充
  • 受检异常不足
    • 类的变更会导致调用者破坏了类的封装性
    • 降低了类的可读性
  • 一般将异常转化为运行时异常去处理,全局异常的统一定义

02 – 强引用、软引用、弱引用、虚引用

目的

  • 了解生命周期
  • 不用的场景使用不同的引用类型,避免 OOM 异常

强引用-strongReference

  • JVM 不会回收这个对象,内存不足,抛出 OOM 异常
  • 只有在类被卸载的时候被销毁,进程结束的时候被卸载
  • 两种回收情况
    • 脱离了作用域之后,就会被回收
    • 这个对象设置成 null,就会被回收

软引用-softReference

  • 使用方式
    • 套用 SoftReference
  • 回收情况
    • 程序发生 OOM 异常
    • 对内存 2G,生于 1024 * 1000 秒之内,没有调用,被回收

弱引用-weakReference

  • 回收情况
    • 设置成 null , GC
  • 注意
    • 字符串在常量池里边,不会被回收

虚引用-phantomReference

  • 我们可以知道这个对象将要被回收了,去做一些事情

* 重点

  • 内存重组的情况下,强引用、弱引用都不会被回收
  • 内存不足的情况下,软引用才会被回收,避免 OOM 异常

03 – 一个方法交换两个 Integer 值

JAVA 传递

分类

  • 值传递
  • 引用传递

注意

  • 传递基本数据类型,传递的是原始数据的一个副本,改变副本,不改变原始的值
  • 传递引用类型,函数接收到的是原始引用类的内存地址,可以修改原来的值

我们引用都是存在堆里边,对象存储在堆中。

做题

  • 需要修改值

    • private final int value

      • 反射改变原来的对象的值

      • field.setAccessable(true)

        可以绕过检查

  • Integer 有缓存

    • -128 ~ 127 都在内存中
      • 为了提高程序的侠侣,减少内存的分配
    • Integer a = 1;
      • 调用 Integer.parseInt()
        • 得到缓存中的地址

总结

  • 函数的值传递、引用传递
  • 对象的引用传递传递的是一个内存地址
  • 反射的可访问性
    • field.setAccessable(true)
  • Java 编译器的自动装箱,自动拆箱
  • Integer 的缓存

04 – 动态代理原理分析

考察点

  • 动态代理本身机制的理解
  • 了解 JVM 的运行原理

目的

  • 提供一个代理
  • 控制对真实对象的访问
  • 做消息的预处理、过滤、日志 等,还有消息的后续处理
  • 增强程序的灵活性

一种设计模式

总结

  • 访问代理对象来访问真实对象
  • 间接性,可以加入处理
  • 可以协调调用者和被调用者的关系,减低系统的耦合度
  • 作为客户端和目标对象的中介,可以起到保护真实对象的作用

实际场景

  • 预处理
  • 身份认证
  • 日志

静态代理与动态代理

静态代理

  • 缺点
    • 一个代理只能代理一类对象,代理多个类,需要多个接口
      • 类膨胀
    • 代理对象中存的是目标对象的引用

动态代理

  • JDK
    • 是西安 InvocationHandler 接口,重写 #invoke() 方法
  • CGLB
    • 对类代理,生成一个子类
    • 利用父类——》》子类的关系,实现拓展

总结

  • JDK 的动态代理是通过接口的方法名在动态生成的方法名里变更,去调用业务实现类的同名方法去实现拦截的。
  • CBLG 使用继承我们具体的需要被代理的业务类,去动态的生成业务类的子类,去重写业务方法,进行代理

05 – 设计模式的优雅落地

体现

  • 思想
  • 代码的可拓展性和灵活度

设计原则

单一职责原则

  • 优点
    • 类的复杂度见底了
    • 类的可读性提高了
    • 类的可维护性提高了
    • 变更引起的风险降低了
  • 缺点
    • 增加了一些类,导致后期的类的维护变得复杂

里氏替换原则

  • 子类可以拓展父类的功能,但不能改变父类原有的功能
    • 只要有父类出现的地方,子类就可以出现,替换以后没有错误和异常
    • 如果子类不能完全的实现父类的方法,或者父类的方法改变了,建议不要用父子类关系,可以用依赖、组合、聚合 等方式。

依赖倒置原则

  • 通过抽象使各个类实现彼此独立,不相互影响,实现松耦合
  • 核心就是依赖倒置原则

接口隔离原则

  • 接口最好细化,提供最小力度的方法

开闭原则

  • 对扩展开放,对修改关闭

迪米特法则

  • 一个类对于自己依赖的类,知道的越少越好。对于自己依赖的类,逻辑要封装到类的内部,对外提供一个 public 方法
  • 应用
    • 迪米特法则

设计模式

策略模式

  • 应用场景
    • 事项某一个功能需要算法、策略
    • 根据环境或者策略选择不同的算法
  • 总结
    • 就是对算法的一个包装,把算法的责任和算法本身分割出来,委派给不同的对象做管理
  • 例子
    • 支付
      • 收银台
        • 网银支付
        • 微信支付
        • 信用卡支付
    • 思想
      • 定义一个工厂类
      • 根据不同的策略生成不同的对象
      • 调用同一个接口

适配器模式

  • 例子
    • 上传文件
      • 阿里云的上传
      • 亚马逊的上传
    • 代码实现
      • 统一的service接口
        • 阿里云的适配器
          • 阿里云的 SDK
        • 亚马逊的适配器
          • 亚马逊的 SDK

工厂模式

06 – 从底层分析 List 和 Set 的区别

  • List
    • 有序允许重复
  • Set
    • 不允许重复

arrayList特性

  • Object[]
    • 可变的动态数组
  • 1.5 倍扩容
  • 删除时,右边的元素全部左移一位
  • transient Object[] elementData
    • 它的值不可以被序列化又重写了 readObject() 方法,大于 0 的时候,会对它进行序列化
  • 三种构造方法
    • 带参数的构造
    • 不带参数的构造
    • Collection<? extends E> 构造
  • 默认的初始化大小
    • DEFAULTCAPACITY_EMPTY_ELEMENTDATA = 10
    • 只有在第一次添加的时候,才知道初始的大小

Set

  • HashSet
    • HashSet 就是对 HashMap 的一个包装
    • 无序性
    • Key 唯一性
  • TreeSet
    • TreeSet 就是对 TreeMap 的一个包装

07 – 从底层分析ClassLoader加载机制

背景

  • 不但要使用技术,还要知道原理
  • 写好Java文件,编译成 Class,组成 Java 程序,运行
  • 一个class 调用另一个 Class ,找不到汇报 ClassNotFoundException
  • 通过程序的需要,通过类加载机制实现动态的加载

类加载器

  • 职责
    • 把 Class 加载到 JVM 内存里面
      • 根据类的名称找到i字节码,然后从字节码中定义一个 Java 类
  • 种类
    • 启动类加载器
    • 扩展类加载器
    • 系统类装载器
    • 自定义装载器

JVM

装载所有的 .class 文件到内存里边

  1. 类装载器

    1. 验证

      验证我们的字节码是不是符合 JVM 规范,让它造成安全问题,验证失败会报 NoclassDefFoundError

      • 文件格式验证
      • 原数据验证
      • 字节码验证
      • 符号验证
    2. 准备

      • 为变量去分配内存
      • 设置类变量的初始化
      • 静态变量的生命周期和类是绑定的,叫他类变量
    3. 解析

      • 把常量池中的符号引用替换成直接引用
        • 类接口的解析
        • 字段解析
        • 方法解析
        • 接口方法解析
  2. 初始化

  3. 使用

  4. 卸载

划分职责

  • 启动类加载器
  • 扩展列加载器
  • 系统类加载器

类装载器

  • BootStrap Classloader
    • JRE\lib\rt.jarXbootclasspath指定的 jar 包
  • extension Classloader
    • JRE\lib\ext\*.jarDjava.ext.dirs 指定的 jar 包
  • application Classloader
    • Load CLASSPATHDjava.class.path 指定的 jar 包
  • Custiom Classloader

加载过程

  • 自底向上检查类是否已经加载
  • 自顶向下尝试加载类

加载

  • 双亲委派模型
    • 加载一个类会已成已成的往上加载,看不看在这个职责范围内告诉下一级,不是我的。
    • 通过分层的方式,并且划分职责,能够圈定哪些类应该在哪个职责去加载
  • 意义
    • 安全性
      • 对于我们核心功能产生影响的类,如果随意定义一个类加载器都可以加载就会造成影响
      • example
        • 定义一个包 java.lang 定义一个类 String 这个自定义 String 中的方法是找不到的
    • 分层性
      • 分层次的加载能够让我们的加载变得很清晰,而不会变得很混乱
    • 统一性
      • Java 类的加载顺序随着它的加载机制存在一种优先级的层次感

ClassLoader 源码

  • 如果父类不为空,就交给父类去加载
    • parent.loadClass(name, false)
  • 如何判断两个类是不是相同的
    • 加载器是不是一个
    • 包名.类名 是一样的,一个类才是相同的

一个类的实例化顺序

  • 没有父类
    • 静态变量,静态初始化块
    • 变量,初始化块
    • 构造器
  • 有父类
    • 父类的 static 方法
    • 子类的 Static 方法
    • 父类的构造方法
    • 子类的构造方法

08 – 关于深克隆和浅克隆

背景

  • 克隆是在内存中,相比于 new 一个对象再去赋值,性能上好一点
  • Java 的一些源码用到了克隆

类别

  • 深克隆
  • 浅克隆

定义

  • 克隆是为了快速的构造一个和原有对象一摸一样的副本

使用

  • 实现 Cloneable() 接口
  • 重写 clone() 方法

源码

  • protected native Object clone() throws CloneNotSupportedException
    • native 表示是调用 C 语言的实现

注意

  • 浅克隆
    • 如果复制的对象都含有原来对象相同的值,而对所有其它对象的引用,仍然指向原来的对象
  • 深克隆
    • 父类子类都需要实现 Cloneable() 接口

09 – 如何将一个对象序列化到文件中

背景

  • 序列化一般封装在底层
  • 用途
    • 加密
    • 持久化
  • 意义
    • 序列化和反序列化的选型
    • 提高通用性、健壮性、安全性
    • 更加方便去扩展
    • 序列化允许我们跨 JVM 传输
  • 网络传输 Java 对象
    • 序列化为 二进制文件
    • 网络传输
    • 反序列化为 java 对象

评价一个序列化好坏的标准

  • 序列化以后数据的大小
  • 序列化本身计算的速度
  • CPU、内存的开销
* 当架构非常大,流量非常多的时候,资源的开销就会变得非常的珍贵

序列化技术

  • Java 原生序列化
    • 缺点
      • 序列化以后数据比较大
      • 不支持跨语言
  • XML
  • JSON
  • Hessian
  • protobuf
  • avro
  • kyro

使用

  • 当有多个 transient 修饰的属性的时候,需要按照顺序,读取,按照顺序,写入。

  • serialVersionUID

    • 最好设置这个,设置以后不对的话会报错

10 – 乐观锁和悲观锁的原理和作用

背景

    • 在 CPU 层面下引入的高速缓存,在多核心的 CPU 下,多线程并行执行的时候,会导致缓存一致性问题

    • 引入总线索或者缓存锁去保证缓存的一致性。

      锁的核心理念就是一种同步机制 在多线程并行执行的时候对共享资源访问的安全性。

  • 分类

    • 重入锁
    • 独占锁
    • 共享锁
    • 读锁
    • 写锁

数据库锁

乐观锁
  • 它认为我们所做的事情是乐观的,每一次获取一些共享资源的数据或者去对数据做一些修改的时候,会认为这个时候不会有所得冲突,不会主动去对共享资源加锁

  • 使用

    • 一般加一个 version 字段
    • 然后写入的时候,进行判断处理,
悲观锁
  • 类似于 Java 中的 synchronized
  • 悲观锁效率比较低
  • 缺点
    • 开销大
    • 系统负载高
    • 降低系统的并行

乐观锁和悲观锁怎么应用

区别
  • 一个先加锁再访问
  • 一个更新的时候,再去重试
在合适的场景下运用合适的技术
  • 乐观锁适用于读多写少的情况下,提高系统的吞吐量
  • 如果锁冲突比较多的话,直接采用雷管所,它会不断地去 #retry(),会减低了系统的性能

11 – BIO-NIO-AIO

种类

  • BIO

    NIO AIO

背景

  • 传统的网络通信的代码
    • server
    • client
  • JDk 1.4 之前
  • java.io
    • 文件流
      • 网络流
  • java.net
定义
  • 同步
  • 是否要亲自的去监听操作,不断地关注是否完成
  • 阻塞
    • IO 操作没有完成,在单线程情况下没有办法进行 IO 操作
过渡时期
  • 每次来一个客户端的 socket 连接,都针对这个 socket 连接创建出一个线程
  • 多线程解决了多个客户端同时连接的情况

BIO

  • 过程
    • 阻塞
    • 来一个 new Thread()
    • Executors.newFixedThreadPool(60)
    • 标记连接,设置 flag,表示是否创建线程
  • 问题
    • 多个客户端连接,服务端的线程数量肯定会增加
  • 改良
    • 客户端连接上来的受 Socket
    • IO 操作的时候才去创建线程

NIO

  • Non-Blocking IO 同步非阻塞 IO
  • 引入 selector 的概念
  • channel 消息通道
  • selector 做一个等级,需要 IO 操作的时候,采取 new Thread()
阻塞,非阻塞
  • 阻塞
    • 单线程下,IO 没有完成,那么当前线程就会一直等待
  • 非阻塞
    • 不管你IO 操作有没有完成,我都会直接去返回,或者抛出一个异常了才取返回
优化
  • 引入缓存
    • 解决读操作,写操作的间断性!!!
  • buffer 是一个数组的形式
  • selector 要和 serverSocketChannel 进行绑定的

AIO

  • 异步
  • 我触发了 IO 操作以后,就不再关心 IO 操作委托给系统,系统完成以后,在告诉自己 OK

12 – Spring中对象注入的几种方式和区别

背景

  • Java 中一个类需要依赖其它类的时候,需要通过 new() 来进行实例化,不好管理,各个类之间的耦合度非常的高

Sping

  • 依赖注入
    • 就像对象的配方一样,定义好以后,直接就可以用
  • 注入方式
    • Set
    • 构造
    • 静态工厂的方法注入
  • 目前
    • 用注解的方式比较多,可能就没又考虑到 构造注入 的方式了
  • XML
    • Set 注入
      • ref 引用
    • 构造
    • 静态工厂的方法注入
  • 使用
    • 生命周琪
    • 定义的 Bean 类似于模板
    • bean 的名称可以通过 ref 引用过来
    • 可以配置对相对应关系
    • 模式
      • 原型模式 Prototype
      • 单例模式 Singleton

13 – Mysql数据库的隔离级别及其区别

事务

  • 定义

    • 事务是应用程序中对数据库的一系列严谨的操作,所有操作必须成功过着失败。
  • 特性 ACID

    • A

      • 原子性

        要不全部成功,要不全部失败

    • C

      • 一致性

        一致性状态变成另一个一致性状态,不会出现不一致的情况

    • I

      • 隔离性

        并行事务之间合理不能相互干扰

    • D

      • 持续性

        事务一旦提交,那么它对数据库的修改是永久性的

数据库并发

  • 问题
    • 脏读
      • 事务A 读了 事务 B 未提交的数据
    • 不可重复度
      • A多次读取同一条数据,B 对这条数据进行了修改,提交了,A 独到的不一样
    • 幻读
      • A重复读取一条数据,B 中间对着条数据进行了新增或者删除
  • 重点
    • 不可重复度侧重的是修改
    • 幻读侧重的是新增和删除

隔离级别

  • 隔离我们事务之间的影响

  • 种类

    • read uncommitted
      • 读取未提交的数据(脏读,很少使用)
    • read committed
      • 读取已提交的数据(解决了脏读,出现不可重复度)
    • repeateable read (默认的)
      • 并发的时候,看到相同的数据行,就会出现幻读,一般通过 innoDB 里边的共享锁和独占锁,还可以通过多版本并发控制的 MVCC 的机制
    • serializable
      • 严格按照顺序,执行,一个事务执行,另一个事务只能挂起

18 – HashMap源码面试题分析

数据结构

  • key,value
  • 数组 + 单项列表

HashMap

  • Hash 算法的作用

    • 目的: 为了 Node 节点的一个下标的计算

    • 要求

      • 根据 hash() 算法得到一个整形数
      • 控制在 0 ~ 15 之间
  • 默认大小

    • DEFAULT_INITIAL_CAPACITY = 1 << 4
  • Hash 算法

    • 右移 16 位
    • 互相 异或 得到一个 hash 值

put 的过程

  1. 先 HASH 算法

  2. 初始化数组大小,

    判断当前的 Node 节点的数组是否为 null

    空 -> 数组的默认大小 16 赋值给 newCap

  3. 根据 key, value 组装成 Node 节点,计算出下标的位置

    1. 和 &(n-1)取余
    2. 数组大小 16 , 对 15 &
  4. 得到下标位置以后

    1. key 值相同,只需要 value 值进行覆盖就行了
    2. 红黑树(JDK 1.8 开始引入红黑树)
    3. 链表的形式,循环遍历,next 为 null,把这个节点放进去
判断数组的容量大小的改变
  • 扩容

  • 扩容因子(负载因子)

  • 两倍扩容

  • 数组的最大长度

    • MAXIMUM_CAPACITY = 1 << 30;
    • 2^30^
  • 扩容以后

    数据迁移

    1. 数组有元素,下面为 null
    2. 数组位置有元素,下面不为 null, 红黑树
    3. 重新拆分,重新打散
    4. 数组有元素,下面不为 null,链表

19 – 如何停止一个线程

如何停止

  • stop()

    • 强制停止,不建议适用
  • 正常

    • interrupt

      • 打个招呼,你这个线程中断执行了

        停止不停止,线程自己去判断

    • stop

      • 标志

20 – Thread.join的实现原理

怎么启动

  • 普通写法

    • 1
      2
      3
      
      thread1.start()
      thread2.start()
      thread3.start()
      
  • start() 方法,只是告诉 JVM 可以去驱动一个线程了,但是什么时候执行,取决于 OS 的调度线程。

join

  • 1
    2
    3
    4
    5
    6
    
    thread1.start();
    thread1.join();
    thread2.start();
    thread2.join();
    thread3.start();
    thread3.join();
    
  • join() 方式是去等待线程的执行结果。

  • join 方法就是主要用来阻塞一个线程,拿到一个线程的执行结果,或者等待

  • wait(0);

    是让主线程去等待

21 – ThreadLocal的实现原理

场景

  • 多线程对同一个变量做一个递增的时候
    • threadLocal
    • 提供了线程的局部变量
  • 访问 ThreadLocal 里的某一个变量的值的时候,都会拥有一个线程内局部变量的副本,
  • 利用 ThreadLocal 使得多线程之间不互相影响

Java 传递

  • 值传递
  • 引用传递

ThreadLocal 对每个线程来说都是一个独立的副本。对于每一个线程来说,拥有的副本都是独立的。

22 – 由浅入深掌握volatile关键字的原理

volatile

  • 内存可见性问题
  • 内存屏障

原理

  • 加了 volatile 一个线程修改的值,会影响到子线程的流程 子线程就会拿到一个最新的值,去判断。
  • 是通过内存屏障和编译器屏障完成这个编译器
  • java 层面四种内存屏障
    • loadload
    • storestore
    • loadstore
    • storeload
  • 内存屏障
    • 就是两个操作之间,读操作或写操作之间,加入响应的屏障,定义了处理的同步点,同步点之前的指令必须全部执行完
  • lock
    • CPU 级别的 汇编指令
    • 作用
      • 使 CPU 的缓存行的数据写回到主内存里
      • 使其它 CPU 核心里边该数据的内存无效
  • volatile
    • 通过内存屏障达到我们可见性的效果
面试回答
  • volatile 是去保证可见性的
  • 可见性表现在不同线程直接按
  • 这个值表更以后,对其它线程的可见性,
  • 通过内存屏障实现
  • 防止指令重排序

23 – 线程池的原理分析

背景

  • 数据库连接池作用
    • 避免了频繁的创建线程来造成极大的开销
    • 通过控制线程池达到限流的目的
    • 当线程池的数量超过我们限制的数量的时候,会抛出拒绝的异常。

线程池的种类

  • Executors.newSingleThreadExecutor()
    • 构建只有一个线程的线程池
  • newFixedThreadPool(1)
    • 返回一个固定线程数量的线程池
  • newcachedThreadPool()
    • 返回一个不限制大小的线程池
    • 但是创建的这些线程,都有一个回收机制,空闲 60 s后,会自动被回收
  • newSinglethreadScheduledExecutor()
    • 是可以延时执行的,我们可以通过它做一些定时调度

线程池顶层接口 ThreadPoolExecutor

  • int corePoolSize
    • 核心线程数
  • int maxiumPoolSize
    • 最大线程数
  • long keepAliveTime
    • 非核心线程数空闲时的活跃时间
  • TimeUnit unit
    • 存活的单位
  • BlockingQueue<Runable> workQueue
    • 阻塞队列
  • ThreadFacotry threadFactory
    • 线程池中构建线程的工厂
  • RegectedExecutionHandler handler
    • 一个拒绝策略

线程池执行的步骤

  1. 调用 execute() 去构建核心线程数、最大线程数

  2. 阻塞队列有阻塞策略,主线程调用 execute() 的时候,如果线程池中还不到和核心线程的个数,就会创建一个线程
  3. 如果核心线程数超过了定义的核心线程数 就会放到阻塞队列中,不断地去除数据去运行

  4. 队列添加失败,就是队列满了,然后就会创建非核心线程,如果线程满了,就会走拒绝策略
  5. 核心线程和非核心线程的区别

线程怎么运行的呢?

  • 是由 worker 的方式去定义线程的。

  • 不是直接运行这个线程,而是调用这个线程的 run() 方法。
  • ThreadLocalPool 里面构建一个 worker,调用我们的 task,调用里边的 Thread

24 – 缓存穿透的原理以及解决方案

Redis 做缓存

  • 单线程模型
  • 如果保证 redis 和 mysql 的一致性
  • 缓存雪崩
  • 缓存击穿

场景

  • 如果缓存数据很少的话,就会由很多请求都落到数据库层面
  • 数据库 IO 和访问量就会很大,导致数据库宕机
  • 查询数据库中都没有的数据,倒是不命中,叫做缓存穿透

解决方案

  • 先查询数据库为空,然后,设置一些规律,传过来 value

    value -> && __ 告诉程序这个数据不存在

    过段时间在查询数据库

布隆过滤器

  • 原理

    • 概率性算法和数据结构
  • 使用

    • 判断一个元素在集合中是否存在
  • 位图算法

    • 位图j就是一个 int 型的数据,

    • 一个整形可以存储 32 个数据,代表一个标志,

      hash 以后,取模,然后落上去,

      如果 这个数 hash 一下,落上去 1, 就可能存在, 0 就是不可能存在

25 – 什么是幂等-如何实现

什么是幂等,如何实现

  • 系统之间相互调用三种状态
    • 成功
    • 失败
    • 未知

问题

  • 在未知的场景下,服务端已经收到了请求,但是客户端不知道,

    他再次请求,导致了数据做了两次累加

幂等的实现

  • 数据库唯一索引
    • 收到请求,添加一条记录,
    • 如果第二次过来,没办法入库
  • 状态机幂等
    • 状态流转有固定的流程
    • 一定是一个状态才能改变成下一个状态

幂等的使用

  • 不管你客户端调用多少次,我跟第一次的处理结果都是一样的,这就是幂等
  • 消息中间件必须实现 幂等。 因为 消息中间件无法保消息只发送一次,怎么保证数据安全,需要幂等。

26 – 数据库和缓存双写如何保证数据一致性

场景

  • 需要根据不同的场景,和对数据一致性的容忍度去做不同的权衡,

    所以要提供相应的思路

  • 转钱

选择

  • 先更新缓存中的数据,再更新数据库
    • 数据库更新失败怎么办?
  • 修改数据库中的数据以后,在更新缓存中的数据
    • 数据的脏读
两个要点
  • 先更新缓存,还是让缓存失效
  • 先操作数据库,还是先操作数据库

面试正确回答

  • 更新缓存,表示数据不但写入到数据库,还写入到缓存,
  • 缓存失效表示,更新数据库,删除缓存中的key
  • 更新缓存代价小,先更新缓存
  • 如果更新缓存代价大,就先让缓存失效
  • 我们根据具体的业务,选择具体方案

最终一致性

  • 缓存中间件保证最终一致性
两点
  • 一个是更新缓存的代价大小
  • 一个是对业务的容忍程度