【JAVA知识点小结】

JAVA开发知识点汇总

  • 限流算法
    • 固定窗口
    • 滑动窗口
    • 漏桶算法
    • 中间件限流
    • 令牌桶算法
    • 网关限流
  • 常见的web安全问题以及应对方式
  • 消息队列
    • kafka
    • RocketMQ
  • 设计模式
    • 策略模式
    • 模板模式
    • 原型模式
    • 单例模式
    • 工厂模式
  • JAVA
    • 数据结构与算法
      • hashcode和equals
    • 泛型
    • JVM
      • 调优辅助工具
        • jProfiler
        • Arthas
      • 垃圾标记算法
        • 三色标记法
        • 可达性分析法
        • 引用计数法
      • 可以作为GCroots的对象有哪些?
      • GC的种类和触发机制
        • 年轻代触发机制(Minor GC)
        • 老年代触发机制(Major GC/Full GC)
        • Full GC 触发机制
      • 为什么需要把Java堆分代?
      • 为什么新生代中Eden:S1:S2 = 8:1:1?
      • 垃圾回收器如何选择?
      • 导出JVM内存快照
      • JVM参数和调优
        • jvm调优的本质?
        • 需要垃圾回收的上线表现?
        • JVM调优原则
        • JVM运行参数设置
    • 并发编程
      • yield,join,wait,sleep的区别
      • jdk源码中线程的状态
    • 线程池
        • CAS
        • 锁升级与锁降级
        • 锁粗化(锁膨胀)
        • 可重入锁
        • 公平锁
        • 读锁与写锁
        • 共享锁和排他锁的区别?
      • 线程终止的优雅方式:两阶段终止
  • 数据库
    • MySQL
      • 索引
      • binlog、redolog和undolog
        • redolog
        • binlog
        • undolog
      • MySQL的MVCC机制是什么和怎么实现的?
        • 当前读
        • 快照读 (一致非锁定读)
      • 数据库的两阶段提交是什么?
      • 百万数据如何做分页查询
    • Redis。
      • redis分布式锁
      • redis持久化
      • redis主从
      • redis集群
      • redis哨兵
      • redis缓存注意项
        • 缓存穿透
        • 缓存击穿
        • 缓存雪崩
        • 缓存预热
        • 缓存降级
      • redis 事务
        • redis事务的特点
        • redis事务的命令
      • redis的基本数据类型和高级数据类型
        • String
        • List
        • Set集合
        • hash
        • Zset
        • HyperLogLogs(基数统计)
        • Bitmap (位存储)
        • geospatial (地理位置)
        • redis查看key
      • redis缓存淘汰策略
      • redis缓存和数据库双写一致性
      • redis和Memcached的区别
        • Spring整合memcache
  • 零拷贝。
    • 一般的拷贝流程以及零拷贝的定义
    • Kafka中的零拷贝
  • Spring
    • OAuth2.0
      • jwt
      • token
      • jwt和token的区别
    • 事务.
      • Spring中事务的传播机制。
      • Spring中事务失效的场景
    • 全局异常处理
    • bean的作用域
    • 注解
      • 自定义注解
        • 字段注解
        • 方法、类注解
          • 权限注解
          • 缓存注解
      • 后端API校验注解
      • lombok相关注解
      • bean生命周期中的注解
      • 加载配置文件的注解
      • bean加载顺序控制
  • 其他

限流算法

固定窗口

滑动窗口

漏桶算法

中间件限流

令牌桶算法

网关限流

https://www.jb51.net/article/284213.htm

常见的web安全问题以及应对方式

SQL注入

XSS

CSRF

url跳转漏洞

点击劫持

OS命令注入攻击

https://blog.51cto.com/zhangchiworkos/5162973

消息队列

kafka

RocketMQ

kafka和RocketMQ的总体区别是,kafka设计初衷是用于日志传输,而RocketMQ的设计用于解决各类应用可靠的消息传输,阿里云官网承诺RocketMQ数据可靠性为10个9,服务可靠性为99.95%。

kafka相比RocketMQ的优势
1、单机吞吐量TPS可上百万,远高于RocketMQ的TPS7万每秒,适用于日志类消息。
2、kafka支持多语言的客户端

RocketMQ相比kafka的优势
1、保证消息不丢( 数据可靠性达10个9)
2、可严格保证消息有序
3、支持分布式事务消息
4、支持按时间做消息回溯(可精确到毫秒级)
5、支持按标识和内容查询消息,用于排查丢消息
6、支持消费失败重试
7、可支持更多的partition, 即更多的消费线程数

https://blog.csdn.net/shijinghan1126/article/details/104724407

设计模式

策略模式

模板模式

原型模式

单例模式

工厂模式

http://www.rply.cn/news/112108.html

JAVA

数据结构与算法

求前K小。

//求给定数组的前K小,使用大根堆
public static ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
        ArrayList<Integer> res = new ArrayList<Integer>();
        // 排除特殊情况
        if (k == 0 || input.length == 0) return res;
        // 大根堆
        PriorityQueue<Integer> q = new PriorityQueue<>((o1, o2) -> o2.compareTo(o1));
        // 创建一个k个大小的堆
        for (int i = 0; i < k; i++) {
            q.add(input[i]);
        }

        //调整堆,因为是求前K小所以仅需要调整元素个数为K个的堆,从第K+1位开始依次从堆中过滤,小的留下
        for (int i = k; i < input.length; i++) {
            if (q.peek() > input[i]) {
                q.poll();
                q.add(input[i]);
            }
        }
        // 堆中元素取出前K个元素存入数组
        for (int i = 0; i < k; i++) {
            res.add(q.poll());
        }
        return res;

    }

    public static void main(String[] args) {
        System.out.println("GetLeastNumbers_Solution(new int[]{4,5,1,6,2,7,3,8},4) = " + GetLeastNumbers_Solution(new int[]{4, 5, 1, 6, 2, 7, 3, 8}, 4));
    }

hashcode和equals

两者在对象比较中应该满足什么样的关系?

1、equals相等的两个对象,hashCode一定相等;

2、hashCode不相等,一定能推出equals也不相等;

3、hashCode相等,equals可能相等,也可能不等。

为什么要同事重写hashcode和equals方法?

1、如果仅重写hashcode方法,这样会导致两个对象具有相应通的属性需要判定为相等时,被判定为FALSE,因为此时equals底层还是用==比较的两个对象的地址,导致与预期不符。

2、如果仅重写equals方法,在单纯的使用两个对象比较也许结果是符合预期的,但是如果使用hash类的集合存储对象时可能会发不符合预期的逻辑错误或者内存泄漏(hash类集合的比较合适存储都是依赖对象的hashcode做判断的)。

3、因此必须同事重写hashcode和euqlas方法

泛型

https://blog.csdn.net/qq_43719791/article/details/117528809

JVM

【JAVA知识点小结】_第1张图片

调优辅助工具

jProfiler

Arthas

垃圾标记算法

三色标记法

主流的垃圾收集器基本上都是基于可达性分析算法来判定对象是否存活的。根据对象是否被垃圾收集器扫描过而用白、灰、黑三种颜色来标记对象的状态的一种方法。而其中

白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始阶段,所有的对象都是白色的,若在分析结束之后对象仍然为白色,则表示这些对象为不可达对象,对这些对象进行回收。
灰色:表示对象已经被垃圾收集器访问过,但是这个对象至少存在一个引用(属性)还
没有被扫描过

黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经被扫描过。黑色表示这个对象扫描之后依然存活,是可达性对象,如果有其他对象引用指向了黑色对象,无须重新扫描,黑色对象不可能不经过灰色对象直接指向某个白色对象

缺点:实际上在并发的情况下会存在多标和漏标的问题。
参考: https://blog.csdn.net/weixin_39555954/article/details/127623284

可达性分析法

通过一系列称为GC Roots 的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的时候,则证明此对象是不可用的。
【JAVA知识点小结】_第2张图片

引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时候计数器为0时的对象就是不能再被使用。
【JAVA知识点小结】_第3张图片
【JAVA知识点小结】_第4张图片

缺点是可能存在循环引用的问题

可以作为GCroots的对象有哪些?

