系统架构整理

以下内容整合了外部资源和自己的实践整理,为了大家平时看着方便和面试的时候进行突击,但不保证内容的100%正确性。

一:Java基础

1:JVM、JRE和JDK的关系

JVM
Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。

JRE
Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包

如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。

JDK
Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等

JVM&JRE&JDK关系图
 c7cf2e7243974a2a8b5a46f7d5172ddb.png

2:多态的实现

      封装、继承、重写、向上转型(子类引用指向父类对象)。
封装
     隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。
继承
     在多态中必须存在有继承关系的子类和父类。
重写
     子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型
     在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。


只有满足了上述的条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

3:反射

什么是反射机制?

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

静态编译和动态编译

    **静态编译:**在编译时确定类型,绑定对象
    **动态编译:**运行时确定类型,绑定对象

反射机制优缺点

    优点: 运行期类型的判断,动态加载类,提高代码灵活度。
    缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。
 

反射机制的应用场景有哪些?

反射是框架设计的灵魂。

在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。

举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性
Java获取反射的三种方法

1.通过new对象实现反射机制 2.通过路径实现反射机制 3.通过类名实现反射机制
 

public class Get {
    //获取反射机制三种方式
    public static void main(String[] args) throws ClassNotFoundException {
        //方式一(通过建立对象)
        Student stu = new Student();
        Class classobj1 = stu.getClass();
        System.out.println(classobj1.getName());
        //方式二(所在通过路径-相对路径)
        Class classobj2 = Class.forName("fanshe.Student");
        System.out.println(classobj2.getName());
        //方式三(通过类名)
        Class classobj3 = Student.class;
        System.out.println(classobj3.getName());
    }
}

4:String相关

什么是字符串常量池?

字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。

String有哪些特性

 不变性 
   String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。
常量池优化
   String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。
final
   使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。

String真的是不可变的吗?

我觉得如果别人问这个问题的话,回答不可变就可以了。 下面只是给大家看两个有代表性的例子:

1) String不可变但不代表引用不可以变

String str = "Hello";
str = str + " World";
System.out.println("str=" + str);

结果

str=Hello World

解析:

实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。

在使用 HashMap 的时候,用 String 做 key 有什么好处?

HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。

String和StringBuffer、StringBuilder的区别是什么

可变性

String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。

线程安全性

String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

性能

每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

对于三者使用的总结

如果要操作少量的数据用 = String

单线程操作字符串缓冲区 下操作大量数据 = StringBuilder

多线程操作字符串缓冲区 下操作大量数据 = StringBuffer

5:Java对象结构

链接:Java对象结构-CSDN博客

重点

String类相关

Java对象结构

二:集合

1:集合和数组的区别

  • 数组是固定长度的;集合可变长度的。

  • 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。

  • 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。

2:使用集合框架的好处

  1. 容量自增长;
  2. 提供了高性能的数据结构和算法,使编码更轻松,提高了程序速度和质量;
  3. 允许不同 API 之间的互操作,API之间可以来回传递集合;
  4. 可以方便地扩展或改写集合,提高代码复用性和可操作性。
  5. 通过使用JDK自带的集合类,可以降低代码维护和学习新API成本。

3:常用的集合类有哪些?

Map接口和Collection接口是所有集合框架的父接口:

    1:Collection接口的子接口包括:Set接口和List接口
    2:Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
    3: Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
    4:List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
 31b91f856bbe4fc8a9302055ca2aeca5.jpeg

Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、List、Queue三种子接口。我们比较常用的是Set、List,Map接口不是collection的子接口。

Collection集合主要有List和Set两大接口

    List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。
    Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。

Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。

Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap 

4:集合框架底层数据结构

Collection

    List

    Arraylist: Object数组,查找快,新增慢
    Vector: Object数组
    LinkedList: 双向循环链表,查找慢,新增快(只需要断开链表就行)

    Set

    HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素
    LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
    TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)
Map 

HashMap: 详情见:HashMap源码_hashmap 源码-CSDN博客
LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
TreeMap: 红黑树(自平衡的排序二叉树) 

                        

5:说一下 ArrayList 的优缺点

ArrayList的优点如下:

    ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快。
    ArrayList 在顺序添加一个元素的时候非常方便。

ArrayList 的缺点如下:

    删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。
    插入元素的时候,也需要做一次元素复制操作,缺点同上。

ArrayList 比较适合顺序添加、随机访问的场景。

6:如何实现数组和 List 之间的转换?

    数组转 List:使用 Arrays. asList(array) 进行转换。
    List 转数组:使用 List 自带的 toArray() 方法。


7: ArrayList 和 LinkedList 的区别是什么?

46723cfef6e7487ab2d1cdfbc8e767e6.png

综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。

8:Queue


BlockingQueue是什么?

Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。Java提供了集中BlockingQueue的实现,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。
在 Queue 中 poll()和 remove()有什么区别?

    相同点:都是返回第一个元素,并在队列中删除返回的对象。
    不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。
 

9:HashMap 与 HashTable 有什么区别?

d924a782f5c8498a9ab2c7ded8f675f7.png

10: ConcurrentHashMap 和 Hashtable 的区别?

aba9a592dd144d6b99d45798fb2d097a.png

1a860de8bea04a18b676621e64b85645.png

b078f879c4d545cdb3a81ce13ae5eec5.png

答:ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。HashMap 没有考虑同步,HashTable 考虑了同步的问题。但是 HashTable 在每次同步执行时都要锁住整个结构。 ConcurrentHashMap 锁的方式是稍微细粒度的。


11: ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?

a9dc772289b047bfb3e381a9bfdba9c0.png

51774ffafe5f4e16b95500f81167f105.png

12:Java集合的快速失败机制 “fail-fast”?

是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。

例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。

原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

解决办法:使用CopyOnWriteArrayList来替换ArrayList

13:CopyOnWriteArrayList

CopyOnWriteArrayList 是一个并发容器。有很多人称它是线程安全的,我认为这句话不严谨,缺少一个前提条件,那就是非复合场景下操作它是线程安全的。

CopyOnWriteArrayList(免锁容器)的好处之一是当多个迭代器同时遍历和修改这个列表时,不会抛出 ConcurrentModificationException。在CopyOnWriteArrayList 中,写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全地执行。

CopyOnWriteArrayList 的使用场景

通过源码分析,我们看出它的优缺点比较明显,所以使用场景也就比较明显。就是合适读多写少的场景。

CopyOnWriteArrayList 的缺点

    由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致 young gc 或者 full gc。
    不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个 set 操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求。
    由于实际使用中可能没法保证 CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次 add/set 都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。

CopyOnWriteArrayList 的设计思想

    ●读写分离,读和写分开
    ●最终一致性
    ●使用另外开辟空间的思路,来解决并发冲突

14:HashMap头插法/尾插法

先看什么是头插法

JDK7源码底层通过数组长度减1和key的hashcode 做与运算((n - 1) & hash]),计算出数组的 index:

①如果这个 index 位没有元素则直接占位;

②只有一个元素时,开始比较 key 是否是同一个对象,如果是同一对象则覆盖,否则把当前 entry.next 指向原来 entry,让其退位让贤,称为头插;

③问题来了,当这个位置有 n 个 entry 时,则要遍历出每个 entry,然后去比较当前 key 与遍历出来的是否是同一个 key,是则接覆盖并退出循环,否则进行下一次遍历,都没有同样的 key 会遍历完整个 entry 链。既然都要遍历完整个 entry,为什么不直接在尾部插入呢,用尾插法不行么?

其实很简单,头插法大多数情况是方便第一和第二种情况的,因为散列性好的话 hash 冲突的概率自然比较小,没有元素或只有一个元素,遍历个锤子,减少遍厉次数呗。而且既然第一和第二种情况时已经写了一个头插方法,就没必要再写一个尾插方法了吧。

JDK8 使用尾插法,为什么呢。

①因为 jdk8 里的单向链表变成红黑树时的条件是确定的,node 链长度+1 大于等于 8 且桶的容量大于 64 时扩容,转成红黑树后遍历几次而已,就可以放心遍历啦,于是就用了尾插法,为了写代码方便。

②头插法在并发扩容时,会形成死环,但我觉得这并不是改成尾插法的主要原因,因为在并发情况使用 hashmap 本身是错误的用法。


 JDK8 HashMap源码

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        //node数组临时变量
        Node<K,V>[] tab;
        Node<K,V> p; 
        int n, i;
        //第一次添加元素
        if ((tab = table) == null || (n = tab.length) == 0)
 
          //数组(hashmap)的长度
            n = (tab = resize()).length;
        //判断下一个数组位置是否有值,无的话则新增一个Node放到Node数组中,下标 i = (n - 1) & hash,n 就是HashMap中数组的长度默认为16,hash就是通过对象key产生的hashcode。也就是下标是通过数         //组长度-1 和 hashcode 按位与产生的。
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
 
      else{
 
        Node<K,V> e; K k;
            //获取当前元素的hash值、key,与put的元素进行比较判断;
            //这个情况是hash值相同,key值也相同的情况,put的元素覆盖当前下标的元素
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //这个地方判断当前下标元素是不是红黑树
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //程序走到这,说明链表第一个节点是hash值相同,key值不相同(或者i = (n - 1) & hash计算后的下标相同,比如负载因子是1.5 容量是4,那么下标0和下标4的会在同一个链表上)
                // 遍历当前链表,把当前的put的key-value封装成Node放到链表的最后(jdk1.8)
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 是因为 binCount 是 0 为第一个节点
                            treeifyBin(tab, hash);
                        break;
                    }
                    //判断链表中是否由hash值相同,key相同的node
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //链表存在hash值相同,key相同的node,将新value覆盖旧value
            if (e != null) { // existing mapping for key
                //老值
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    //覆盖老值
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
  }
        ++modCount;
        if (++size > threshold)
             //创建一个比原来数组容量大两倍的新数组,遍历原来的数组,把原来数组上的元素重新按 
             //位与运算,放到新数组上。
            resize();
        afterNodeInsertion(evict);
        return null;
}
 

重点

Hashmap底层原理、源码(JDK1.7和JDK1.8)

ConcurrentHashMap底层原理(JDK1.7和JDK1.8)

三:异常

Java异常架构

cc0253c3723b408c8dcdef58c6b2d3e0.png

1. Throwable

Throwable 是 Java 语言中所有错误与异常的超类。

Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。

Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。

2. Error(错误)

定义:Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。

特点:此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。

这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!

3. Exception(异常)

程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。

运行时异常
定义:RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。

特点:Java 编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。比如NullPointerException空指针异常、ArrayIndexOutBoundException数组下标越界异常、ClassCastException类型转换异常、ArithmeticExecption算术异常。此类异常属于不受检异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。虽然 Java 编译器不会检查运行时异常,但是我们也可以通过 throws 进行声明抛出,也可以通过 try-catch 对它进行捕获处理。如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!

RuntimeException 异常会由 Java 虚拟机自动抛出并自动捕获(就算我们没写异常捕获语句运行时也会抛出错误!!),此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。

编译时异常
定义: Exception 中除 RuntimeException 及其子类之外的异常。

特点: Java 编译器会检查它。如果程序中出现此类异常,比如 ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。该异常我们必须手动在代码里添加捕获语句来处理该异常。

4. 受检异常与非受检异常

Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。

受检异常
编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。除 RuntimeException 及其子类外,其他的 Exception 异常都属于受检异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。

非受检异常
编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。
 

外链:

事务回滚必须是unchecked异常才会滚 (如果是内存溢出等异常,Java 虚拟机会处于不稳定状态,并且事务的状态也可能不可预测。因此,Java 事务管理器无法保证事务会回滚。)

如果想check异常也想回滚怎么办,注解上面写明异常类型即可:@Transactional(rollbackFor=Exception.class),否则受检异常(checked exception)事务是不起作用的(比如一些IOException等)

5:Java异常处理

243189837b294944a9bfd8c5864af041.png

6. JVM 是如何处理异常的?

在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。

JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM 发现可以处理异常的代码时,会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。


7. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

答:会执行,在 return 前执行。