1、虚拟机栈中引用的对象。比如:各个线程被调用的方法中使用到的参数、局部变量等。
2、本地方法栈内JNI(通常说的本地方法)引用的对象。
3、方法区中类静态属性引用的对象。比如:Java类的引用类型静态变量
4、方法区中常量引用的对象。比如:字符串常量池(string Table) 里的引用
5、所有被同步锁synchronized持有的对象
6、Java虚拟机内部的引用。基本数据类型对应的Class对象,一些常驻的异常对象(如:NullPointerException、OutOfMemoryError) ,系统类加载器。
7、反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
8、除了这些固定的GCRoots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整GC Roots集合。比如:分代收集和局部回收(Partial GC)。

GC的种类和触发机制

GC英文全称为Garbage Collection
JVM常见的GC包括三种:Minor GC,Major GC与Full GC
针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大种类型:
a、一种是部分收集(Partial GC)
b、一种是整堆收集(Full GC)
部分收集(Partial GC):不是完整收集整个Java堆的垃圾收集,其中又分为:
a、新生代收集(Minor GC/Young GC):只是新生代的垃圾收集
b、老年代收集(Major GC/Old GC):只是老年代的垃圾收集
c、混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集,目前,只有G1 GC会有这种行为
整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集

注意:JVM在进行GC的时候,并非每次都对所有区域都进行垃圾回收,大部分时候回收的都是指新生代。

年轻代触发机制(Minor GC)

当年轻代空间不足时,就会触发Minor GC,这里的年轻代空间不足指的是Eden区满,Survivor区满不会触发GC(每次Minor GC 会清理年轻代的内存)。

因为JVM汇总大部分对象朝生夕死,MinorGC会发生的很频繁,回收也比较迅速。Minor
GC会引发STW,暂停其他用户线程,等垃圾回收结束,用户线程才恢复运行

老年代触发机制(Major GC/Full GC)

当老年代空间不足时,会尝试触发MajorGC ,如果之后空间依旧不足,就会触发MajorGC。

Full GC 触发机制

触发Full GC执行的情况有如下五种:
1、调用System.gc(),系统建议执行Full GC,但是不必然执行
2、老年代空间不足
3、方法区空间不足
4、通过Minor GC后进入老年代的平均大小大于老年代的可用内存
5、由Eden区,from区向to区复制时,对象大小大于to区可用内存,则把对象转存到老年代,并且老年代的可用内存小于该对象大小(如果Full GC后空间仍不足会抛出OOM异常)

Full GC也是开发和JVM调优时需要尽可能避免或者将减少的

为什么需要把Java堆分代?

不同对象的生命周期不同,而且百分之八九十的对象都是临时对象,分代回收就是为了百分之十左右的长时间存活的对象进行管理和减少回收次数,优化了内存和应用的效率。

为什么新生代中Eden:S1:S2 = 8:1:1?

发生垃圾回收时,会将Eden区和Survivor from中还存活的对象一次性复制到另一块Survivor区域(复制算法),然后就清空调Eden区和Survivor from区中的数据。这样新生代中可用的内存:复制算法所需要的担保内存 = 9:1,这样即使所有的对象都不会存活,那么也只会“浪费”10%的内存空间。不过我们也无法保证存活的对象一定<2%或10%,所以当新生代中Survivor to区内存不够用时,就会触发老年代的担保机制进行分配担保,进行majorGC 或者 full GC。

垃圾回收器如何选择?

1、优先调整堆的大小让JVM自适应完成。
2、如果内存小于100M,使用串行收集器
3、如果是单核、单机程序,并且没有停顿时间的要求,串行收集器
4、如果是多CPU、需要高吞吐量、允许停顿时间超过1秒,选择并行或者JVM自己选择
5、如果是多CPU、追求低停顿时间,需快速响应(比如延迟不能超过1秒,如互联网应用),使用并发收集器
官方推荐G1
,性能高。
现在互联网的项目,基本都是使用G1。可以使用-XX:+PrintCommandLineFlags查看默认的垃圾回收器。

截止JDK1.8,一共有7款不同的垃圾收集器。每一款的垃圾收集器都有不同的特点,在具体使用的时候,需要根据具体的情况选用不同的垃圾收集器。
【JAVA知识点小结】_第5张图片

GC发展阶段:Serial => Parallel(并行)=> CMS(并发)=> G1 => ZGC

参考
https://blog.csdn.net/weixin_45525272/article/details/126370223
https://blog.csdn.net/sd_960614/article/details/126900380

导出JVM内存快照

首先,使用 jps 命令查看已启动的进程和对应的PID;jps
然后使用 jmap命令和查出的线程ID导出堆内存信息;jmap -dump:format=b,file=heap.bin [jpd_id]
然后可以使用MAT或者JProfiler性能分析工具分析JVM快照。

JVM参数和调优

jvm调优的本质?

回收垃圾,及时回收没有用的垃圾对象,及时释放掉内存空间。

需要垃圾回收的上线表现?

1、垃圾对象太多(对象沾满内存),内存不足,程序性能降低
2、垃圾回收线程过多,频繁回收垃圾,导致程序性能降低。
3、垃圾回收频繁导致STW
4、Heap内存(老年代)持续上涨达到设置的最大内存值;
5、Full GC 次数频繁;
6、GC 停顿时间过长(超过1秒);
7、应用出现OutOfMemory 等内存异常;

JVM调优原则

JVM调优是一个手段,但并不一定所有问题都可以通过JVM进行调优解决,因此,在进行JVM调优时,我们要遵循一些原则:

1、大多数的Java应用不需要进行JVM优化;
2、大多数导致GC问题的原因是代码层面的问题导致的(代码层面);
3、上线之前,应先考虑将机器的JVM参数设置到最优;
4、减少创建对象的数量(代码层面);
5、减少使用全局变量和大对象(代码层面);
6、优先架构调优和代码调优,JVM优化是不得已的手段(代码、架构层面);
7、分析GC情况优化代码比优化JVM参数更好(代码层面);
通过以上原则,我们发现,其实最有效的优化手段是架构和代码层面的优化,而JVM优化则是最后不得已的手段,也可以说是对服务器配置的最后一次“压榨”。

JVM运行参数设置

-XX:+PrintGC 使用这个参数,虚拟机启动后,只要遇到GC就会打印日志
-XX:+PrintGCDetails 可以查看详细信息,包括各个区的情况
-Xms 设置Java程序启动时初始化堆大小
-Xmx 设置Java程序能获得最大的堆大小
-Xmx20m -Xms5m -XX:+PrintCommandLineFlags 可以将隐式或者显示传给虚拟机的参数输出
-Xmn 可以设置新生代的大小,设置一个比较大的新生代会减少老年代的大小,这个参数对系统性能以及GC行为有很大的影响,新生代大小一般会设置整个堆空间的1/3到1/4左右
-XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例。含义:-XX:SurvivorRatio=eden/from**/eden/to
HeapDumpOnOutOfMemoryError,使用该参数可以在内存溢出时导出整个堆信息,与之配合使用的还有参数-XX:HeapDumpPath
,可以设置导出堆的存放路径(结合内存分析工具:Memory Analyzer或者JProfiler)
栈参数配置 Java虚拟机提供了参数-Xss来指定线程的最大栈空间,整个参数也直接决定了函数可调用的最大深度
虚拟机提供了一个参数来控制新生代对象的最大年龄,当超过这个年龄范围就会晋升老年代-XX:MaxTenuringThreshold,默认情况下为15。

以8C32G的服务器为例,给出以下JVM参数模板(使用的cms垃圾回收器,使用时根据需要请修改)

-XX:+PrintGC
-XX:+PrintGCDetails
-Xms2048m
-Xmx2048m   //xms和xmx保持一直可以避免每次垃圾回收完成后JVM重新分配内存。
-XX:+PrintCommandLineFlags
-Xmn1024m     //整个堆大小=年轻代大小+老年代大小+持久代(默认64m)
-XX:SurvivorRatio=8:1:1
-XX:+UseConcMarkSweepGC
-XX:HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=D:/JVM/demo.dump
//晋升至老年代的年龄限制
-XX:MaxTenuringThreshold=15
//调整大对象标准,超过的直接分配到老年代,默认单位是字节
-XX:PretenureSizeThreshold=1000000

-Xms 默认情况下堆内存的64分之一

-Xmx 默认情况下对内存的4分之一

-Xmn 默认情况下堆内存的64分之一

-XX:NewRatio 默认为2

-XX:SurvivorRatio 默认为8

参考
https://baijiahao.baidu.com/s?id=1747377064336158073&wfr=spider&for=pc
https://blog.csdn.net/huanglu0314/article/details/127280874
https://blog.csdn.net/daguanjia11/article/details/120655483
https://blog.csdn.net/daguanjia11/article/details/120655483
https://zhuanlan.zhihu.com/p/437612041

并发编程

yield,join,wait,sleep的区别

wait
是继承自Object的方法,当前线程调用wait方法,是在告诉别的线程,我需要等待了,既会释放cpu,也会释放共享资源的锁,进入挂起状态。wait必须在synchronized代码块内部执行,因为wait需要获得共享资源的锁并且释放锁。需要notify/notifyAll来唤醒。
join
Thread的非静态方法。底层用了wait方法。假如现在有两个线程,main线程和t线程.main线程里调用t.join.那么这时,t会取得线程对象t的锁,然后main线程 wait,释放cpu,t线程执行,直到t线程执行完后,main线程继续执行。
sleep
是Thread的静态方法,会使当前线程释放cpu,但不会释放锁资源,可以理解为只和cpu有关,不涉及锁资源。涉及锁资源的,是wait,join方法。
yield
也是Thread的静态方法,和sleep方法类似,会使当前线程释放cpu,但不会释放锁资源。和sleep不同的是,sleep必须设置时间,但是yield不能设置时间,时间值是随机的

综上:sleep方法和yield方法都是Thread的静态方法,都会释放cpu但是不会释放锁资源;wait和join方法会释放cpu和共享资源的锁,不同的是wait的线程需要手动唤醒,join导致的线程阻塞不用显示唤醒。

jdk源码中线程的状态

Java中线程的状态分为六种
NEW:初始状态,线程被创建,但是还没有调用start()方法。
RUNNABLE:运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称作“运行中”。
BLOCKED:阻塞状态,表示线程阻塞于锁。
WAITING:等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
TIMED_WAITING:超时等待状态,该状态不同于 WAITING,它是可以在指定的时间自行返回的。
TERMINATED:终止状态,表示当前线程已经执行完毕。

【JAVA知识点小结】_第6张图片

线程池

线程池的创建方式总共有以下 7 种:
Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。
Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。
Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。
Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。

线程池的构造函数有7个参数,分别是corePoolSizemaximumPoolSizekeepAliveTimeunitworkQueuethreadFactoryhandler

【JAVA知识点小结】_第7张图片

线程池创建后会存储一定数量的线程和corePoolSize相等,有任务进入线程池时,首先是核心线程数进行工作如果所有核心线程数都在工作,多余的工作任务就排入workQUeue中,指导工作队列排满之后,创建新的工作线程直到maximumPoolSize,如果任务还继续增加且工作队列已经满了,就会执行相应的hadler拒绝策略。指导所有任务都处理完成后,如果线程池中的线程还多余核心线程数且线程空闲时间超过了keepAliveTime就会呗kill,直到线程个数减少至核心线程的数目。

线程池的拒绝策略有四种,也可以通过继承RejectedExecutionHandler接口,重写rejectedExecution方法实现自己的拒绝策略。
1、AbortPolicy
第一种拒绝策略是 AbortPolicy,这种拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException的RuntimeException
2、DiscardPolicy
第2种拒绝策略是 DiscardPolicy,这种拒绝策略正如它的名字所描述的一样,当新任务被提交后直接被丢弃掉,也不会给你任何的通知
3、DiscardOldestPolicy
第3种拒绝策略是 DiscardOldestPolicy,如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务
4、CallerRunsPolicy
第4种拒绝策略是 CallerRunsPolicy,相对而言它就比较完善了,当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务

综上,线程池在两种情况下会拒绝新任务:调用用了线程池的shudown方法和线程池的工作任务已经很饱和 的时候。

CAS

CAS锁是一种轻量级锁。

CAS 的三个缺点,分别是 ABA 问题(用referrence引用解决)、自旋时间过长以及线程安全的范围不能灵活控制。

锁升级与锁降级

synchronized会经历四个阶段:无锁状态、偏向锁、轻量级锁、重量级锁依次从耗费资源最少,性能最高,到耗费资源多,性能最差。
1、对于Synchronized的锁升级过程是无锁到偏向锁,偏向锁是对象头中的Marword中的线程ID存储为当前线程ID;
2、查看对象头中的MarkWord里的Lock Record指针指向的是否是当前线程的虚拟机栈,如果是,拿锁执行业务(轻量级锁);如果不是则进行CAS,尝试修改,若是修改几次都没有成功,再升级到重量级锁。
3、查看对象头中的MarkWord里的指向的ObjectMonitor,查看owner是否是当前线程,如果不是,扔到ObjectMonitor里的EntryList中排队,并挂起线程,等待被唤醒(重量级锁)。

一般情况下,新new出来的一个对象,暂时就是无锁状态。
https://www.jb51.net/article/281099.htm

对于读锁与写锁的转化来说:
锁降级: 获取写锁,获取读锁,释放之前获取的写锁。这时,写锁降级为读锁。
锁升级: 获取读锁,获取写锁,释放之前获取的读锁。这时,读锁升级为写锁。

https://blog.csdn.net/qq_55660421/article/details/123828778

锁粗化(锁膨胀)

锁膨胀是编译Java文件的时候,JIT帮我们做的优化,它会减少锁的获取和释放次数。

锁消除则是在一个加锁的同步代码块中,没有任何共享资源,也不存在锁竞争的情况,JIT编译时,就直接将锁的指令优化掉。

可重入锁

synchronized和ReentrantLock都是可重入锁。

可重入锁最多可以冲入的次数2^31-1次
https://blog.csdn.net/zjp_01/article/details/127324509

公平锁

Syhcronized是非公平锁,ReentrantLock可以使公平锁也可以是非公平锁。

ReentrantLock reentrantLock = new ReentrantLock();	//默认是非公平锁
ReentrantLock reentrantLock = new ReentrantLock(true);  //传入参数true就是公平锁

读锁与写锁

读锁是支持重进入的共享锁。

共享锁和排他锁的区别?

共享锁又叫S锁,拍他锁又叫X锁。
【JAVA知识点小结】_第8张图片

线程终止的优雅方式:两阶段终止

先看一下基础:
interrupt()的作用:
1、给正常运行的线程打上interrupted的状态
2、唤醒处于sleep、wait和join这三个轻量级阻塞状态的线程,抛出InterruptedException异常。
3、需要注意:在以上第二点的情况下,interrupt抛出异常后,线程的打断标记会重置为false。
isInterrupted()和interrupted()的区别:
前者是非静态函数只适用于读取中断状态,不修改状态;后者返回读取的中断状态,还会重置中断标志位。

参考:https://blog.csdn.net/qq_57421630/article/details/130095865
两阶段终止的实现
实现其实还是以上提到的interrupt、interrupted和isInterrupted的特性:interrupt对运行中的线程仅仅设置中断状态并不中断线程,对轻量级阻塞状态的线程会发出InterruptedException异常,然后重置中断状态;interrupted会直接重置中断状态;isInterrupted直接读取中断状态不改变中断状态。

public class TestTwoStageTermination {
    public static void main(String[] args) throws InterruptedException {
        TwoStageTermination t1 = new TwoStageTermination();
        t1.start();
        Thread.sleep(3000);
        t1.stop();
    }
}

class TwoStageTermination {
    private Thread monitor;

    // 启动线程
    public void start() {
        monitor = new Thread(() -> {
            while (true) {
                Thread currentThread = Thread.currentThread();
                // 根据打断标记,退出循环,线程结束
                if (currentThread.isInterrupted()) {   // isInterrupted() 打断标记的状态
                    System.out.println("打断标记:true, 线程退出!");
                    break;
                }
                try {
                    Thread.sleep(1000);  // 情况一:睡眠中打断,抛出InterruptedException异常,唤醒线程,清除打断标记:false,需要手动重置打断标记为true
                    System.out.println("线程运行中···"); // 情况二:线程正常运行,打断后,线程不会自动停止,打断标记置为:true,用打断标记写if判断
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    currentThread.interrupt(); // 再次打断:重置打断标记为true,使得循环退出
                }
            }
        });

        monitor.start();
    }