注意:在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会返回修改后的值。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也可以通过提升编译器的语法检查级别来产生警告或错误。

8. 常见的异常 有哪些?

RuntimeException

ClassCastException(类转换异常)
IndexOutOfBoundsException(数组越界)
NullPointerException(空指针)
ArrayStoreException(数据存储异常,操作数组时类型不一致)
ConcurrentModificationException(集合并发异常)

Error

java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。

java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误。

重点:

java异常结构

事务回滚相关

常见异常:ConcurrentModificationException、java.lang.OutOfMemoryError、java.lang.StackOverflowError

四:并发编程(JUC)

并发编程又称JUC编程,JUC可能也是Java核心里最难的一块儿,指的是Java的并发工具包,里边提供了各种各样的控制同步和线程通信的工具类,位于rt.jar下面,就是我们对于jdk中java.util .concurrent 工具包的简称,其结构如下

59a0a8fc2b5e463d8c17322838c99326.png

java.util.concurrent :并发相关的

java.util.concurrent.atomic: 原子性相关的

java.util.concurrent.locks :lock锁相关的

1: JMM( Java Memory Model)

 ​ JMM 是Java内存模型( Java Memory Model),简称JMM。它本身只是一个抽象的概念,并不真实存在,它描述的是一种规则或规范,是和多线程相关的一组规范。通过这组规范,定义了程序中对各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。需要每个JVM 的实现都要遵守这样的规范,有了JMM规范的保障,并发程序运行在不同的虚拟机上时,得到的程序结果才是安全可靠可信赖的。如果没有JMM 内存模型来规范,就可能会出现,经过不同 JVM 翻译之后,运行的结果不相同也不正确的情况。

JMM 抽象出主存储器(Main Memory)和工作存储器(Working Memory)两种。
●主存储器是实例对象所在的区域,所有的实例都存在于主存储器内。比如,实例所拥有的字段即位于主存储器内,主存储器是所有的线程所共享的。
●工作存储器是线程所拥有的作业区,每个线程都有其专用的工作存储器。工作存储器存有主存储器中必要部分的拷贝,称之为工作拷贝(Working Copy)。
所以,线程无法直接对主内存进行操作,此外,线程A想要和线程B通信,只能通过主存进行。

9550f96c94304a06854f6a2a73967d17.png

①JMM的三大特性:原子性、可见性、有序性。

●原子性

一个或多个操作,要么全部执行,要么全部不执行(执行的过程中是不会被任何因素打断的)。

●可见性

只要有一个线程对共享变量的值做了修改,其他线程都将马上收到通知,立即获得最新值。

●有序性

Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性

volatile 是因为其本身包含“禁止指令重排序”的语义

synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行进入

②JMM中的八种操作

为了支持 JMM,Java 定义了8种原子操作,用来控制主存与工作内存之间的交互:

●read 读取:作用于主内存,将共享变量从主内存传送到线程的工作内存中。
●load 载入:作用于工作内存,把 read 读取的值放到工作内存中的副本变量中。
●use 使用:作用于工作内存,把工作内存的值传递给执行引擎,当虚拟机遇到一个需要使用这个变量的指令时,就会执行这个动作。
●assign 赋值:作用于工作内存,把执行引擎获取到的值赋值给工作内存中的变量,当虚拟机栈遇到给变量赋值的指令时,就执行此操作。
●store 存储:作用于工作内存,把工作内存中的变量传送到主内存中。
●write 写入:作用于主内存,把从工作内存中 store 传送过来的值写到主内存的变量中。
●lock锁定: 作用于主内存,把变量标记为线程独占状态。
●unlock解锁: 作用于主内存,它将释放独占状态。

③JMM缓存不一致问题

缓存一致性协议(MESI)

多个cpu从主内存读取同一个数据到各自的高速缓存,当其中某个cpu修改了缓存里的数据,该数据会马上同步回主内存,其它cpu通过总线嗅探机制可以感知到数据的变化从而将自己缓存里的数据失效

缓存加锁

缓存锁的核心机制是基于缓存一致性协议来实现的,一个处理器的缓存回写到内存会导致其他处理器的缓存无效,IA-32和Intel64处理器使用MESI实现缓存一致性协议

④Volatile可见性、有序性底层原理

lock指令实现可见性,内存屏障实现禁止指令重排(底层也是lock指令)

底层原理通过汇编1ock前缀指令,它会锁定这块内存区域的缓存(缓存行锁定)
IA-32和Intel 64架构软件开发者手册对lock指令的解释:
1)会将当前处理器缓存行的数据立即写回到系统内存。
2)这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效(MESI协议)
3)提供内存屏障功能,使lock前后指令不能重排序

⑤内存屏障

de8200b323f24b6898b7aa92e191b219.png

2:并发编程的优缺点

为什么要使用并发编程(并发编程的优点)

    充分利用多核CPU的计算能力:通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升

    方便进行业务拆分,提升系统并发能力和性能:在特殊的业务场景下,先天的就适合于并发编程。现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分 。

并发编程有什么缺点

并发编程的目的就是为了能提高程序的执行效率,提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如**:内存泄漏、上下文切换、线程安全、死锁**等问题。
 

3:并发编程三要素(JMM规定)

原子性:原子,即一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。

可见性:一个线程对共享变量的修改,另一个线程能够立刻看到。(synchronized,volatile)

有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)

出现线程安全问题的原因:

     ●线程切换带来的原子性问题

     ●缓存导致的可见性问题

     ●编译优化带来的有序性问题

解决办法:

    ● JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
    ● synchronized、volatile、LOCK,可以解决可见性问题
    ● Happens-Before 规则可以解决有序性问题
 

4:什么是上下文切换?

多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。

概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。

上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。

Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
 

5:守护线程和用户线程有什么区别呢?

守护线程和用户线程

    用户 (User) 线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程
    守护 (Daemon) 线程:运行在后台,为其他前台线程服务。也可以说守护线程是 JVM 中非守护线程的 “佣人”。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作

main 函数所在的线程就是一个用户线程啊,main 函数启动的同时在 JVM 内部同时还启动了好多守护线程,比如垃圾回收线程。

比较明显的区别之一是用户线程结束,JVM 退出,不管这个时候有没有守护线程运行。而守护线程不会影响 JVM 的退出。

注意事项:

    ● setDaemon(true)必须在start()方法前执行,否则会抛出 IllegalThreadStateException 异常
    ● 在守护线程中产生的新线程也是守护线程
    ● 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
    ● 守护 (Daemon) 线程中不能依靠 finally 块的内容来确保执行关闭或清理资源的逻辑。因为我们上面也说过了一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作,所以守护 (Daemon) 线程中的 finally 语句块可能无法被执行。
 

6:生产环境CPU飙升处理方法?

    ●找出cpu耗用厉害的进程pid, 终端执行top命令,然后按下shift+p 查找出cpu利用最厉害的pid号
    ●根据上面第一步拿到的pid号,top -H -p pid 。然后按下shift+p,查找出cpu利用率最厉害的线程号,比如top -H -p 1328
    ●将获取到的线程号转换成16进制,去百度转换一下就行
    ●使用jstack工具将进程信息打印输出,jstack pid号 > /tmp/t.dat,比如jstack 31365 > /tmp/t.dat
    ●编辑/tmp/t.dat文件,查找线程号对应的信息
 

7:创建线程有哪几种方式?

创建线程有四种方式:

  • 继承 Thread 类;
  • 实现 Runnable 接口;
  • 实现 Callable 接口;
  • 使用 Executors 工具类创建线程池

8:说一下 runnable 和 callable 有什么区别?

相同点

    ●都是接口

    ●都可以编写多线程程序

    ●都采用Thread.start()启动线程

主要区别

    ●Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
   ● Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息

注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
 

 9:为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!

new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。

而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。

10:线程的状态和基本操作

说说线程的生命周期及五种基本状态?

c8361730faf443ce9cee444a99b6c0dc.png

11:sleep() 和 wait() 有什么区别?

两者都可以暂停线程的执行

   ● 类的不同:sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法。
   ● 是否释放锁:sleep() 不释放锁;wait() 释放锁。
  ●  用途不同:Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
  ●  用法不同:wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。
 

12:Java中线程通信协作的最常见的三种方式:

 ● syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()

 ● ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()

 ● LockSupport工具类的park()/unpark()

a4fbb4a18f3f4bccae4b662304e178f1.png

 ● Sychronized

Object类中的wait、notify、notifyAll用于线程等待和唤醒的方法,都必须在sychronized内部执行(必须用到关键字sychronized),且成对出现使用,先wait,再notify才可以。

 ● Condition

传统的synchronized和Lock实现等待唤醒通知的约束,线程需要获得并持有锁,必须在锁块(synchronized或lock中),必须要先等待后唤醒,线程才能够被唤醒。

 ● LockSupport

LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根揭底,LockSupport调用的是Unsafe中的native代码。

 public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit)。permit只有两个值1和0,默认是0。
可以把许可看成是一种(0,1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1。
permit默认是0,所以一开始调用park()方法,当前线程a就会阻塞,直到别的线程将当前线程的permit设置为1时,也就是调用 LockSupport.unpark(a),a线程会被唤醒,消耗一个permit,然后执行相关逻辑,由于消费了一个permit,当前线程的permit又变为了0。
 

为什么可以先唤醒线程后阻塞线程

-----因为unpark获取了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
-----因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行。

13:你对线程优先级的理解是什么?

每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。我们可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个 int 变量(从 1-10),1 代表最低优先级,10 代表最高优先级。

Java 的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级有关,如非特别需要,一般无需设置线程优先级。 

14:重排序与数据依赖性

为什么代码会重排序?

在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,但是不能随意重排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件:

    ● 在单线程环境下不能改变程序运行的结果;

    ● 存在数据依赖关系的不允许重排序

需要注意的是:重排序不会影响单线程环境的执行结果,但是会破坏多线程的执行语义。

as-if-serial规则和happens-before规则的区别

   ● as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变。

   ● as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。

    ● as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。

总结:

as-if_serial单线程内,计算机底层排序不会影响原有的业务逻辑

happens-before多线程内,具有前后关系的不允许重排序

15:Synchronized 

在 Java 中,synchronized 关键字是用来控制线程同步的,就是在多线程的环境下,控制 synchronized 代码段不被多个线程同时执行。synchronized 可以修饰类、方法、变量。

另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

 说一下 synchronized 底层实现原理?

synchronized是Java中的一个关键字,在使用的过程中并没有看到显示的加锁和解锁过程。因此有必要通过javap命令,查看相应的字节码文件。

synchronized 同步语句块的情况

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("synchronized 代码块");
        }
    }
}

通过JDK 反汇编指令 javap -c -v SynchronizedDemo

e20d510f0d3144f3bdfe22b50dbaa2aa.png

可以看出在执行同步代码块之前之后都有一个monitor字样,其中前面的是monitorenter,后面的是离开monitorexit,不难想象一个线程也执行同步代码块,首先要获取锁,而获取锁的过程就是monitorenter ,在执行完代码块之后,要释放锁,释放锁就是执行monitorexit指令。

为什么会有两个monitorexit呢?

这个主要是防止在同步代码块中线程因异常退出,而锁没有得到释放,这必然会造成死锁(等待的线程永远获取不到锁)。因此最后一个monitorexit是保证在异常情况下,锁也可以得到释放,避免死锁。
仅有ACC_SYNCHRONIZED这么一个标志,该标记表明线程进入该方法时,需要monitorenter,退出该方法时需要monitorexit。

synchronized可重入的原理

重入锁是指一个线程获取到该锁之后,该线程可以继续获得该锁。底层原理维护一个计数器,当线程获取该锁时,计数器加一,再次获得该锁时继续加一,释放锁时,计数器减一,当计数器值为0时,表明该锁未被任何线程所持有,其它线程可以竞争获取锁。
 

多线程中 synchronized 锁升级的原理是什么?

synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。

锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。

详见上面的Java对象结构

16:Volatile

Volatile 关键字的作用

对于可见性,Java 提供了 volatile 关键字来保证可见性和禁止指令重排。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性,详细的可以参见 java.util.concurrent.atomic 包下的类,比如 AtomicInteger。