    // 打断线程
    public void stop() {
        monitor.interrupt();   // interrupt() 打断线程
    }
}

两阶段终止模式是一种并发设计模式,它用于优雅地终止线程。它将终止过程分成两个阶段,第一阶段由线程T1向线程T2发送终止指令,第二阶段是由线程T2响应终止指令。这种模式通过将停止线程这个动作分解为准备阶段和执行阶段这两个阶段,提供了一种通用的用于优雅地停止线程的方法!(优雅地终止线程可以确保线程在终止之前完成它应该完成的任务并执行一些收尾工作,从而保证数据和业务的安全性。如果随意终止线程,可能会导致数据丢失或损坏,或者导致程序运行不稳定。)

数据库

MySQL

索引

binlog、redolog和undolog

【JAVA知识点小结】_第9张图片

redolog

redo log(重做日志)是InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力。当MySQL实例宕机了,重启时。InnoDB存储引擎会使用redi log恢复数据,保证数据的持久性和完整性。

MySQL 中数据是以页为单位,你查询一条记录,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到 Buffer Pool 中。后续的查询都是先从 Buffer Pool 中找,没有命中再去硬盘加载,减少硬盘 IO 开销,提升性能。更新表数据的时候,也是如此,发现 Buffer Pool 里存在要更新的数据,就直接在 Buffer Pool 里更新。然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(redo log buffer)里,接着刷盘到 redo log 文件里。
【JAVA知识点小结】_第10张图片

binlog

redo log 它是物理日志,记录内容是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎。
而 binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server 层。
不管用什么存储引擎,只要发生了表数据更新,都会产生 binlog 日志。

Binlog主要用于数据备份、主备、主主、主从的数据同步,保证数据库的数据一致性。bilog惠济路所有设计更新数据的逻辑操作,并且是顺序写。
binlog 日志有三种格式,可以通过binlog_format参数指定。statement、row、mixed。
binlog的写入时机也非常简单,事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。

【JAVA知识点小结】_第11张图片

undolog

在MySQL中恢复机制是通过回滚日志(undo log)实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。

总结
MySQL InnoDB 引擎使用 redo log(重做日志) 保证事务的持久性
MySQL依赖这个 undo log(回滚日志) 来保证事务的原子性
MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性
个人理解顺便提一下事务的隔离性也正是依赖mvcc的机制实现的(记录的隐藏字段、undolog、ReadView)。

参考:http://wed.xjx100.cn/news/54595.html?action=onClick

MySQL的MVCC机制是什么和怎么实现的?

MVCC(Multi-Version Concurrency Control) 英文是多版本并发控制。在MySQL的Innodb引擎中存在的。MVCC只在READ COMMITTED和REPEATABLE READ这两个级别下工作。 也对应了MVCC的实现和隔离级别是有关联的:undolog + ReadView实现mvcc,ReadView的不通策略对应不同的事务隔离级别,ReadView复用的时候是可重复读隔离级别,ReadView的策略是每次读取创建新的版本的是RC读已提交级别。

MVCC 的实现依赖于:隐藏字段、Read View、undo log。
在内部实现中,InnoDB 通过数据行的 DB_TRX_ID事务IDRead View 来判断数据的可见性,如不可见,则通过数据行的 DB_ROLL_PTR回滚指针 找到 undo log 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 Read View 之前已经提交的修改和该事务本身做的修改。

当前读

当前读,顾名思义就是读取当前最新的数据,并且对读取的数据加锁,阻止其他事务同时修改相同的记录,避免出现安全问题。
下面这些场景会使用当前读:
update、delete、insert
select … lock in share mode (主动加共享锁)
select … for update (主动加排他锁)

快照读 (一致非锁定读)

一致读取意味着InnoDB使用多版本控制在某个时间点向查询提供数据库的快照,也可以简称为快照读。
相关说明:

InnoDB在隔离级别读已提交级别和可重复读级别上使用
一致读不会在访问的表上加任何锁,其他会话可以同时修改表上的数据。
读已提交级别下,每次select都会生成一个快照。
可重复读级别下,开启事务之后第一个select才会生成快照,而不是事务一开始就生成快照
快照读的实现方式通过多版本控制MVCC机制实现,

【JAVA知识点小结】_第12张图片

参考地址:https://blog.csdn.net/QuietThinking/article/details/117046744

数据库的两阶段提交是什么?

为了解决两份日志之间的逻辑一致问题,InnoDB存储引擎使用两阶段提交方案
理很简单,将redo log的写入拆成了两个步骤prepare和commit,这就是两阶段提交。
【JAVA知识点小结】_第13张图片
由上图可知redolog在事务执行过程中不断记录事务操作的变化,redolog日志有prepare和commit两种状态(用来保证binlog和redolog的一致性),事务操作完后曾并且binlog写入完后时redolog会从prepare状态转变为commit状态;
若在事务过程中发生系统故障时,数据库会根据redolog日志状态(此时也就是prepare状态)恢复到事务之前的状态(原子性);
若事务已经提交(也就是commit状态),但是数据未更新,数据库工会根据redolog日志完成还没有完成的事务操作(原子性)。

百万数据如何做分页查询

分页查询分为** 物理分页和逻辑分页**,物理分页就是冲马上有情况查询出来就是单独一页的结果,逻辑分页就是把相关的数据都查询出来,后端或者前端做分页展示。

方法一:

select * from product limit start,count;
这种方法也是Uzi长见最常使用的,在start起始位置小时还可以,数据量一旦到几十万几百万效率就很差了。

方法二:

覆盖索引
select * from product a join(select id from product limit start,count)b on a.ID = b.ID;
这种好像是减少读取仅buffer pool的垃圾数据,效率提升很多。

Redis。

redis分布式锁

redis实现另一种RedLock的分布式锁,它具有以下几个特性:
1、安全特性:互斥访问,即永远只有一个client可以拿到锁。
2、避免思索:最终client都可以拿到锁,不会出现死锁(即使原本获取锁的client挂掉了)。
3、容错性:只要大部分Redis及诶点存活就可以正常提供服务。

在Redis 中实现分布式锁,可以用下面的方法:

SET resource-name anystring NX EX lock-time

java中可以使用jedis set 命令加锁,即

jedis.set(keys,args,"NX","PX",3000)

1
该命令仅在密钥尚不存在时才设置密钥(NX选项),到期时间为3000毫秒(PX选项,EX选项单位为秒)

客户端执行以上的命令:

如果服务器返回 OK ,那么这个客户端获得锁。 如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。
设置的过期时间到达之后,锁将自动释放。

可以通过以下修改,让这个锁的实现更健壮:

不使用固定的字符串作为键的值,而是设置一个不可猜测(non-guessable)的长随机字符串,作为口令串(token)。 不使用 DEL
命令来释放锁,而是发送一个 Lua 脚本,这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除。

这两个改动可以防止持有过期锁的客户端误删现有锁的情况出现。
以下是一个简单的解锁脚本示例:

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end

https://www.modb.pro/db/12231

redis持久化

RDB 和AOF两种持久化方式。 实际使用一般是两种结合使用。
AOF默认不开启,修改redis配置文件中的appendonly 为yes即可开启AOF

AOF同步频率设置
1、appendfsync always.
始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好
2、appendfsync everysec
每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
3、appendfsync no
redis不主动进行同步,把同步时机交给操作系统。

如果对数据晚会智能星要求不那么严格们可以使用RDB持久化;反之可以用RDB。RDB持久化方式也可以配置持久化的条件,save或者bgsave前台或者后端异步进行等。

redis主从

redis集群

redis集群中主节点负责读写,从节点只有部分读取任务,主要做备份,在主节故障的时候可以切换为主节点。

redis哨兵

哨兵是一种检测redis集群中节点是否健康的一种机制,只要有一个哨兵节点发现有不健康redis节点所有哨兵就开始对该入redis节点进行测试,超过半数节点认为它不健康了,就触发主从切换;哨兵还用于redis集群的leader节点的选举,如果leader节点发生故障,哨兵就会通知各个节点开始选举新的leader,收到请求选举的节点会把票投给第一个 发送到请求的redis节点,获取票数最多的节点成为新的leader节点。

redis缓存注意项

缓存穿透

应对办法:布隆过滤器、

缓存击穿

应对办法:热点数据永不过期、缓存预热等

缓存雪崩

应对办法:
1、设置缓存随机过期时间
2、配置定时任务随机刷新缓存有效期

缓存预热

缓存降级

redis 事务

redis事务是一组有序的命令集合,所有命令都会按着顺序序列化的执行(业绩苏活在redis的事务执行过程中不会被客户端发送来的其他命令打断)。
redis事务的执行过程主要分为两个阶段:组队阶段和执行阶段。

  • 组队阶段:将所有的操作命令都放入队列中
  • 执行阶段:将放入队列的命令按着顺序执行(FIFO),执行郭晨更具有原子性。

redis事务的特点

单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

没有隔离级别的概念
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行

不保证原子性
事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

redis事务的命令

redis的事务是一种很弱的事务。

命令 作用 返回值
MULTI 显示开启事务 always OK
EXEC 原子性的执行事务(如果之前有watch key监视的key没有被更改就继续执行,否则就停止执行) 成功:返回数组 失败:返回NULL
DISCARD 清楚事务中的commands队列,恢复连接状态。如果有watch检视的key就释放监视的keys always OK
WATCH 将给出的key标记为检测状态,作为事务执行判断的一种条件(这种检测是CAS轻量级锁实现的,最多可以监视的key的个数为) always OK
UNWATCH 主动清楚事务中keys的监测状态,如果exec成功或者DISCARD就没必要执行了 always OK
# 监控key
watch key1
#开启事务
MULTI
# 提交执行操作的命令组
#取消事务
# disard
# 执行
exec

redis的基本数据类型和高级数据类型

String 、Hah、Set、Zset、List、bitmap类型。
【JAVA知识点小结】_第14张图片

String

【JAVA知识点小结】_第15张图片
使用场景:
缓存: 经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。

计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。

session:常见方案spring session + redis实现session共享

List

【JAVA知识点小结】_第16张图片
使用列表的技巧:
lpush+lpop=Stack(栈)
lpush+rpop=Queue(队列)
lpush+ltrim=Capped Collection(有限集合)
lpush+brpop=Message Queue(消息队列

使用场景:
TimeLine: 有人发布微博,用lpush加入时间轴,展示新的列表信息。(消息队列)

Set集合

【JAVA知识点小结】_第17张图片
说那个图中SMEMBER应该是SMEMBERS。(redis版本5.0

标签(tag),给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。

点赞,或点踩,收藏等,可以放到set中实现

hash

【JAVA知识点小结】_第18张图片
缓存: 能直观,相比string更节省空间,的维护缓存信息,如用户信息,视频信息等

Zset

127.0.0.1:6379> zadd myscoreset 100 hao 90 xiaohao
(integer) 2
127.0.0.1:6379> ZRANGE myscoreset 0 -1
1)
2)
127.0.0.1:6379> ZSCORE myscoreset hao
"100"

排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。

HyperLogLogs(基数统计)

什么是基数?

举个例子,A = {1, 2, 3, 4, 5}, B = {3, 5, 6, 7, 9};那么基数(不重复的元素)= 1, 2, 4, 6, 7, 9; (允许容错,即可以接受一定误差)

HyperLogLogs 基数统计用来解决什么问题?

这个结构可以非常省内存的去统计各种计数,比如注册 IP 数、每日访问 IP 数的页面实时UV、在线用户数,共同好友数等。

它的优势体现在哪?

一个大型的网站,每天 IP 比如有 100 万,粗算一个 IP 消耗 15 字节,那么 100 万个 IP 就是 15M。而 HyperLogLog 在 Redis 中每个键占用的内容都是 12K,理论存储近似接近 2^64 个值,不管存储的内容是什么,它一个基于基数估算的算法,只能比较准确的估算出基数,可以使用少量固定的内存去存储并识别集合中的的唯一元素。而且这个估算的基数并不一定准确,是一个带有 0.81% 标准错误的近似值(对于可以接受一定容错的业务场景,比如IP数统计,UV等,是可以忽略不计的)。

127.0.0.1:6379> pfadd key1 a b c d e f g h i # 创建第一组元素
(integer) 1
127.0.0.1:6379> pfcount key1 # 统计元素的基数数量
(integer) 9
127.0.0.1:6379> pfadd key2 c j k l m e g a # 创建第二组元素
(integer) 1
127.0.0.1:6379> pfcount key2
(integer) 8
127.0.0.1:6379> pfmerge key3 key1 key2 # 合并两组:key1 key2 -> key3 并集
OK
127.0.0.1:6379> pfcount key3
(integer) 13

Bitmap (位存储)

Bitmap 即位图数据结构,都是操作二进制位来进行记录,只有0 和 1 两个状态。

比如:统计用户信息,活跃,不活跃! 登录,未登录! 打卡,不打卡! 两个状态的,都可以使用 Bitmaps!
相关命令使用

使用bitmap 来记录 周一到周日的打卡! 周一:1 周二:0 周三:0 周四:1 …

127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0

查看某一天是否有打卡!

127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 5
(integer) 0

统计操作,统计 打卡的天数!

127.0.0.1:6379> bitcount sign # 统计这周的打卡记录,就可以看到是否有全勤!
(integer) 3

geospatial (地理位置)

Redis 的 Geo 在 Redis 3.2 版本就推出了! 这个功能可以推算地理位置的信息: 两地之间的距离, 方圆几里的人
geoadd添加地理位置

127.0.0.1:6379> geoadd china:city 118.76 32.04 manjing 112.55 37.86 taiyuan 123.43 41.80 shenyang
(integer) 3
127.0.0.1:6379> geoadd china:city 144.05 22.52 shengzhen 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 3

两级无法直接添加,我们一般会下载城市数据(这个网址可以查询 GEO:www.jsons.cn/lngcode)!

有效的经度从-180度到180度。

有效的纬度从-85.05112878度到85.05112878度。

#当坐标位置超出上述指定范围时,该命令将会返回一个错误。
127.0.0.1:6379> geoadd china:city 39.90 116.40 beijin
(error) ERR invalid longitude,latitude pair 39.900000,116.400000

geopos获取指定的成员的经度和纬度

127.0.0.1:6379> geopos china:city taiyuan manjing
1) 1) "112.54999905824661255"
1) "37.86000073876942196"
2) 1) "118.75999957323074341"
1) "32.03999960287850968"

geodist
如果不存在, 返回空

获得当前定位, 一定是一个坐标值!
单位如下:m
km
mi 英里
ft 英尺

127.0.0.1:6379> geodist china:city taiyuan shenyang m
"1026439.1070"
127.0.0.1:6379> geodist china:city taiyuan shenyang km
"1026.4391"

georadius
附近的人 ==> 获得所有附近的人的地址, 定位, 通过半径来查询
获得指定数量的人

127.0.0.1:6379> georadius china:city 110 30 1000 km 以 100,30 这个坐标为中心, 寻找半径为1000km的城市
1) "xian"
2) "hangzhou"
3) "manjing"
4) "taiyuan"
127.0.0.1:6379> georadius china:city 110 30 500 km
1) "xian"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist
1) 1) "xian"
2) "483.8340"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord withdist count 2
1) 1) "xian"
2) "483.8340"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
2) 1) "manjing"
2) "864.9816"
3) 1) "118.75999957323074341"
2) "32.03999960287850968"

参数 key 经度 纬度 半径 单位 [显示结果的经度和纬度] [显示结果的距离] [显示的结果的数量]

georadiusbymember
显示与指定成员一定半径范围内的其他成员

127.0.0.1:6379> georadiusbymember china:city taiyuan 1000 km
1) "manjing"
2) "taiyuan"
3) "xian"
127.0.0.1:6379> georadiusbymember china:city taiyuan 1000 km withcoord withdist count 2
1) 1) "taiyuan"
2) "0.0000"
3) 1) "112.54999905824661255"
2) "37.86000073876942196"
2) 1) "xian"
2) "514.2264"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"

参数与 georadius 一样

geohash(较少使用)
该命令返回11个字符的hash字符串

127.0.0.1:6379> geohash china:city taiyuan shenyang
1) "ww8p3hhqmp0"
2) "wxrvb9qyxk0"

将二维的经纬度转换为一维的字符串, 如果两个字符串越接近, 则距离越近

geo的底层实现
geo底层的实现原理实际上就是Zset, 我们可以通过Zset命令来操作geo