volatile 常用于多线程环境下的单次操作(单次读或者单次写)。
 

Volatile 变量和 Atomic 变量有什么不同?

volatile 变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用 volatile 修饰 count 变量,那么 count++ 操作就不是原子性的。

而 AtomicInteger 类提供的 atomic 方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。

17:CAS

CAS(compare and swap),比较并交换。可以解决多线程并行情况下使用重量级(synchronized)造成性能损耗的一种机制。

CAS 操作包含三个操作数—内存值(V)、预期值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。(ABA问题可以通过添加版本号处理)

一个线程从主内存中读到值V并准备修改,由于JMM规范,线程会把V拷贝一份到本地内存(线程内存),并对V进行操作变为了B,准备刷新到主内存的时候,线程会把本地内存旧的值(预期值)A和主内存中V值进行比较,如果相等,就会将改变后的B写入主内存,如果不相等,则一直循环对比,直到成功为止。

AtomicInteger
我们知道轻量级锁volatile只实现了JMM里面的可见性和有序性(禁止指令重排),但是不保证原子性,这样就导致了i++的时候会有并发的问题,比如1000个线程对共享变量+1的时候,结果总是小于1000的,对于这种情况,要么加重量级锁synchronized,要么使用java.util.concurrent.atomic包下的AtomicInteger原子类。AtomicInteger使用volatile实现可见性和有序性,使用Unsafe类实现原子性。

Unsafe

Unsafe类是rt.jar包下sun.misc的类,里面包含了大量带native修饰的方法,用于调用本地方法,也就是C语言实现的方法,用来操作计算机底层数据,此操作是原子性的。

 
    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
 
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
 
    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
 
    public native Object getObjectVolatile(Object var1, long var2);
 
    public native void putObjectVolatile(Object var1, long var2, Object var4);
 
    public native int getIntVolatile(Object var1, long var2);
 
    public native void putIntVolatile(Object var1, long var2, int var4);

相关源码,jdk1.8

 新New 一个对象AtomicInteger myInteger = new AtomicInteger(2);

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;
 
    // 获取Unsafe类,用于操作本地方法
    private static final Unsafe unsafe = Unsafe.getUnsafe();
	// 值的偏移量(也就是共享变量value的地址)
    private static final long valueOffset;
 
    // 获取valueOffset
    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    // volatile修饰的共享变量
    private volatile int value;
    
	// 构造方法初始化共享变量value
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
 
    // 给当前对象的值自动递增1
    public final int getAndIncrement() {
	     // this:当前对象 valueOffset:共享变量value的地址    1:自动递增的值 
		 // 调用到Unsafe类的getAndAddInt方法
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
  
}

 Unsafe.java

 // 获取并且加1, var1:AtomicInteger的对象, var2:AtomicInteger共享变量value的地址
   // var4:整数1
   public final int getAndAddInt(Object var1, long var2, int var4) {
        // 主内存当前的值
        int var5;
        do {
		   // getIntVolatile本地方法,根据var1和var2获取主内存当前的值
            var5 = this.getIntVolatile(var1, var2);
        } 
		// compareAndSwapInt:CAS操作,判断var1对象var2地址的值是否和目前var5的值相同,由于并发问题,var2地址的值可能改变
		// 相同则表示var2地址的值没变,var5 = var5 + var4,并且返回var5,结束自旋,否则重新获取最新var2地址的值赋值给var5,再次比较
		while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
 
        return var5;
    }

18:Synchronized、Volatile、CAS 比较

(1)synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。

(2)volatile 提供多线程共享变量可见性和禁止指令重排序优化(通过使用原子类可保证线程安全)。

(3)CAS 是基于冲突检测的乐观锁(非阻塞)

19:Synchronized 和 Lock 有什么区别?

    ●首先synchronized是Java内置关键字,在JVM层面,Lock是个Java类;
    ● synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
    ● synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
    ● 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
 

20:Synchronized 和 ReentrantLock 区别是什么?

synchronized 是和 if、else、for、while 一样的关键字,ReentrantLock 是类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比synchronized 更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量

synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。

相同点:两者都是可重入锁

两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

主要区别如下:

●ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
●ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
●ReentrantLock 只适用于代码块锁,而 synchronized 可以修饰类、方法、变量等。
●二者的锁机制其实也是不一样的。ReentrantLock 底层调用的是 Unsafe 的park 方法加锁(基于AQS),synchronized 操作的应该是对象头中 mark word
 

21:synchronized 和 volatile 的区别是什么?

synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。

volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。

区别

    ●volatile 是变量修饰符;synchronized 可以修饰类、方法、变量。

    ●volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。

   ● volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。

    ●volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

    ●volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。


22:多线程和线程池

https://carry.blog.csdn.net/article/details/113309275icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/113309275
https://carry.blog.csdn.net/article/details/113369641icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/113369641

https://carry.blog.csdn.net/article/details/113373014icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/113373014

23:线程同步的辅助类

https://carry.blog.csdn.net/article/details/112767369icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/112767369

24:Java线程池如何合理配置核心线程数

1、查看机器的CPU核数,然后在设定具体参数:

CPU核数 = Runtime.getRuntime().availableProcessors();

2、分析线程池处理的程序是CPU密集型,还是IO密集型(一般都是IO密集型)

CPU密集型: 核心线程数 = CPU核数 + 1;

IO密集型: 核心线程数 = CPU核数 * 2;

(某大厂实践经验)

IO密集型时,大部分线程都阻塞,故需要多配置线程数:

参考公式:CPU核数 /(1 - 阻系数)

比如8核CPU:8/(1 - 0.9)=80个线程数

阻塞系数在0.8~0.9之间

25:AQS

①AQS简介

●AQS全名:AbstractQueuedSynchronizer,是并发容器J.U.C(java.util.concurrent)下locks包内的一个类。它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列。底层实现的数据结构是一个双向链表。

●AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器, 比如我们提到的 ReentrantLock,Semaphore,其他的诸如 ReentrantReadWriteLock,SynchronousQueue,FutureTask(jdk1.7) 等等皆是基于 AQS 的。当然,我们自己也能利用 AQS 非常轻松容易地构造出符合我们自己需求的同步器。

●AQS 使用了模板方法模式,自定义同步器时需要重写下面几个 AQS 提供的模板方法

isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int)//独占方式。尝试获职资源,成功则返回true,失败则返回false。
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireshared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源正数表示成功,且有剩余资源。
tryReleaseshared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false

●AQS 定义两种资源共享方式:Exclusive(独占)、Share(共享)

●AQS线程间通信使用的是LockSupport的park()、unpark()

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
    private void unparkSuccessor(Node node) {

        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

207ddc835b9047bd9b9e7607cf957694.png

●AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

●AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。

状态信息通过protected类型的getState,setState,compareAndSetState进行操作

 返回同步状态的当前值

    protected final int getState() {
        return state;
    }

设置同步状态的值

  protected final void setState(int newState) {
        state = newState;
    }

原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)

 protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

②AQS设计思想

使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基础框架。

利用int类型标识状态。在AQS类中有一个叫做state的成员变量

 /**
     * The synchronization state.
     */
    private volatile int state;

基于AQS有一个同步组件,叫做ReentrantLock。在这个组件里,stste表示获取锁的线程数,假如state=0,表示还没有线程获取锁,1表示有线程获取了锁。大于1表示重入锁的数量。

继承:子类通过继承并通过实现它的方法管理其状态(acquire和release方法操纵状态)。

可以同时实现排它锁和共享锁模式(独占、共享),站在一个使用者的角度,AQS的功能主要分为两类:独占和共享。它的所有子类中,要么实现并使用了它的独占功能的api,要么使用了共享锁的功能,而不会同时使用两套api,即便是最有名的子类ReentrantReadWriteLock也是通过两个内部类读锁和写锁分别实现了两套api来实现的。

 ③AQS的大致实现思路

AQS内部维护了一个CLH队列来管理锁。线程会首先尝试获取锁,如果失败就将当前线程及等待状态等信息包装成一个node节点加入到同步队列sync queue里。 接着会不断的循环尝试获取锁,条件是当前节点为head的直接后继才会尝试。如果失败就会阻塞自己直到自己被唤醒。而当持有锁的线程释放锁的时候,会唤醒队列中的后继线程。

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。

 ④CLH队列(线程同步队列)

因为获取锁是有条件的,没有获取锁的线程就要阻塞等待,那么就要存储这些等待的线程。

在AQS中我们使用CLH队列储存这些等待的线程,但它并不是直接储存线程,而是储存拥有线程的node节点。所以先介绍重要内部类Node。

内部类Node

   static final class Node {
        // 共享模式的标记
        static final Node SHARED = new Node();
        // 独占模式的标记
        static final Node EXCLUSIVE = null;
 
        // waitStatus变量的值,标志着线程被取消
        static final int CANCELLED =  1;
        // waitStatus变量的值,标志着后继线程(即队列中此节点之后的节点)需要被阻塞.(用于独占锁)
        static final int SIGNAL    = -1;
        // waitStatus变量的值,标志着线程在Condition条件上等待阻塞.(用于Condition的await等待)
        static final int CONDITION = -2;
        // waitStatus变量的值,标志着下一个acquireShared方法线程应该被允许。(用于共享锁)
        static final int PROPAGATE = -3;
 
        // 标记着当前节点的状态,默认状态是0, 小于0的状态都是有特殊作用,大于0的状态表示已取消
        volatile int waitStatus;
 
        // prev和next实现一个双向链表
        volatile Node prev;
        volatile Node next;
 
        // 该节点拥有的线程
        volatile Thread thread;
 
        // 可能有两种作用:1. 表示下一个在Condition条件上等待的节点
        // 2. 表示是共享模式或者独占模式,注意第一种情况节点一定是共享模式
        Node nextWaiter;
 
        // 是不是共享模式
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
 
        // 返回前一个节点prev,如果为null,则抛出NullPointerException异常
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
 
        // 用于创建链表头head,或者共享模式SHARED
        Node() {
        }
 
        // 使用在addWaiter方法中
        Node(Thread thread, Node mode) {
            this.nextWaiter = mode;
            this.thread = thread;
        }
 
        // 使用在Condition条件中
        Node(Thread thread, int waitStatus) {
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

重要的成员属性:

6d615d3fde8a4c8b8acaf91448b4b6c5.png

操作CLH队列

存储CLH队列

    // CLH队列头
    private transient volatile Node head;
 
    // CLH队列尾
    private transient volatile Node tail;

设置CLH队列头head

    /**
     * 通过CAS函数设置head值,仅仅在enq方法中调用
     */
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) {  // t为null,表示队列为空,先初始化队列
                // 采用CAS函数即原子操作方式
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

设置CLH队列尾tail

    /**
     * 通过CAS函数设置tail值,仅仅在enq方法中调用
     */
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }
   // 向队列尾插入新节点,如果队列没有初始化,就先初始化。返回原先的队列尾节点
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            // t为null,表示队列为空,先初始化队列
            if (t == null) {
                // 采用CAS函数即原子操作方式,设置队列头head值。
                // 如果成功,再将head值赋值给链表尾tail。如果失败,表示head值已经被其他线程,那么就进入循环下一次
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                // 新添加的node节点的前一个节点prev指向原来的队列尾tail
                node.prev = t;
                // 采用CAS函数即原子操作方式,设置新队列尾tail值。
                if (compareAndSetTail(t, node)) {
                    // 设置老的队列尾tail的下一个节点next指向新添加的节点node
                    t.next = node;
                    return t;
                }
            }
        }
    }

将当前线程添加到CLH队列尾

   // 通过给定的模式mode(独占或者共享)为当前线程创建新节点,并插入队列中
    private Node addWaiter(Node mode) {
        // 为当前线程创建新的节点
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        // 如果队列已经创建,就将新节点插入队列尾。
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 如果队列没有创建,通过enq方法创建队列,并插入新的节点。
        enq(node);
        return node;
    }

26:ThreadLocal源码