127.0.0.1:6379> type china:city
zset

查看全部元素 删除指定的元素

127.0.0.1:6379> zrange china:city 0 -1 withscores
1) "xian"
2) "4040115445396757"
3) "hangzhou"
4) "4054133997236782"
5) "manjing"
6) "4066006694128997"
7) "taiyuan"
8) "4068216047500484"
9) "shenyang"
1) "4072519231994779"
2) "shengzhen"
3) "4154606886655324"
127.0.0.1:6379> zrem china:city manjing
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "xian"
2) "hangzhou"
3) "taiyuan"
4) "shenyang"
5) "shengzhen"

redis查看key

方式一:查看所有键值对的Key,然后通过get命令获取每个Key对应的Value。

> keys * # 查找所有Key
1) "key1"
2) "key2"
3) "key3"
> get key1 # 获取Key1的Value
"value1"

方式二:
Redis的SCAN命令可以通过游标(cursor)方式遍历所有键值对,每次返回的结果包含一个游标和一些键值对。需要注意的是,Redis的SCAN命令只能保证命令执行时刻的快照数据,可能会漏掉某些键值对。

casn 0

方式三:Redis的INFO命令可以获取数据库的各种统计信息,包括键值对数量、内存使用情况等。通过解析INFO命令的结果,就可以得到所有键值对的信息。

> info # 获取数据库信息
# Server
redis_version:6.2.4
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:3f246c8ecf1610dc
redis_mode:standalone
os:Darwin 20.4.0 x86_64
arch_bits:64
[...省略一些信息...]
# Keyspace
db0:keys=5,expires=0,avg_ttl=0

https://baijiahao.baidu.com/s?id=1709170155160213718&wfr=spider&for=pc

redis缓存淘汰策略

在配置文件redis.conf 中,可以通过参数 maxmemory 来设定最大内存,当数据内存达到 maxmemory 时,便会触发redis的内存淘汰策略(我们一般会将该参数设置为物理内存的四分之三)。

当 Redis 的内存超过最大允许的内存之后,Redis 会触发内存淘汰策略。(过期策略是指正常情况下清除过期键,内存淘汰是指内存超过最大值时的保护策略)。内存淘汰策略可以通过maxmemory-policy进行配置,目前Redis提供了以下几种(2个LFU的策略是4.0后出现的):

volatile-lru,针对设置了过期时间的key,使用lru算法进行淘汰。
allkeys-lru,针对所有key使用lru算法进行淘汰。
volatile-lfu,针对设置了过期时间的key,使用lfu算法进行淘汰。
allkeys-lfu,针对所有key使用lfu算法进行淘汰。
volatile-random,从所有设置了过期时间的key中使用随机淘汰的方式进行淘汰。
allkeys-random,针对所有的key使用随机淘汰机制进行淘汰。
volatile-ttl,针对设置了过期时间的key,越早过期的越先被淘汰。
noeviction,不会淘汰任何数据,当使用的内存空间超过 maxmemory 值时,再有写请求来时返回错误。
LRU
LFU

redis缓存和数据库双写一致性

1、延时双删
先删除缓存,在更新数据库,过一段时间再删除缓存。这个时间间隔不太好确认(因此一般用延时队列进行异步处理)。
2、先更新库再删除缓存

先删除缓存没有办法解决旧数据存活问题
a.如果更新操作时间消耗长的话,那其他线程已经又读取旧数据到缓存了
b.如果更新时间消耗较短,那旧数据存活时间也就很短了,此时删不删缓存作用不大。
其次是否需要延时防止 产生脏数据呢?
a.如果写操作频繁,那缓存自然就更新的快,延时的作用不大。
b.如果写操作不频繁,那产生脏数据的概率很小,延时也作用不大。
综上:如果对数据一致性要求不打这么严格的话可以根据需要选择缓存和数据库一致的操作,如果要求严格数据一致,就需要同国家所,在更新过程中不允许读操作(牺牲一段时间缓存的高速读写能力)。

redis和Memcached的区别

Redis是单线程的;而Memcached是多线程的。

Memcache 在并发场景下,用cas保证一致性
redis事务支持比较弱,只能保证事务中的每个操作连续执行
mongoDB不支持事务

redis支持(快照、AOF):依赖快照进行持久化,aof增强了可靠性的同时,对性能有所影响
memcache不支持,通常用在做缓存,提升性能;
MongoDB从1.8版本开始采用binlog方式支持持久化的可靠性

Memcached的读写速度高于Redis。

使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。

1.Redis是一个开源的内存数据结构存储,用作数据库,缓存和消息代理;
2.Memcached是一个免费的开源高性能分布式内存对象缓存系统,它通过减少数据库负载来加速动态Web应用程序。

Spring整合memcache

以下是一个使用Spring整合Memcache的示例代码:

首先,在pom.xml文件中添加以下依赖:

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
    <groupId>redis.clientsgroupId>
    <artifactId>jedisartifactId>
dependency>

创建一个配置类,用于配置Memcache的连接信息。例如,创建一个MemcacheConfig.java文件:

@Configuration
public class MemcacheConfig {

    @Value("${memcache.host}")
    private String memcacheHost;

    @Value("${memcache.port}")
    private int memcachePort;

    @Bean
    public MemcachedClientFactoryBean memcachedClient() {
        MemcachedClientFactoryBean memcachedClientFactoryBean = new MemcachedClientFactoryBean();
        memcachedClientFactoryBean.setServers(memcacheHost + ":" + memcachePort);
        return memcachedClientFactoryBean;
    }

    @Bean
    public MemcachedClient memcachedClient(MemcachedClientFactoryBean memcachedClientFactoryBean) throws Exception {
        return memcachedClientFactoryBean.getObject();
    }
}

在application.properties文件中配置Memcache的连接信息。例如:

memcache.host=localhost
memcache.port=11211

创建一个Service类,使用Memcache缓存数据。例如,创建一个UserService.java文件:

@Service
public class UserService {

    @Autowired
    private MemcachedClient memcachedClient;

    public User getUserById(String id) throws Exception {
        User user = (User) memcachedClient.get(id);
        if (user == null) {
            // 从数据库中查询用户数据
            user = userRepository.findById(id);
            if (user != null) {
                memcachedClient.set(id, 60, user);
            }
        }
        return user;
    }
}

使用controller测试Memcache的示例:

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/{id}")
    public User getUserById(@PathVariable String id) throws Exception {
        return userService.getUserById(id);
    }
}

以上示例演示了如何使用Spring整合Memcache。在配置文件中配置Memcache的连接信息,并使用MemcachedClient来进行数据的缓存和读取。

memcached是高性能的分布式内存缓存服务器。

https://blog.csdn.net/wzngzaixiaomantou/article/details/125533413
https://blog.csdn.net/weixin_50744881/article/details/122539744
https://blog.csdn.net/weixin_43709538/article/details/115251944

零拷贝。

一般的拷贝流程以及零拷贝的定义

Kafka中的零拷贝

Spring

OAuth2.0

jwt

token

jwt和token的区别

总结:OAth2.0是一种用于授权方文第三方应用程序的开放标准协议。它允许用户授权第三方应用程序代表用户获取访问资源的权限,而无需将其凭证(如账号和密码)直接提供给第三方应用程序。

事务.

Spring中事务的传播机制。

【JAVA知识点小结】_第19张图片

Spring的事务传播机制可以使用 @Transactional(propagation=Propagation.REQUIRED) 来定义,Spring事务传播机制的级别包含一下七种:

REQUIRED
REQUIRED是Spring默认的传播机制。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。REQUIRED传播机制最常用的情况是在一个事务中进行多个操作,要么全部成功,要么全部失败。如果其中一个操作失败,整个事务都将被回滚。

SUPPORTS
SUPPORTS传播机制表示当前方法如果在一个事务中被调用,则加入该事务;否则,以非事务的方式运行。SUPPORTS传播机制适用于对事务要求不高的操作,例如读取操作。

MANDATORY
MANDATORY传播机制表示当前方法必须在一个事务中被调用,否则将抛出异常。MANDATORY传播机制适用于在需要事务的情况下调用方法。

REQUIRES_NEW
REQUIRES_NEW传播机制表示当前方法必须开启一个新事务运行,如果当前存在事务,则挂起该事务。REQUIRES_NEW传播机制适用于对事务要求较高的操作,例如更新操作。