https://carry.blog.csdn.net/article/details/122048423icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/122048423

27: 自己实现自旋锁(死循环+CAS)

手动实现自旋锁-CSDN博客实现自旋锁的方法主要是死循环+CAS,借鉴AtomicInteger原子类public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + va...https://blog.csdn.net/CarryBest/article/details/124387958?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22124387958%22%2C%22source%22%3A%22CarryBest%22%7D

28:读写锁

java读写锁的作用-CSDN博客Java读写锁,也就是ReentrantReadWriteLock,其包含了读锁和写锁,其中读锁是可以多线程共享的,即共享锁,而写锁是排他锁,在更改时候不允许其他线程操作。读写锁底层是同一把锁(基于同一个AQS),所以会有同一时刻不允许读写锁共存的限制。解决读大于写的情况涉及并发问题,一般都有公共资源,俗称线程操纵资源类,新建资源(模拟缓存),缓存一般有3个方法,设置缓存、获取缓存、情况缓存。class MyCache { private static Maphttps://blog.csdn.net/CarryBest/article/details/124386820?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22124386820%22%2C%22source%22%3A%22CarryBest%22%7D,>

29:TaskDecorator——异步多线程中传递上下文等变量

https://blog.csdn.net/CarryBest/article/details/116532427?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22116532427%22%2C%22source%22%3A%22CarryBest%22%7Dicon-default.png?t=N7T8https://blog.csdn.net/CarryBest/article/details/116532427?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22116532427%22%2C%22source%22%3A%22CarryBest%22%7D

重点:

多线程源码

线程池源码

各种锁源码

AQS源码

JMM

ThreadLocal源码

五:JVM

1:JVM

https://carry.blog.csdn.net/article/details/114674074icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/114674074

2:JVM调优

说一下 JVM 调优的工具?

JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。

jconsole:用于对 JVM 中的内存、线程和类等进行监控;
jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存 

MAT:

使用MAT分析生产线上OOM问题-CSDN博客java线程池如何合理配置核心线程数java线程池如何合理配置核心线程数https://blog.csdn.net/CarryBest/article/details/129055894?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22129055894%22%2C%22source%22%3A%22CarryBest%22%7D

CMS常用优化参数

-XX:+UseConcMarkSweepGC 激活CMS收集器
-XX:ConcGCThreads 设置CMS线程的数量
-XX:+UseCMSInitiatingOccupancyOnly 只根据老年代使用比例来决定是否进行CMS
-XX:CMSInitiatingOccupancyFraction 设置触发CMS老年代回收的内存使用率占比
-XX:+CMSParallelRemarkEnabled 并行运行最终标记阶段,加快最终标记的速度
-XX:+UseCMSCompactAtFullCollection 每次触发CMS Full GC的时候都整理一次碎片
-XX:CMSFullGCsBeforeCompaction=* 经过几次CMS Full GC的时候整理一次碎片
-XX:+CMSClassUnloadingEnabled 让CMS可以收集永久带,默认不会收集
-XX:+CMSScavengeBeforeRemark 最终标记之前强制进行一个Minor GC

-XX:+ExplicitGCInvokesConcurrent 当调用System.gc()的时候,执行并行gc,只有在CMS或者G1下该参数才有效

其它参数

-Xms2048m   // jvm启动时堆内存大小
-Xmx4096m // 最大内存
-Xss512k // 每个线程的内存  jdk1.4默认256k,jdk1.5+默认1m
 
-XX:MaxNewSize=2048m      // 新生代可被分配的内存的最大上限
-XX:NewRatio=1  		   // 新生代/老年代
-XX:+UseParNewGC   			// 使用ParNewGC,和CMS对应
-XX:SurvivorRatio=8      // Eden与survivor的比
 
-XX:MetaspaceSize=256m    // 初始化元空间大小
-XX:MaxMetaspaceSize=512m // 最大元空间大小
 
-Xloggc:/data/logs/xx/xx_gc_%p.log // GC日志 位置
-XX:+PrintGCDetails // 打印详细GC日志

六::Spring

1:IOC

控制反转即IOC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。

Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
 

控制反转(IoC)有什么作用

    ●管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要程序猿来维护的话,那是相当头疼的

    ●解耦,由容器去维护具体的对象

    ●托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的
 

2: AOP

什么是AOP

OOP(Object-Oriented Programming)面向对象编程,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。

AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。
 

解释一下Spring AOP里面的几个名词

(1)切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。

(2)连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

(3)通知(Advice):在AOP术语中,切面的工作被称为通知。

(4)切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。

(5)引入(Introduction):引入允许我们向现有类添加新方法或属性。

(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。

(7)织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入:
 

●编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。
●类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应  用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。
●运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。 

Spring通知有哪些类型?

在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过SpringAOP框架触发的代码段。

Spring切面可以应用5种类型的通知:

    ●前置通知(Before):在目标方法被调用之前调用通知功能;
    ●后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
    ●返回通知(After-returning ):在目标方法成功执行之后调用通知;
    ●异常通知(After-throwing):在目标方法抛出异常后调用通知;
    ●环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
 

Spring5的几种通知的执行顺序:

程序执行正常:

1、环绕通知前
2、@Before通知
3、程序逻辑
4、@AfterReturning通知
5、@After通知
6、环绕通知后

程序执行异常:

1、环绕通知前
2、@Before通知
3、@AfterThrowing异常通知
4、@After通知
异常日志

Spring AOP and AspectJ AOP 有什么区别?AOP 有哪些实现方式?

AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。

(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。

(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

JDK动态代理和CGLIB动态代理的区别

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

    JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。

    如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

3: 解释Spring框架中bean的生命周期

在传统的Java应用中,bean的生命周期很简单。使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。相比之下,Spring容器中的bean的生命周期就显得相对复杂多了。正确理解Spring bean的生命周期非常重要,因为你或许要利用Spring提供的扩展点来自定义bean的创建过程。下图展示了bean装载到Spring应用上下文中的一个典型的生命周期过程。

bean在Spring容器中从创建到销毁经历了若干阶段,每一阶段都可以针对Spring如何管理bean进行个性化定制。

正如你所见,在bean准备就绪之前,bean工厂执行了若干启动步骤。

我们对上图进行详细描述:

●Spring对bean进行实例化;

●Spring将值和bean的引用注入到bean对应的属性中;

●如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;

●如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;

●如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;

●如果bean实现了BeanPostProcessor接口, Spring调用它们的post-ProcessBeforeInitialization()方法;

●如果bean实现了InitializingBean接口,Spring调用它们的after-PropertiesSet()方法。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用;

●如果bean实现了BeanPostProcessor接口,Spring调用它们的post-ProcessAfterInitialization()方法;

●此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;

●如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。


 BeanNameAware

作用:让Bean获取自己在BeanFactory配置中的名字(根据情况是id或者name)。 
Spring自动调用。并且会在Spring自身完成Bean配置之后,且在调用任何Bean生命周期回调(初始化或者销毁)方法之前就调用这个方法。换言之,在程序中使用BeanFactory.getBean(String beanName)之前,Bean的名字就已经设定好了。

BeanFactoryAware

作用:让Bean获取配置他们的BeanFactory的引用。

这个方法可能是在根据某个配置文件创建了一个新工厂之后,Spring才调用这个方法,并把BeanFactory注入到Bean中。 
让bean获取配置自己的工厂之后,当然可以在Bean中使用这个工厂的getBean()方法,但是,实际上非常不推荐这样做,因为结果是进一步加大Bean与Spring的耦合,而且,能通过DI注入进来的尽量通过DI来注入。 
当然,除了查找bean,BeanFactory可以提供大量其他的功能,例如销毁singleton模式的Bean。 
factory.preInstantiateSingletons();方法。preInstantiateSingletons()方法立即实例化所有的Bean实例,有必要对这个方法和Spring加载bean的机制做个简单说明。 
方法本身的目的是让Spring立即处理工厂中所有Bean的定义,并且将这些Bean全部实例化。因为Spring默认实例化Bean的情况下,采用的是lazy机制,换言之,如果不通过getBean()方法(BeanFactory或者ApplicationContext的方法)获取Bean的话,那么为了节省内存将不实例话Bean,只有在Bean被调用的时候才实例化他们

ApplicationContextAware

ApplicationContextAware是Spring框架提供的一个特殊的回调接口,用于帮助对象访问到Spring的应用上下文ApplicationContext。

当在自己的类中实现ApplicationContextAware接口时,可以通过Spring提供的回调机制访问到Spring的应用上下文,从而获得Spring IoC容器中的bean实例、配置信息以及进行国际化操作、事件发布等操作。

在ApplicationContextAware接口中定义了一个setApplicationContext方法,当类实现了该接口之后,Spring IoC容器会自动调用该方法并将当前的ApplicationContext注入到所实现的setApplicationContext方法中,这样就可以在该类中使用Spring的应用上下文了。

在一些开源的Spring工具库中会看到这种技术的使用,因为这些库往往需要与Spring容器交互,比如读取容器的配置,访问其他的bean等等,通过实现ApplicationContextAware接口就可以非常方便地完成这些工作。

但注意,一般不推荐在的业务代码中使用,因为这样会增加代码与Spring的耦合性,违反了“依赖于抽象,而非依赖于具体实现”的面向对象设计原则。业务代码应当尽可能减少对具体框架的依赖,以提高代码的通用性和可移植性。

比如定义一个工具类,用于获取各种Bean

import org.springframework.beans.BeansException;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.ApplicationContextAware;  
import org.springframework.stereotype.Component;  
  
@Component  
public class SpringContextUtil implements ApplicationContextAware {  
  
    // Spring应用上下文  
    private static ApplicationContext applicationContext;  
  
    // 当Spring容器创建该类的实例时,会自动调用此方法,注入应用上下文  
    @Override  
    public void setApplicationContext(ApplicationContext context) throws BeansException {  
        SpringContextUtil.applicationContext = context;  
    }  
  
    // 提供一个静态方法,返回应用上下文  
    public static ApplicationContext getApplicationContext() {  
        return applicationContext;  
    }  
  
    // 提供一个获取Bean的静态方法  
    public static <T> T getBean(Class<T> beanClass) {  
        if (applicationContext != null) {  
            return applicationContext.getBean(beanClass);  
        } else {  
            throw new IllegalStateException("ApplicationContext is not initialized yet!");  
        }  
    }  
}


BeanPostProcessor

是 Spring 提供的众多接口之一,他的作用主要是如果我们需要在Spring 容器完成 Bean 的实例化、配置和其他的初始化前后添加一些自己的逻辑处理,我们就可以定义一个或者多个 BeanPostProcessor 接口的实现,然后注册到容器中。

AOP生成代理对象也是在这个阶段实现的

本节中会对以下两部分进行讲解:
 

@Component
public  class MyBeanPostProcessor implements BeanPostProcessor {
    /**
     * 实例化、依赖注入完毕,在调用显示的初始化之前完成一些自定义的初始化逻辑
     * * 方法返回值不能为null
     * * 返回null那么在后续初始化方法会报空指针异常或者通过getBean()方法获取不到bena实例
     * * 如果设置为null情况下,后置处理器从Spring IoC容器中取出bean实例对象没有再次放回IoC容器中
     */
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization执行了" + beanName);
        return bean;
    }
 
    /**
     * 实例化、依赖注入、初始化完成后时执行
     * * 方法返回值也不能为null
     * * 如果返回null那么在后续初始化方法将报空指针异常或者通过getBean()方法获取不到bena实例对象
     * *  如果设置为null情况下,后置处理器从Spring IoC容器中取出bean实例对象没有再次放回IoC容器中
     */
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization" + beanName);
        return bean;
    }
}

InitializingBean

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

这个接口就是这么的简单,只有这么一个方法,目的就是进一步调整实例的状态

afterPropertiesSet发生作用的时机是当前类的实例化的时候,而BeanPostProcessor则是所有类,这也是为什么afterPropertiesSet的函数中没有参数

public class Account implements  InitializingBean {
    private String name;
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
    public Account(String name){
        this.name=name;
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        this.setName("bbfff");
    }
}

init-method

bean 配置文件属性 init-method 用于在bean初始化时指定执行方法,用来替代继承 InitializingBean接口,也有相应的注解@PostConstruct

DisposableBean

其实看过了InitializingBean,再看DisposableBean就简单很多了。 当bean使用完毕后,容器将检查singleton类型的bean,看其是否实现了DisposableBean接口,接口中有一个方法提供了singleton类型的对象实例销毁之前执行的销毁逻辑。
 

public interface DisposableBean {
    void destroy() throws Exception;
}

如上代码,提供了一个destroy()方法实现销毁逻辑,我们来用下吧

public class Account implements  InitializingBean, DisposableBean {
    private String name;
 
    // getter setter constructor
 
    @Override
    public void destroy() throws Exception {
        System.out.println("exit...##############################################");
    }
}

其实跟上边的基本一样,只是多实现了一个接口,同时实现了destroy()方法。

@Bean
    @Scope("singleton")
    public Account account(){
        return new Account("fff");
    }

同时,在config中我把Account的bean设为了 singleton 实例,其实不设置也可以,默认就是 singleton,接着进行测试

@Test
    public void tt(){
        System.out.println(account.getName());
    }
bbfff
2019-11-10 14:51:54.970  INFO 14292 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
exit...##############################################

可以看到打印出来了 exit...############## ,说明成功的执行了destroy方法,当然,这是因为我们的容器关闭导致的。

可以发现我们上边很多次强调了要是 singleton 的bean,这是因为 prototype 的bean实例在实例化返回给请求方时后,就不再由容器管理,自然不会执行后续的destroy 方法

destroy-method

bean 配置文件属性 destroy-method 用于在bean销毁时指定执行方法,用来替代继承 DisposableBean接口,也有相应的注解@PreDestroy

哪些是重要的bean生命周期方法? 你能重载它们吗?

有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。

bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。
 

4: Spring循环依赖

需要看懂上面的Bean的生命周期,了解AOP代理对象是在 BeanPostProcessor 的初始后方法生产的

Spring三级缓存解决循环依赖-CSDN博客我们都知道Spring中的BeanFactory是一个IOC容器,负责创建Bean和缓存一些单例的Bean对象,以供项目运行过程中使用。创建Bean的大概的过程:实例化Bean对象,为Bean对象在内存中分配空间,各属性赋值为默认值初始化Bean对象,为Bean对象填充属性将Bean放入缓存首先,容器为了缓存这些单例的Bean需要一个数据结构来存储,比如Map {k:name; v:bean}。而我们创建一个Bean就可以往Map中存入一个Bean。这时候我们仅https://blog.csdn.net/CarryBest/article/details/124319921?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22124319921%22%2C%22source%22%3A%22CarryBest%22%7D

重点

Spring框架中bean的生命周期

Spring循环依赖

七:SpringMVC

什么是Spring MVC?简单介绍下你对Spring MVC的理解?

Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把模型-视图-控制器分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
Spring MVC的优点

(1)可以支持各种视图技术,而不仅仅局限于JSP;

(2)与Spring框架集成(如IoC容器、AOP等);

(3)清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。

(4) 支持各种请求资源的映射策略。
核心组件
Spring MVC的主要组件?

(1)前端控制器 DispatcherServlet(不需要程序员开发)

作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。

(2)处理器映射器HandlerMapping(不需要程序员开发)

作用:根据请求的URL来查找Handler

(3)处理器适配器HandlerAdapter

注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。

(4)处理器Handler(需要程序员开发)

(5)视图解析器 ViewResolver(不需要程序员开发)

作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view)

(6)视图View(需要程序员开发jsp)

View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等)
 

1: 工作原理

请描述Spring MVC的工作流程?描述一下 DispatcherServlet 的工作流程?

(1)用户发送请求至前端控制器DispatcherServlet;
(2) DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;
(3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
(4)DispatcherServlet 调用 HandlerAdapter处理器适配器;
(5)HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);
(6)Handler执行完成返回ModelAndView;
(7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
(8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
(9)ViewResolver解析后返回具体View;
(10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
(11)DispatcherServlet响应用户。
 

2: Spring MVC与Struts2区别

相同点

都是基于mvc的表现层框架,都用于web项目的开发。

不同点

1.前端控制器不一样。Spring MVC的前端控制器是servlet:DispatcherServlet。struts2的前端控制器是filter:StrutsPreparedAndExcutorFilter。

2.请求参数的接收方式不一样。Spring MVC是使用方法的形参接收请求的参数,基于方法的开发,线程安全,可以设计为单例或者多例的开发,推荐使用单例模式的开发(执行效率更高),默认就是单例开发模式。struts2是通过类的成员变量接收请求的参数,是基于类的开发,线程不安全,只能设计为多例的开发。

3.Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,Spring MVC通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。

4.与spring整合不一样。Spring MVC是spring框架的一部分,不需要整合。在企业项目中,Spring MVC使用更多一些。

重点:

Spring MVC的工作流程

八:MyBatis

1:MyBatis是什么?

MyBatis 是一款优秀的持久层框架,一个半 ORM(对象关系映射)框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

2:ORM是什么

ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。

3:为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?

Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。

而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。

4:JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的? 

1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。

解决:在mybatis-config.xml中配置数据链接池,使用连接池管理数据库连接。

2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。

解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。

3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。

解决: Mybatis自动将java对象映射至sql语句。

4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。

解决:Mybatis自动将sql执行结果映射至java对象。
 

5:Mybatis优缺点

优点

与传统的数据库访问技术相比,ORM有以下优点:

   ● 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用
    ● 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接
    ● 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)
    ● 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护
    ● 能够与Spring很好的集成

缺点

    ● SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
   ● SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

6:Hibernate 和 MyBatis 的区别

相同点

都是对jdbc的封装,都是持久层的框架,都用于dao层的开发。

不同点

映射关系

   ● MyBatis 是一个半自动映射的框架,配置Java对象与sql语句执行结果的对应关系,多表关联关系配置简单
    ● Hibernate 是一个全表映射的框架,配置Java对象与数据库表的对应关系,多表关联关系配置复杂

SQL优化和移植性

    ● Hibernate 对SQL语句封装,提供了日志、缓存、级联(级联比 MyBatis 强大)等特性,此外还提供 HQL(Hibernate Query Language)操作数据库,数据库无关性支持好,但会多消耗性能。如果项目需要支持多种数据库,代码开发量少,但SQL语句优化困难。
    ● MyBatis 需要手动编写 SQL,支持动态 SQL、处理列表、动态生成表名、支持存储过程。开发工作量相对大些。直接使用SQL语句操作数据库,不支持数据库无关性,但sql语句优化容易。

开发难易程度和学习成本

   ● Hibernate 是重量级框架,学习使用门槛高,适合于需求相对稳定,中小型的项目,比如:办公自动化系统

   ● MyBatis 是轻量级框架,学习使用门槛低,适合于需求变化频繁,大型的项目,比如:互联网电子商务系统
 

7:请说说MyBatis的工作原理

Mybatis应用程序通过SqlSessionFactoryBuilder从mybatis-config.xml配置文件中构建出SqlSessionFactory,然后,SqlSessionFactory的实例直接开启一个SqlSession,再通过SqlSession实例获得Mapper对象并运行Mapper映射的SQL语句,完成对数据库的CRUD和事务提交,之后关闭SqlSession。如下图所示:

MyBatis的工作原理如下图所示:

8: Mybatis 源码 MappedStatement原理

https://carry.blog.csdn.net/article/details/124433422icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/124433422

9:Mybatis都有哪些Executor执行器?它们之间的区别是什么?

Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。

ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。

BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
 

10:使用MyBatis的mapper接口调用时有哪些要求?

1、Mapper接口方法名和mapper.xml中定义的每个sql的id相同。

2、Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同。

3、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。

4、Mapper.xml文件中的namespace即是mapper接口的类路径。

11:通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?

Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MappedStatement对象。

Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。

12:Mybatis的一级、二级缓存

1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置<cache/> ;

3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
 

重点

MyBatis工作原理

Mybatis的一级、二级缓存

九:Spring Boot

1: 什么是 Spring Boot?

Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。内置了 Tomcat/ Jetty 等容器,打包后即可直接执行。

2: Spring Boot 有哪些优点?

Spring Boot 主要有如下优点:

    ●容易上手,提升开发效率,为 Spring 开发提供一个更快、更广泛的入门体验。
    ●开箱即用,远离繁琐的配置。
    ●提供了一系列大型项目通用的非业务性功能,例如:内嵌服务器、安全管理、运行数据监控、运行状况检查和外部化配置等。
    ●没有代码生成,也不需要XML配置。
    ●避免大量的 Maven 导入和各种版本冲突。

3:Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?

启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:

@SpringBootConfiguration:这个注解的底层是一个@Configuration注解,意思被@Configuration注解修饰的类是一个IOC容器,支持JavaConfig的方式来进行配置。

@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。里面包含连个比较重要的注解@AutoConfigurationPackage和@Import。

@ComponentScan:这个就是扫描注解的意思,默认扫描当前类所在的包及其子包下包含的注解,将@Controller/@Service/@Component/@Repository等注解加载到IOC容器中;

4:Spring Boot 自动配置原理是什么?

注解 @EnableAutoConfiguration表明启动自动装配,里面包含两个个比较重要的注解@AutoConfigurationPackage和@Import。

@AutoConfigurationPackag

作用是指定springboot扫描包,默认就是扫描启动类同包下的类。扫描@Enitity、@MapperScan等第三方依赖的注解,@ComponentScan只扫描@Controller/@Service/@Component/@Repository这些常见注解。所以这两个注解扫描的对象是不一样的

@Import(AutoConfigurationImportSelector.class)

是自动装配的核心注解,AutoConfigurationImportSelector.class中有个selectImports方法,selectImports方法调用了getCandidateConfigurations方法

getCandidateConfigurations方法中,我们可以看下断言,说找不到META-INF/spring.factories,由此可见,这个方法是用来找META-INF/spring.factories文件的

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
        ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

我们可以定位到这个方法所在的类处于spring-boot-autoconfigure-.jar包中,

其中spring.factories文件是一组组的key=value的形式,包含了key为EnableAutoConfiguration的全类名,value是一个AutoConfiguration类名的列表,以逗号分隔。

最终,@EnableAutoConfiguration注解通过@SpringBootApplication注解被间接的标记在了SpringBoot的启动类上,SpringApplicaton.run方法的内部就会执行selectImports方法,进而找到所有JavaConfig配置类全限定名对应的class,然后将所有自动配置类加载到IOC容器中。

总结

SpringBoot启动的时候通过@EnableAutoConfiguration注解找到META-INF/spring.factories文件中的所有自动配置类,并对其加载,这些自动配置类都是以AutoConfiguration结尾来命名的。它实际上就是一个JavaConfig形式的IOC容器配置类,通过以Properties结尾命名的类中取得在全局配置文件中配置的属性,如server.port。
*Properties类的含义:封装配置文件的相关属性。
*AutoConfiguration类的含义:自动配置类,添加到IOC容器中。

5:什么是 YAML?

YAML 是一种人类可读的数据序列化语言。它通常用于配置文件。与属性文件相比,如果我们想要在配置文件中添加复杂的属性,YAML 文件就更加结构化,而且更少混淆。可以看出 YAML 具有分层配置数据。
YAML 配置的优势在哪里 ?

YAML 现在可以算是非常流行的一种配置文件格式了,无论是前端还是后端,都可以见到 YAML 配置。那么 YAML 配置和传统的 properties 配置相比到底有哪些优势呢?

   ● 配置有序,在一些特殊的场景下,配置有序很关键
   ●支持数组,数组中的元素可以是基本数据类型也可以是对象
   ● 简洁

6:Spring Boot 是否可以使用 XML 配置 ?

Spring Boot 推荐使用 Java 配置而非 XML 配置,但是 Spring Boot 中也可以使用 XML 配置,通过 @ImportResource 注解可以引入一个 XML 配置。

7:spring boot 核心配置文件是什么?bootstrap.properties 和 application.properties 有何区别 ?

单纯做 Spring Boot 开发,可能不太容易遇到 bootstrap.properties 配置文件,但是在结合 Spring Cloud 时,这个配置就会经常遇到了,特别是在需要加载一些远程配置文件的时侯。

spring boot 核心的两个配置文件:

    ●bootstrap (. yml 或者 . properties):boostrap 由父 ApplicationContext 加载的,比 applicaton 优先加载,配置在应用程序上下文的引导阶段生效。一般来说我们在 Spring Cloud Config 或者 Nacos 中会用到它。且 boostrap 里面的属性不能被覆盖;
    ●application (. yml 或者 . properties): 由ApplicatonContext 加载,用于 spring boot 项目的自动化配置。
 

8:如何实现 Spring Boot 应用程序的安全性?

为了实现 Spring Boot 的安全性,我们使用 spring-boot-starter-security 依赖项,并且必须添加安全配置。它只需要很少的代码。配置类将必须扩展WebSecurityConfigurerAdapter 并覆盖其方法。

9:Spring Boot 中的 starter 到底是什么 ?

首先,这个 Starter 并非什么新的技术点,基本上还是基于 Spring 已有功能来实现的。首先它提供了一个自动化配置类,一般命名为 XXXAutoConfiguration ,在这个配置类中通过条件注解来决定一个配置是否生效(条件注解就是 Spring 中原本就有的),然后它还会提供一系列的默认配置,也允许开发者根据实际情况自定义相关配置,然后通过类型安全的属性注入将这些配置属性注入进来,新注入的属性会代替掉默认属性。正因为如此,很多第三方框架,我们只需要引入依赖就可以直接使用了。当然,开发者也可以自定义 Starter

spring-boot-starter-parent 有什么用 ?

我们都知道,新创建一个 Spring Boot 项目,默认都是有 parent 的,这个 parent 就是 spring-boot-starter-parent ,spring-boot-starter-parent 主要有如下作用:

   ● 定义了 Java 编译版本为 1.8 。
   ● 使用 UTF-8 格式编码。
   ● 继承自 spring-boot-dependencies,这个里边定义了依赖的版本,也正是因为继承了这个依赖,所以我们在写依赖时才不需要写版本号。
    ● 执行打包操作的配置。
    ● 自动化的资源过滤。
    ● 自动化的插件配置。
    ● 针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml。

10:Spring Boot 打成的 jar 和普通的 jar 有什么区别 ?

Spring Boot 项目最终打包成的 jar 是可执行 jar ,这种 jar 可以直接通过 java -jar xxx.jar 命令来运行,这种 jar 不可以作为普通的 jar 被其他项目依赖,即使依赖了也无法使用其中的类。

Spring Boot 的 jar 无法被其他项目依赖,主要还是他和普通 jar 的结构不同。普通的 jar 包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 \BOOT-INF\classes 目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置,将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。

重点:

Spring Boot自动装配

十:Spring Cloud

1:为什么需要学习Spring Cloud

不论是商业应用还是用户应用,在业务初期都很简单,我们通常会把它实现为单体结构的应用。但是,随着业务逐渐发展,产品思想会变得越来越复杂,单体结构的应用也会越来越复杂。这就会给应用带来如下的几个问题:

    ●代码结构混乱:业务复杂,导致代码量很大,管理会越来越困难。同时,这也会给业务的快速迭代带来巨大挑战;
    ●开发效率变低:开发人员同时开发一套代码,很难避免代码冲突。开发过程会伴随着不断解决冲突的过程,这会严重的影响开发效率;
    ●排查解决问题成本高:线上业务发现 bug,修复 bug 的过程可能很简单。但是,由于只有一套代码,需要重新编译、打包、上线,成本很高。

由于单体结构的应用随着系统复杂度的增高,会暴露出各种各样的问题。近些年来,微服务架构逐渐取代了单体架构,且这种趋势将会越来越流行。Spring Cloud是目前最常用的微服务开发框架,已经在企业级开发中大量的应用。
 

2:优缺点

微服务的框架那么多比如:dubbo、Kubernetes,为什么就要使用Spring Cloud的呢?

优点:

    ●产出于Spring大家族,Spring在企业级开发框架中无人能敌,来头很大,可以保证后续的更新、完善
    ●组件丰富,功能齐全。Spring Cloud 为微服务架构提供了非常完整的支持。例如、配置管理、服务发现、断路器、微服务网关等;
    ●Spring Cloud 社区活跃度很高,教程很丰富,遇到问题很容易找到解决方案
    ●服务拆分粒度更细,耦合度比较低,有利于资源重复利用,有利于提高开发效率
    ●可以更精准的制定优化服务方案,提高系统的可维护性
    ●减轻团队的成本,可以并行开发,不用关注其他人怎么开发,先关注自己的开发
    ●微服务可以是跨平台的,可以用任何一种语言开发
    ●适于互联网时代,产品迭代周期更短

缺点:

    ●微服务过多,治理成本高,不利于维护系统
    ●分布式系统开发的成本高(容错,分布式事务等)对团队挑战大

总的来说优点大过于缺点,目前看来Spring Cloud是一套非常完善的分布式框架,目前很多企业开始用微服务、Spring Cloud的优势是显而易见的。因此对于想研究微服务架构的同学来说,学习Spring Cloud是一个不错的选择。
 

3:SpringBoot和SpringCloud的区别?

●SpringBoot专注于快速方便的开发单个个体微服务。

●SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,

●为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务

●SpringBoot可以离开SpringCloud独立使用开发项目, 但是SpringCloud离不开SpringBoot ,属于依赖的关系

●SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。
 

4:Spring Cloud 和dubbo区别?

(1)服务调用方式 dubbo是RPC ,springcloud Rest Api

(2)注册中心,dubbo 是zookeeper springcloud是eureka,也可以是zookeeper

(3)服务网关,dubbo本身没有实现,只能通过其他第三方技术整合,springcloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发,springcloud支持断路器,与git完美集成配置文件支持版本控制,事物总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素。

5:Spring Cloud和SpringBoot版本对应关系

6:CAP

通俗理解CAP理论大白话-CSDN博客文章浏览阅读12次。对于刚刚接触分布式系统的小伙伴们来说,一提起分布式系统,就感觉高大上,深不可测。而且看了很多书和视频还是一脸懵逼。这篇文章主要使用大白话的方式,带你理解一下分布式系统中的CAP理论。保证你能听懂。为了防止被误以为是洗文的嫌疑,我在这里先说明一下:我参考了知乎和博客园上等相关文章,还有下面的图不是我自己画的,我觉得能清晰地表达出意思就是好图,在百度图片上下载了一波。一、什么是分布式系统拿一个最简单的例子,就比如说我们的图书管理系统。之前的系统包含了所有的功能,比如用户注册登录、管理员功能、图书借阅https://blog.csdn.net/CarryBest/article/details/115791859?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22115791859%22%2C%22source%22%3A%22CarryBest%22%7D

7:Eureka

Eureka是Netflix开发的服务发现框架,集成在其子项目spring-cloud-netflix中,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。
Eureka是一个服务治理组件,它主要包括服务注册和服务发现,主要用来搭建服务注册中心。属于CAP理论中的AP

Eureka包含两个组件: Eureka Server和Eureka Client
Eureka Server

提供服务注册服务各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到,只需要新建一个Spring boot服务,启动类添加@EnableEurekaServer即可

package com.carry.www.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * 类描述:
 *
 * @author :carry
 * @version: 1.0  CreatedDate in  2020年04月30日
 * <p>
 * 修订历史: 日期			修订者		修订描述
 */
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication{

        public static void main(String[] args) {
            SpringApplication.run(EurekaApplication.class, args);
        }

}

bootstrap.yml

server:
  port: 8060 //注册中心端口号
  
spring:
  application:
    name: eureka-server//注册中心名字

eureka:
  instance:
    hostname: eureka
    appname: eureka-server 
    prefer-ip-address: false #将hostname 注册到Eureka Server上  http://eureka:8060/
    instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}} # 在信息列表时显示主机名称
    lease-renewal-interval-in-seconds: 30    # 续约更新时间间隔(默认30秒),eureka客户端向服务端发送心跳的时间间隔
    lease-expiration-duration-in-seconds: 90 # 设置心跳的周期间隔,续约到期时间(默认90秒)
  client: # 客户端进行Eureka注册的配置
    register-with-eureka: true  # 当前的微服务注册到eureka之中(多个注册中心需要)
    fetch-registry: false #表示是否从Eureka Server上获取注册信息,默认为true,作为注册中心不需要
    service-url:
      defaultZone: http://127.0.0.1:8060/eureka/
  server:
    eviction-interval-timer-in-ms: 4000  # 设置清理的间隔时间,而后这个时间使用的是毫秒单位(默认是60秒)
    enable-self-preservation: false  #关闭保护模式,用来预防网络问题导致的服务剔除
    renewal-percent-threshold: 0.9