NOT_SUPPORTED
NOT_SUPPORTED传播机制表示当前方法不应该在事务中运行,如果存在事务,则挂起该事务。NOT_SUPPORTED传播机制适用于对事务没有要求的操作,例如日志记录等。

NEVER
NEVER传播机制表示当前方法不应该在事务中运行,如果存在事务,则抛出异常。NEVER传播机制适用于禁止在事务中运行的操作,例如安全检查等。

NESTED
NESTED传播机制表示当前方法必须在一个嵌套事务中运行,如果当前存在事务,则在该事务内开启一个嵌套事务;如果当前没有事务,则创建一个新事务。NESTED传播机制适用于需要分步操作的场景,例如订单中创建订单和订单项的操作。

子事务如果是NESTED模式,则会保存savepoint,以便后面回滚到该savepoint。
回滚时,直接回滚到savepoint,且不会设置全局回滚标识。因此即相当于就是回滚了子事务。主事务由于try-catch了异常,因此执行方法的时候也没有抛出异常,正常走提交流程,且没有全局回滚标识,故不会回滚,正常提交。
sql
PS: mysql的savepoint机制
BEGIN;
INSERT INTO test_entity values (1, ‘aaa’, 10); ①
SAVEPOINT savepoint1;
INSERT INTO test_entity values (2, ‘bbb’, 11); ②
ROLLBACK TO savepoint1;
RELEASE SAVEPOINT savepoint1;
COMMIT;

总结
在实际应用中,我们需要根据具体业务场景选择合适的事务传播机制。例如,对于对事务要求不高的读取操作,可以选择SUPPORTS传播机制;对于更新操作,可以选择REQUIRES_NEW传播机制。

Spring中事务失效的场景

1、数据库引擎不支持事务

2、没有被Spring管理
比如是用Transactional注解的类没有被加载成一个Bean.

3、@Transactional注解只能用于publiv的方法上,否则会失效。(如果方法必须是public,则可以开启基于Aspectj框架的静态代理模式)

4、被事务注解修饰的方法发生了类内部的“自身调用”,没有经过Spring的代理类,默认只有调用外部代理类的方法,事务才会生效。

Spring默认只有调用Spring代理类的public方法没食物才能生效。

5、没有配置事务管理器

在Spring Boot框架默认在引入spring-boot-starter-data-jdbc后就自动配置了DataSourceTransactionManager。

6、主动设置了不支持事务

@Tansactional(propagation = Propagation.NOT_SUPPRTED)

7、异常没有被抛出导致事务回滚
以下方法把异常捕获没有抛出,事务不会回滚

@service
public class UserServiceImpl implements UserService{
	@Transactional
	public void update(User user){
		try{
			// update cmd
		} catch {
			//空,没有抛出异常
		}
	}
}

8、异常类型不匹配
这种情况和第七种类似都是因为遗产更没有捕获到导致事务无法生效,没有回滚;比如Spring默认回滚的是RuntimeException异常,和程序排除的Exception一场不匹配,所以事务不生效(没有回滚)。如果要触发默认的运行时异常之外的异常,则需要再@Transactional注解中指定异常类:

@Transactional(rollbackFor = Exception.class)

全局异常处理

1、@ControllerAdvice 注解和 @ExceptionHandler 注解实现全局异常处理。

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
        // 处理异常逻辑
        return ResponseEntity.status(status).body(ex.getMessage());
    }
}

2、使用自定义的异常处理类来实现全局异常处理。可以继承 Spring 的 ResponseEntityExceptionHandler 类,并重写相应的方法来处理特定的异常。

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
        // 处理异常逻辑
        return ResponseEntity.status(status).body(ex.getMessage());
    }
}

3、@RestControllerAdvice 注解和 @ExceptionHandler 注解实现全局异常处理。(与第一钟类似)

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(SelfDefineOrException.class)
    public ResponseEntity<String> handleException(Exception e) {
        // 处理异常逻辑
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
    }
}

bean的作用域

作用域的种类
Spring 容器在初始化一个 Bean 的实例时,同时会指定该实例的作用域。Spring3 为 Bean 定义了五种作用域,具体如下。

1)singleton

单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域。

2)prototype

原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。

3)request

在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。

4)session

在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。

5)global Session

在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。

Spring 容器默认作用域是 singleton,多线程下一个类也只有一个对象。(单例模式)
bean的作用域是prototype时,每个线程都会创建自己的对象,但是每线程获取的原始对象都是一致的。(原型模式 javaObject对象中的clone方法如是;提到了克隆,也就是拷贝也分深拷贝与浅拷贝 )

原文链接:
https://blog.csdn.net/unbelievevc/article/details/126259496

注解

自定义注解

字段注解

自定义字段注解声明:

@Target({ ElementType.FIELD}) //只允许用在类的字段上
@Retention(RetentionPolicy.RUNTIME) //注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解
@Constraint(validatedBy = ParamConstraintValidated.class)
public @interface Check {
    /**
     * 合法的参数值
     * */
    String[] paramValues();

    /**
     * 提示信息
     * */
    String message() default "参数不为指定值";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

验证器类需要实现ConstraintValidator泛型接口:

public class ParamConstraintValidated implements ConstraintValidator<Check, Object> {
    /**
     * 合法的参数值,从注解中获取
     * */
    private List<String> paramValues;

    @Override
    public void initialize(Check constraintAnnotation) {
        //初始化时获取注解上的值
        paramValues = Arrays.asList(constraintAnnotation.paramValues());
    }

    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        if (paramValues.contains(o)) {
            return true;
        }

        //不在指定的参数列表中
        return false;
    }
}

使用方式
定义一个实体类:
@Data

public class User {
    /**
     * 姓名
     * */
    private String name;

    /**
     * 性别 man or women
     * */
    @Check(paramValues = {"man", "woman"})
    private String sex;
}

对sex字段加校验,其值必须为woman或者man
测试

@RestController("/api/test")
public class TestController {
    @PostMapping
    public Object test(@Validated @RequestBody User user) {
        return "hello world";
    }
}

注意需要在User对象上加上@Validated注解,这里也可以使用@Valid注解

@Target 定义注解的使用位置,用来说明该注解可以被声明在那些元素之前。
ElementType.TYPE:说明该注解只能被声明在一个类前。
ElementType.FIELD:说明该注解只能被声明在一个类的字段前。
ElementType.METHOD:说明该注解只能被声明在一个类的方法前。
ElementType.PARAMETER:说明该注解只能被声明在一个方法参数前。
ElementType.CONSTRUCTOR:说明该注解只能声明在一个类的构造方法前。
ElementType.LOCAL_VARIABLE:说明该注解只能声明在一个局部变量前。
ElementType.ANNOTATION_TYPE:说明该注解只能声明在一个注解类型前。
ElementType.PACKAGE:说明该注解只能声明在一个包名前
@Constraint 通过使用validatedBy来指定与注解关联的验证器
@Retention 用来说明该注解类的生命周期。
RetentionPolicy.SOURCE: 注解只保留在源文件中
RetentionPolicy.CLASS : 注解保留在class文件中,在加载到JVM虚拟机时丢弃
RetentionPolicy.RUNTIME: 注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解。

方法、类注解

权限注解

1、自定义注解