management:
  endpoints:
    web:
      exposure:
        include: '*'


EurekaClient
通过注册中心进行相互访问,是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)。添加注解@EnableDiscoveryClient。

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableHystrix
@EnableDiscoveryClient
@MapperScan(basePackages = {"com.carry.www.account.mapper"})
@ComponentScan(basePackages = "com.carry.*")//注解util类
public class AccountApplication implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(AccountApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {

        System.out.println("###################### AccountApplication 服务启动完成!######################");

    }

}

application.yml

server:
  port: 8001

spring:
  application:
    name: service-account  // 此名字用来对外提供访问

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8060/eureka/ //注册中心地址


 Eureak原理以及相关源码分析

 Eureak原理以及相关源码分析-CSDN博客文章浏览阅读2次。@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED, rollbackFor = {Throwable.class}, readOnly = false)传播行为:propagation = Propagation.REQUIRES_NEW 不生效问题因为:由于this对象没被spring代理生成新的代理对象,而是本类的上下文方法上的事物注解不会起作用当前类的对象没有https://blog.csdn.net/CarryBest/article/details/116594376?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22116594376%22%2C%22source%22%3A%22CarryBest%22%7D

 spring cloud 高可用eureka注册中心

https://carry.blog.csdn.net/article/details/102605888icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/102605888

8:Rest+Ribbon

Srping Cloud ribbon是基于Netfix Ribbo实现的一套客户端 负债均衡工具

Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。

Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤繁琐

使用

启动类注册一个RestTemplate,加上@LoadBalanced开启负载均衡

@SpringBootApplication
@EnableDiscoveryClient
public class ServiceTestRibbonApplication {

	public static void main(String[] args) {
		SpringApplication.run(ServiceTestRibbonApplication .class, args);
	}

	@Bean
	@LoadBalanced
	RestTemplate restTemplate() {
		return new RestTemplate();
	}

}

@Service
public class TestService {

    @Autowired
    RestTemplate restTemplate;

    public String hiService(String name) {
        return restTemplate.getForObject("http://服务名/方法?参数="+参数,String.class);
    }

}

9:OpenFeign

Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。默认集成了Ribbon,实现了负载均衡的效果。

1.feign采用的是基于接口的注解
2.feign整合了ribbon,具有负载均衡的能力
3.整合了Hystrix,具有熔断的能力

OpenFeign和Feign的区别

Feign:

Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。

Feign是在2019就已经不再更新了,通过maven网站就可以看出来,随之取代的是OpenFeign,从名字上就可以知道,他是Feign的升级版。

OpenFeign:

OpenFeign是Spring Cloud 在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
 

 使用

启动类加上@EnableFeignClients注解即可

调用其他服务

接口层,新建其他服务的接口(方法名、参数等需要一致)


@FeignClient(value = "其他服务名")
public interface AccountClient {
    @RequestMapping(value = "/其他服务方法名",method = RequestMethod.GET)
    String reduceAccount(@RequestParam String accountId, @RequestParam BigDecimal money);
}




 Controller层

@RestController
@RequestMapping(value = "/order")
public class OrderController {

    @Autowired
    private AccountClient accountClient;

    @Autowired
    private GoodClient goodClient;

    @Autowired
    private OrderService orderService;

   /**
    * @方法描述: 下单服务
    * @Param: [goodId 商品id, accountId 账户id, buyCount 商品数量]
    * @return: java.lang.String
    * @Author: carry
    */
    @PostMapping("/submitOrder")
    @GlobalTransactional
    public String submitOrder(
            @RequestParam("goodId") String goodId,
            @RequestParam("accountId") String accountId,
            @RequestParam("buyCount") int buyCount) {

        Good good = goodClient.getGoodById(goodId);
        BigDecimal orderPrice = good.getPrice().multiply(new BigDecimal(buyCount));
        // 减少商品库存
        goodClient.reduceStock(goodId, buyCount);
        // 减少账户金额
        accountClient.reduceAccount(accountId, orderPrice);

        Order order = toOrder(goodId, accountId, orderPrice);
        orderService.addOrder(order);

        return "下单成功.";
    }

    /**
     * @方法描述:  封装订单信息
     * @Param: [goodId 商品id, accountId 账户id, orderPrice 订单金额]
     * @return: com.carry.www.openfeign.domin.Order
     * @Author: carry
     */
    private Order toOrder(String goodId, String accountId, BigDecimal orderPrice) {
        Order order = new Order();
        order.setGoodId(goodId);
        order.setAccountId(accountId);
        order.setPrice(orderPrice);

        return order;
    }

}

超时处理

如果openFeign没有设置对应得超时时间,那么将会采用Ribbon的默认超时时间。

两种方案如下:

  • 设置openFeign的超时时间
  • 设置Ribbon的超时时间
1、设置Ribbon的超时时间(不推荐)

设置很简单,在配置文件中添加如下设置:

ribbon:
  # 值的是建立链接所用的时间,适用于网络状况正常的情况下, 两端链接所用的时间
  ReadTimeout: 5000
  # 指的是建立链接后从服务器读取可用资源所用的时间
  ConectTimeout: 5000
2、设置openFeign的超时时间(推荐)

openFeign设置超时时间非常简单,只需要在配置文件中配置,如下:

default设置的是全局超时时间,对所有的openFeign接口服务都生效

feign:
  client:
    config:
      ## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
      default:
        connectTimeout: 5000
        readTimeout: 5000

给服务单独配置一个超时时间,配置如下:

feign:
  client:
    config:
      ## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
      default:
        connectTimeout: 5000
        readTimeout: 5000
      ## 为serviceC这个服务单独配置超时时间
      serviceA:
        connectTimeout: 30000
        readTimeout: 30000

        

10:Hystrix

史上最简单的SpringCloud教程 | 第四篇:断路器(Hystrix)(Finchley版本)_spring cloud断路器使用步骤-CSDN博客文章浏览阅读10w+次,点赞68次,收藏92次。转载请标明出处: http://blog.csdn.net/forezp/article/details/69934399 本文出自方志朋的博客在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或..._spring cloud断路器使用步骤https://blog.csdn.net/forezp/article/details/81040990

11:Sentinel

常见的熔断降级框架有Hystrix、Sentinel,openFeign默认支持的就是Hystrix,但是阿里的Sentinel无论是功能特性、简单易上手等各方面都完全秒杀Hystrix 

 使用

配置文件中开启sentinel熔断降级,要想openFeign使用sentinel的降级功能,还需要在配置文件中开启,添加如下配置:

feign:
  sentinel:
    enabled: true

添加降级回调类

这个类一定要实现openFeign的接口,如下图:

@Component
@Slf4j
public class AccountClientFallbackService implements AccountClient{

    @Override
    public void reduceAccount(String accountId, BigDecimal money) {
        log.error("######## reduceAccount出错,服务降级 ##########");
    }

}
@FeignClient(name = "api-account",fallback = AccountClientFallbackService.class)
public interface AccountClient {

    /**
     * @方法描述: 扣除账户金额
     * @Param: [accountId 账户编号, money 金额]
     * @return: void
     * @Author: carry
     */
    @PostMapping("/reduceAccount")
    void reduceAccount(@RequestParam String accountId, @RequestParam BigDecimal money);
}

一旦AccountClient中对应得接口出现了异常则会调用这个类中对应得方法进行降级处理。

其他

https://forezp.blog.csdn.net/article/details/115632888icon-default.png?t=N7T8https://forezp.blog.csdn.net/article/details/115632888

12:Spring Cloud Config

史上最简单的SpringCloud教程 | 第六篇: 分布式配置中心(Spring Cloud Config)(Finchley版本)_java spring cloud moduleconfig-CSDN博客文章浏览阅读10w+次,点赞54次,收藏103次。转载请标明出处: http://blog.csdn.net/forezp/article/details/70037291 本文出自方志朋的博客在上一篇文章讲述zuul的时候,已经提到过,使用配置服务来保存各个服务的配置文件。它就是Spring Cloud Config。一、简介在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要..._java spring cloud moduleconfighttps://blog.csdn.net/forezp/article/details/81041028

https://carry.blog.csdn.net/article/details/107315205icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/107315205

13:Spring Cloud Gateway

Spring Cloud Gateway初体验_springcloudgateway gzip-CSDN博客文章浏览阅读10w+次,点赞31次,收藏114次。转载请标明出处:https://www.fangzhipeng.com/springcloud/2018/11/06/sc-f-gateway1/本文出自方志朋的博客这篇文章讲述了如何简单地使用Spring Cloud Gateway,来源于Spring Cloud官方案例,地址https://spring.io/guides/gs/gateway 。简介Spring Cloud G..._springcloudgateway gziphttps://blog.csdn.net/forezp/article/details/83792388

14:Spring Security

Spring Security在架构上将认证与授权分离,并提供了扩展点。它是一个轻量级的安全框架,它确保基于Spring的应用程序提供身份验证和授权支持。它与Spring MVC有很好地集成 ,并配备了流行的安全算法实现捆绑在一起。

SpringSecurity执行流程?

1.客户端发起一个请求,进入 Security 过滤器链。
2.当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。
3.当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。
4.当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。

主要类

SecurityConfig.java
新建SecurityConfig类继承WebSecurityConfigurerAdapter
package com.carry.www.common.config;


import com.carry.www.security.UserAuthenticationProvider;
import com.carry.www.security.UserPermissionEvaluator;
import com.carry.www.security.handle.*;
import com.carry.www.security.jwt.JWTAuthenticationTokenFilter;
import com.carry.www.utils.constant.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * 类描述: SpringSecurity核心配置类
 *
 * @author carry
 * @version 1.0        CreateDate: 2020年2月24日
 * <p>
 * 修订历史:
 * 日期			修订者		修订描述
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限注解,默认是关闭的 使用表达式方法级别的安全性
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 自定义登录成功处理器
     */
    @Autowired
    private UserLoginSuccessHandler userLoginSuccessHandler;

    /**
     * 自定义登录失败处理器
     */
    @Autowired
    private UserLoginFailureHandler userLoginFailureHandler;

    /**
     * 自定义注销成功处理器
     */
    @Autowired
    private UserLogoutSuccessHandler userLogoutSuccessHandler;

    /**
     * 自定义暂无权限处理器
     */
    @Autowired
    private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;

    /**
     * 自定义未登录的处理器
     */
    @Autowired
    private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;

    /**
     * 自定义登录逻辑验证器
     */
    @Autowired
    private UserAuthenticationProvider userAuthenticationProvider;

    /**
     * 集成第三方单点登录
     */
    @Autowired
    BaseUrl baseUrl;

    /**
     * @方法描述:  加密方式
     * @Param: []
     * @return: org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
     * @Author: carry
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * @方法描述:  自定义权限验证
     * @Param: []
     * @return: org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler
     * @Author: carry
     */
    @Bean
    public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler() {
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(new UserPermissionEvaluator());
        return handler;
    }

    /**
     * @方法描述:   自定义登录验证逻辑
     * @Param: [auth]
     * @return: void
     * @Author: carry
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        //这里可启用我们自己的登陆验证逻辑
        auth.authenticationProvider(userAuthenticationProvider);
    }

    /**
     * @方法描述: security主配置
     * @Param: [http]
     * @return: void
     * @Author: carry
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //####################登录、退出等验证 START###################################################
        http.authorizeRequests()
                // 不进行权限验证的请求或资源(从配置文件中读取,比如主页、登录、spring boot admin监控页面)
                .antMatchers(Constants.antMatchers.split(",")).permitAll()
                // 其他的需要身份验证
                .anyRequest().authenticated()
                .and()
                // 配置未登录自定义处理类
                .httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler)
                .and()
                // 配置登录地址,前端需要form表单的形式提交
                .formLogin()
                .loginProcessingUrl("/login/userLogin")
                // 配置登录成功自定义处理类
                .successHandler(userLoginSuccessHandler)
                // 配置登录失败自定义处理类
                .failureHandler(userLoginFailureHandler)
                .and()
                // 配置登出地址
                .logout()
                .logoutUrl("/login/userLogout")
                // 配置用户登出自定义处理类
                .logoutSuccessHandler(userLogoutSuccessHandler)
                .and()
                // 配置没有权限自定义处理类
                .exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler)
                .and()
                // 开启跨域
                .cors()
                .and()
                // 取消跨站请求伪造防护
                .csrf().disable();
        //####################登录、退出等验证 END###################################################

        //####################jwt 拦截 START###################################################
        // 基于Token不需要session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 基于Token禁用缓存
        http.headers().cacheControl();
        // 添加JWT过滤器
        http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));
        //####################jwt 拦截 START###################################################

        //####################ssoLogin拦截 登录前拦截 START###################################################
        http.addFilterBefore(new SelfSsoAuthenticationFilter("/login/userLogin", baseUrl.getHttpUlr()), UsernamePasswordAuthenticationFilter.class);
        //####################ssoLogin拦截 登录前拦截 END###################################################

    }
}
UserLoginSuccessHandler.java
package com.carry.www.security.handle;

import com.carry.www.common.utils.JWTTokenUtil;
import com.carry.www.common.utils.ResultUtil;
import com.carry.www.core.service.AuthService;
import com.carry.www.security.entity.SelfUserDetails;
import com.carry.www.utils.base.DateUtils;
import com.carry.www.utils.base.IpUtils;
import com.carry.www.utils.base.UUIDUtil;
import com.carry.www.utils.constant.Constants;
import com.carry.www.utils.redis.RedisUtils;
import com.carry.www.utils.spring.SpringUtils;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

/**
 * 登录成功处理类
 * 此处的2个token 1:真正的服务器token  2:返回给前台的虚拟token:uuid,也就是真正token的key
 * @author carry
 * @version 1.0 CreateDate: 2020年2月24日
 * <p>
 * 修订历史: 日期 修订者 修订描述
 */