@Target({ ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionCheck {
    /**
     * 资源key
     * */
    String resourceKey();
}

2、实现拦截器类

public class PermissionCheckInterceptor extends HandlerInterceptorAdapter {
    /**
     * 处理器处理之前调用
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        PermissionCheck permission = findPermissionCheck(handlerMethod);

        //如果没有添加权限注解则直接跳过允许访问
        if (permission == null) {
            return true;
        }

        //获取注解中的值
        String resourceKey = permission.resourceKey();

        //TODO 权限校验一般需要获取用户信息,通过查询数据库进行权限校验
        //TODO 这里只进行简单演示,如果resourceKey为testKey则校验通过,否则不通过
        if ("testKey".equals(resourceKey)) {
            return true;
        }

        return false;
    }

    /**
     * 根据handlerMethod返回注解信息
     *
     * @param handlerMethod 方法对象
     * @return PermissionCheck注解
     */
    private PermissionCheck findPermissionCheck(HandlerMethod handlerMethod) {
        //在方法上寻找注解
        PermissionCheck permission = handlerMethod.getMethodAnnotation(PermissionCheck.class);
        if (permission == null) {
            //在类上寻找注解
            permission = handlerMethod.getBeanType().getAnnotation(PermissionCheck.class);
        }

        return permission;
    }
}

3、 测试验证

@GetMapping(“/api/test”)
@PermissionCheck(resourceKey = “test”)
public Object testPermissionCheck() {
return “hello world”;
}

缓存注解

1、自定义注解
@Target({ ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomCache {
/**
* 缓存的key值
* */
String key();
}

注解可以用在方法或类上,但是缓存注解一般是使用在方法上的

2、实现切面

@Aspect
@Component
public class CustomCacheAspect {
    /**
     * 在方法执行之前对注解进行处理
     *
     * @param pjd
     * @param customCache 注解
     * @return 返回中的值
     * */
    @Around("@annotation(com.cqupt.annotation.CustomCache) && @annotation(customCache)")
    public Object dealProcess(ProceedingJoinPoint pjd, CustomCache customCache) {
        Object result = null;

        if (customCache.key() == null) {
            //TODO throw error
        }

        //TODO 业务场景会比这个复杂的多,会涉及参数的解析如key可能是#{id}这些,数据查询
        //TODO 这里做简单演示,如果key为testKey则返回hello world
        if ("testKey".equals(customCache.key())) {
            return "hello word";
        }

        //执行目标方法
        try {
            result = pjd.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        return result;
    }
}

因为缓存注解需要在方法执行之前有返回值,所以没有通过拦截器处理这个注解,而是通过使用切面在执行方法之前对注解进行处理。如果注解没有返回值,将会返回方法中的值

3、测试

@GetMapping("/api/cache")
@CustomCache(key = "test")
public Object testCustomCache() {
    return "don't hit cache";
}

参考:https://juejin.cn/post/6844903858393595917

后端API校验注解

@DecimalMax
标注的元素可定义为BigDecimal、BigInteger、String、byte、short、int、long以及他们的包装类型
不支持double、float类型(否则小数点会有精度丢失的可能)
限制必须为数字,最大值不能超过指定值
@DecimalMin
标注的元素可定义为BigDecimal、BigInteger、String、byte、short、int、long以及他们的包装类型
不支持double、float类型(否则小数点会有精度丢失的可能)
限制必须为数字,最小值不能低于指定值
@Max
标注的元素可定义为BigDecimal、BigInteger、String、byte、short、int、long以及他们的包装类型
不支持double、float类型(否则小数点会有精度丢失的可能)
限制必须为数字,最大值不能超过指定值
@Min
标注的元素可定义为BigDecimal、BigInteger、String、byte、short、int、long以及他们的包装类型
不支持double、float类型(否则小数点会有精度丢失的可能)
限制必须为数字,最小值不能低于指定值
@Digits(integer=,fraction=)
标注的元素可定义为BigDecimal、BigInteger、String、byte、short、int、long以及他们的包装类型
限制必须为数字类型,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@NotBlank
入参不能为空,只支持String类型的校验
@Future
必须为未来时间
@Past
必须为过去时间
@DateTimeFormat
前端传至后端规定的格式
如:@DateTimeFormat(pattern = “yyyy-MM-dd HH:mm:ss”)
@JsonFormat
后端转换date类型格式传至前端
如:@JsonFormat(pattern = “yyyy-MM-dd HH:mm:ss”, timezone = “GMT+8”)
@NotEmpty
入参不能为空,只支持List、Set、Map集合的校验
@AssertFalse
标注的元素必须定义为布尔类型
校验必须是null值或者布尔类型的false值
@AssertTrue
标注的元素必须定义为布尔类型
校验必须是null值或者布尔类型的true值
@NotNull
入参不能为空,支持定义任何类型,多数用于基础类型的校验
@Size
限制入参必须为字符串或者集合,不超过定义的范围
@Email
邮箱格式校验,必须是@后跟*.*格式拼接
@Pattern(regexp=)
声明的字符串需要匹配定义的正则表达式,或者为null 值

参考地址:https://blog.csdn.net/Hiber12/article/details/120283353

lombok相关注解

原文中提到的大致有以下几点:

  1. 此注解会生成equals(Object other) 和 hashCode()方法。

  2. 它默认使用非静态,非瞬态的属性

  3. 可通过参数exclude排除一些属性

  4. 可通过参数of指定仅使用哪些属性

  5. 它默认仅使用该类中定义的属性且不调用父类的方法

  6. 可通过callSuper=true解决上一点问题。让其生成的方法中调用父类的方法

@Data相当于@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode这5个注解的合集。
因此当使用@Data注解时,则有了@EqualsAndHashCode注解,那么就会在此类中存在equals(Object other) 和 hashCode()方法,且不会使用父类的属性,这就导致了可能的问题。
比如,有多个类有相同的部分属性,把它们定义到父类中,恰好id(数据库主键)也在父类中,那么就会存在部分对象在比较时,它们并不相等,却因为lombok自动生成的equals(Object other) 和 hashCode()方法判定为相等,从而导致出错。
应对此问题的方法有以下两种:
1. 使用@Getter @Setter @ToString代替@Data并且自定义equals(Object other) 和 hashCode()方法,比如有些类只需要判断主键id是否相等即足矣。
2. 或者使用在使用@Data时同时加上@EqualsAndHashCode(callSuper=true)注解。

bean生命周期中的注解

@PostConstruct 在bean加载前执行被该注解修饰的方法
@PreDestroy 在bean销毁前执行被该注解修饰的方法

加载配置文件的注解

/*- 名称:@PropertySource

- 类型:**类注解**

- 位置:类定义上方

- 作用:加载properties文件中的属性值

- 范例:*/

  @PropertySource(value = "classpath:filename.properties")
  public class ClassName {
      @Value("${propertiesAttributeName}")
      private String attributeName;
  }

/*- 说明:

  - 不支持*通配格式,一旦加载,所有spring控制的bean中均可使用对应属性值

- 相关属性

  - value(默认):设置加载的properties文件名

  - ignoreResourceNotFound:如果资源未找到,是否忽略,默认为false
*/

bean加载顺序控制

/*@DependsOn

- 名称:@DependsOn

- 类型:类注解、方法注解

- 位置:bean定义的位置(类上或方法上)

- 作用:控制bean的加载顺序,使其在指定bean加载完毕后再加载

- 范例:*/

  @DependsOn("beanId")
  public class ClassName {
  }

/*- 说明:

  - 配置在方法上,使@DependsOn指定的bean优先于@Bean配置的bean进行加载

  - 配置在类上,使@DependsOn指定的bean优先于当前类中所有@Bean配置的bean进行加载

  - 配置在类上,使@DependsOn指定的bean优先于@Component等配置的bean进行加载

- 相关属性

  - value(默认):设置当前bean所依赖的bean的id

(2)@Order

- 名称:@Order

- 类型:配置类注解

- 位置:配置类定义的位置(类上)

- 作用:控制配置类的加载顺序

- 范例:*/

  @Order(1)
  public class SpringConfigClassName {
  }

/*(3)@Lazy

- 名称:@Lazy

- 类型:类注解、方法注解

- 位置:bean定义的位置(类上或方法上)

- 作用:控制bean的加载时机,使其延迟加载

- 范例:*/

  @Lazy
  public class ClassName {
  }

以上三个控制bean加载顺序的注解的应用场景
@DependsOn /- 微信订阅号,发布消息和订阅消息的bean的加载顺序控制
双11活动期间,零点前是结算策略A,零点后是结算策略B,策略B操作的数据为促销数据。策略B加载顺序与促销数据的加载顺序
/

@Lazy /- 程序灾难出现后对应的应急预案处理是启动容器时加载时机/

@Order /-多个种类的配置出现后,优先加载系统级的,然后加载业务级的,避免细粒度的加载控制/

FactoryBean
对单一的bean的初始化过程进行封装,达到简化配置的目的
FactoryBean与BeanFactory区别
FactoryBean:封装单个bean的创建过程
BeanFactory:Spring容器顶层接口,定义了bean相关的获取操作

参考地址:https://www.cnblogs.com/yangjiaoshou/p/14959019.html

其他

学习地址:pdai.tech

你可能感兴趣的:(jvm,java,mysql,redis,spring)