@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private AuthService authService;

    private Map<String, Object> logParam = new HashMap<String, Object>();

    /**
     * 登录成功返回结果
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) {
        RedisUtils redisUtils = SpringUtils.getBean(RedisUtils.class);

        //获取用户身份信息
        SelfUserDetails selfUserDetails = (SelfUserDetails) authentication.getPrincipal();
        String username = "";
        if (selfUserDetails != null) {
            username = selfUserDetails.getUsername();
        }

        // 根据当前登录用户名,判断redis中,是否已经生成过token(多个浏览器同时登陆,只生成一个真实的token)
        String realToken = String.valueOf(redisUtils.get(Constants.APP_NAME + "_" + username + "_token"));
        // JWT判断 token 无效时 重新生成token 放入 redis中,否则使用原有的token
        if (StringUtils.isNotBlank(realToken) && JWTTokenUtil.isExpiration(realToken)) {
            realToken = JWTTokenUtil.createAccessToken(selfUserDetails);
            //和JWT失效时间一致 Constants.expiration
            redisUtils.set(Constants.APP_NAME + "_" + username + "_token", realToken, Constants.EXPIRATION_12);
        }

        // 生成返回给客户端的唯一标识,虚拟token,前端用
        // 不同的浏览器同时登陆同一用户,对应同一个后端token,所以此处使用多个uuid作为key,各自的退出互不影响
        String uuid = UUIDUtil.createUUID();
        // 存tokenId ,虚拟token, 后台获取用
        selfUserDetails.setTokenId(uuid);
        // 将 uuid 唯一标识 与 token 的关联关系 存储在 redis 中
        redisUtils.set(uuid, realToken);

        //###########################其他数据放缓存 START###############################
        //将当前登录的用户信息放入redis
        redisUtils.set(uuid+Constants.APP_NAME + "_" + username + "_token", JSONObject.fromObject(selfUserDetails).toString());

        //###########################其他数据放缓存 END###############################


        //###########################获取用户角色集合 START###############################
        Collection<GrantedAuthority> userRolesCollations = selfUserDetails.getAuthorities();
        List<String> roleList = new LinkedList<String>();
        for (GrantedAuthority grantedAuthority : userRolesCollations) {
            String authority = grantedAuthority.getAuthority();
            roleList.add(authority.replace("ROLE_", ""));
        }

        String[] roleIds = roleList.toArray(new String[0]);
        selfUserDetails.setRoleIds(roleIds);
        //###########################获取用户角色集合 END###############################


        // ####################################记录登录日志 START####################################
        logParam.put("id", uuid);
        logParam.put("user_id",selfUserDetails.getId());
        logParam.put("user_name", username);
        logParam.put("ip", IpUtils.getIpAddr(request));
        logParam.put("mark", "用户认证成功");
        logParam.put("time", DateUtils.getNowDateTimeFmt("yyyy-MM-dd hh24:mi:ss"));
        logParam.put("type", "0");
        logParam.put("mark", "用户认证成功");

        ServletContext sc = request.getServletContext();
        WebApplicationContext cxt = WebApplicationContextUtils.getWebApplicationContext(sc);
        if (cxt != null && cxt.getBean(AuthService.class) != null && authService == null) {
            authService = cxt.getBean(AuthService.class);
        }
        try {
            authService.addLoginLog(logParam);
        } catch (Exception e) {

            System.out.println(e.getMessage());
        }
        // ####################################记录登录日志 END####################################

        // ###############################封装返回参数 给前台 START ##################################
        Map<String, Object> resultData = new HashMap<>();
        resultData.put("code", 0);
        resultData.put("type", Constants.TOKEN_PREFIX);
        resultData.put("token", uuid);
        resultData.put("user", selfUserDetails);
        resultData.put("msg", "登录成功");
        // ###############################封装返回参数 给前台 END ##################################

        ResultUtil.responseJson(response, resultData);
    }
}

工程截图

Spring Security 自定义登录认证管理以及抛出自定义信息

https://carry.blog.csdn.net/article/details/102929683icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/102929683

14:Nacos

Nacos 代替 eureka 成为注册中心

https://carry.blog.csdn.net/article/details/107340194icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/107340194

Nacos动态路由配置

https://carry.blog.csdn.net/article/details/112985659icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/112985659


 https://carry.blog.csdn.net/article/details/113114079icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/113114079

15: 微服务监控spring boot admin简单使用

https://carry.blog.csdn.net/article/details/103367388icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/103367388

重点

CAP

为什么要用Spring Cloud 而不用 dubbo?

Eureak和Zookeeper的相同点和不同

Eureak原理源码

十一:Redis

redis又叫非关系型数据库、内存型数据库、key-value数据库

1:基础知识

https://thinkwon.blog.csdn.net/article/details/103522351icon-default.png?t=N7T8https://thinkwon.blog.csdn.net/article/details/103522351

2:Redis 线程模型

Redis 线程模型-CSDN博客文章浏览阅读3次。文件事件处理器(file event handler)Redis 基于 Reactor 模式开发了自己的网络事件处理器: 这个处理器被称为文件事件处理器(file event handler)文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文https://blog.csdn.net/CarryBest/article/details/113613459?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22113613459%22%2C%22source%22%3A%22CarryBest%22%7D

3:数据类型(以及底层结构)

  • String:简单动态字符串(SDS)
  • List:双向链表(linkedlist)、压缩列表(ziplist)
  • Hash:压缩列表(ziplist)、哈希表(dictionary)
  • Sorted Set:压缩列表(ziplist)、跳表(skiplist)
  • Set:哈希表(dictionary)、整数数组(intset)

Redis - SDS

Redis - SDS-CSDN博客Redis深入浅出——字符串和SDS_二月的博客的博客-CSDN博客_redis sdshttps://blog.csdn.net/CarryBest/article/details/124278972?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22124278972%22%2C%22source%22%3A%22CarryBest%22%7D

Redis-List

Redis-List-CSDN博客Redis底层数据结构之listhttps://blog.csdn.net/CarryBest/article/details/129520693?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22129520693%22%2C%22source%22%3A%22CarryBest%22%7D

Redis-Sorted Set漫画:什么是跳表? - 知乎这是发生在很多年以前的故事...... 几天以前...... 几天之后...... 拍卖行的商品总数量有几十万件,对应数据库商品表的几十万条记录。 如果是按照商品名称精确查询还好办,可以直接从数据库查出来,最多也就上百条…icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/53975333


4:Redis的四种模式:单机、主从、哨兵、集群简介

Redis的四种模式:单机、主从、哨兵、集群简介-CSDN博客Redis的四种模式:单机、主从、哨兵、集群简介 (qq.com)https://blog.csdn.net/CarryBest/article/details/128865748?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22128865748%22%2C%22source%22%3A%22CarryBest%22%7D

 5:windows下redis一主二从三哨兵搭建

https://carry.blog.csdn.net/article/details/113878939icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/113878939

6:Redis和MySQL如何保持数据一致性(最终一致性)

Redis和MySQL如何保持数据一致性(最终一致性)?-CSDN博客文章浏览阅读2次。Redis和MySQL如何保持数据一致性?https://blog.csdn.net/CarryBest/article/details/129055060?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22129055060%22%2C%22source%22%3A%22CarryBest%22%7D

7:如何解决Redis大key问题

https://carry.blog.csdn.net/article/details/114668684icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/114668684

8:Redis常用场景以及常用数据结构

 ●用作缓存

项目中国家省市区、一些不常变的数据,常用Hash

项目中集合的存储、音乐榜单、大V粉丝展示等,常用List

 ●用于存储Token

常用String

 ●用于分布式锁

9:Spring Boot 兼容Redis、Redisson单机和哨兵模式

Spring Boot 兼容Redis、Redisson单机和哨兵模式-CSDN博客文章浏览阅读8次。【代码】Spring boot 配置redis、redisson单机和哨兵模式。https://blog.csdn.net/CarryBest/article/details/132472365?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22132472365%22%2C%22source%22%3A%22CarryBest%22%7D

10:Redis事务

https://carry.blog.csdn.net/article/details/123257759icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/123257759

11:Redis Cluster 哈希槽

https://carry.blog.csdn.net/article/details/129314252icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/129314252

12:Spring Cloud + Redis 实现重复提交接口幂等性

Spring Cloud + Redis 实现重复提交接口幂等性-CSDN博客文章浏览阅读2次。幂等性原本是数学上的概念,即使公式:f(x)=f(f(x)) 能够成立的数学性质。用在编程领域,则意为对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的。接口的增删改查操作:1.查询是天然的幂等操作;2.删除一次和多次删除都是把数据删除。(返回结果可能不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个,在不考虑返回结果的情况下,删除操作也具有幂等性);3.修改大多数是幂等的,但如果是增量修改需要保证幂等性;4.新增当然不是幂等的。流程如下:1.前端弹https://blog.csdn.net/CarryBest/article/details/114064261?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22114064261%22%2C%22source%22%3A%22CarryBest%22%7D

重点 

Redis线程模型

数据类型(以及底层结构)

Redis和MySQL如何保持数据一致性(最终一致性)

如何解决Redis大key问题

Redis常用场景以及常用数据结构

十二:Mysql

1:基础

https://thinkwon.blog.csdn.net/article/details/104778621icon-default.png?t=N7T8https://thinkwon.blog.csdn.net/article/details/104778621

2:计算机组成原理与mysql的关系

 https://carry.blog.csdn.net/article/details/116128290icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/116128290

https://carry.blog.csdn.net/article/details/116133093icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/116133093

3:Mysql体系架构


https://carry.blog.csdn.net/article/details/121910593icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/121910593

4:MySQL InnoDB存储引擎

MySQL InnoDB存储引擎-CSDN博客MySQL InnoDB存储引擎MySQL InnoDB存储引擎。https://blog.csdn.net/CarryBest/article/details/130244487?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22130244487%22%2C%22source%22%3A%22CarryBest%22%7D

5: Mysql InnoDB引擎的4大特性

Mysql InnoDB引擎的4大特性-CSDN博客InnoDB引擎的4大特性InnoDB引擎的4大特性InnoDB引擎的4大特性https://blog.csdn.net/CarryBest/article/details/129303623?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22129303623%22%2C%22source%22%3A%22CarryBest%22%7D

6:MySQL- Connection Pool(数据库连接池)

https://carry.blog.csdn.net/article/details/122041131icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/122041131

7:MySQL-锁

https://carry.blog.csdn.net/article/details/121910617icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/121910617

8: MySQL-Cache和Buffer(高速缓存区)

MySQL-Cache和Buffer(高速缓存区)-CSDN博客MySQL-Cache和Buffer(高速缓存区)https://blog.csdn.net/CarryBest/article/details/122048488?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22122048488%22%2C%22source%22%3A%22CarryBest%22%7D

9: MySQL-日志

https://blog.csdn.net/CarryBest/article/details/121910675?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22121910675%22%2C%22source%22%3A%22CarryBest%22%7Dicon-default.png?t=N7T8https://blog.csdn.net/CarryBest/article/details/121910675?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22121910675%22%2C%22source%22%3A%22CarryBest%22%7D

● binlog

https://blog.csdn.net/CarryBest/article/details/129746967?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22129746967%22%2C%22source%22%3A%22CarryBest%22%7Dicon-default.png?t=N7T8https://blog.csdn.net/CarryBest/article/details/129746967?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22129746967%22%2C%22source%22%3A%22CarryBest%22%7D

● redo log

MySQL redo log-CSDN博客Mysql redo logMysql redo loghttps://blog.csdn.net/CarryBest/article/details/124475624?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22124475624%22%2C%22source%22%3A%22CarryBest%22%7D

● undo log

MySQL undo log-CSDN博客mysql undologmysql undologhttps://blog.csdn.net/CarryBest/article/details/129853280?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22129853280%22%2C%22source%22%3A%22CarryBest%22%7D

10:MySQL-主从复制

https://blog.csdn.net/CarryBest/article/details/121910694?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22121910694%22%2C%22source%22%3A%22CarryBest%22%7Dicon-default.png?t=N7T8https://blog.csdn.net/CarryBest/article/details/121910694?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22121910694%22%2C%22source%22%3A%22CarryBest%22%7D

Windows环境MySql主从同步、读写分离的搭建(Linux类似)-CSDN博客主服务器 ip:192.168.50.97从服务器 ip:172.28.4.244同步测试用的数据库scheme:carry-middleware。https://blog.csdn.net/CarryBest/article/details/130425830?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22130425830%22%2C%22source%22%3A%22CarryBest%22%7D

11: MySQL MVCC

MySQL MVCC-CSDN博客mvccd多版本并发控制https://blog.csdn.net/CarryBest/article/details/128952716?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22128952716%22%2C%22source%22%3A%22CarryBest%22%7D

重点

Mysql体系架构

Mysql InnoDB引擎

Mysql InnoDB引擎的4大特性

索引

事务

锁(MVCC)

日志(区分mysql服务层、存储引擎层、undolog、redolog、binlog)

B+树

SQL优化

数据库优化

十三:Kafka

1:

2:kafka相关整理

https://carry.blog.csdn.net/article/details/113176138icon-default.png?t=N7T8https://carry.blog.csdn.net/article/details/113176138

十四:设计模式

单例模式、代理模式、工厂模式、模板模式、装饰器模式、观察者模式

十五:分布式事务

seata

十六:分布式锁

redission

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值