1 Star 0 Fork 0

chenchaoran / sky-ladder

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

Do More

@Author chencr

@Date 2023.05

img

0. 计算机基础

0.1 CPU/内存

0.2 IO/FS

0.3 网络/协议

0.3.1 网络拓扑

img
  • 用户设备:Laptop,用于访问网络资源。
  • 网络交换机:Switch,用于连接局域网内的设备,例如 Laptop 和 Router。
  • 路由器:Router,用于连接不同网络,将局域网与互联网相连。
  • 防火墙:Firewall,用于保护网络内部资源,阻止未经授权的访问。
  • 互联网:Internet,提供连接到其他网络和全球信息资源。
  • 服务器:Server,用于托管网络应用程序和数据。
IP地址

IPV4 - 32位。(所以数据库存储ip时,可以使用 int 类型)

IPV6 - 128位

一个网卡会有 3 个关键配置:
  • IP 地址,例如:10.0.2.15
  • 子网掩码,例如:255.255.255.0
  • 网关的 IP 地址,例如:10.0.2.2

根据这ip地址子网掩码就可以计算网络号。如果两台计算机计算出的网络号相同,说明两台计算机在同一个网络,可以直接通信。如果两台计算机计算出的网络号不同,那么两台计算机不在同一个网络,不能直接通信。

不在同一网络下的两个计算机,它们之间必须通过路由器或者交换机这样的网络设备间接通信,我们把这种设备称为网关

0.3.2 网络模型

image-20230520164055610

0.3.3 Socket 套接字

“飞鸽传书”。

0.4 数据结构

0.4.1 bit - byte

1byte = 8bit 1字节 = 8比特(位)

0.4.2 stack - heap

注意:数据结构中的堆栈 与JVM中的堆栈,不是一回事!!
  • 堆/栈:

    堆:

    栈:

    队列:

    当然,java中也有描述这些数据结构的封装类。
  • JVM堆/栈:指在虚拟机内存中的两块区域。

    堆 Heap:变量存储空间

    栈 Stack:线程空间

0.4 字符编码

常用字符编码包括 ASCII、UTF-8、UTF-16、GBK、GB2312 等。

由于二进制太长,通常记录时用十六进制,可以方便阅读。(4位二进制对应1位十六进制[0x??],1字节 = 0x??,0x??

img

0.4.1 ASCII

  • 定义了128个字符,基本对应于键盘上的 数字、英文大小写、标点、控制命令 等。
  • ascii编码字符占1个字节。(2^8=256,存128个ascii字符用1个字节绰绰有余)

0.4.2 Unicode(万国码、国际码)

  • 目前收录了约 13万 个字符(各种国家语言),且仍在不断增加。

    Unicode字符对应表:http://www.unicode.org

    汉字对应表:http://www.chi2ko.com/tool/CJK.htm

  • Unicode是一个字符集映射(理论上可以无穷大),不是一个编码方式,而是一类,如上图。

一个常见乱码:�
// 输出 efbfbdefbfbd
char[] kuijinkao = HexUtil.encodeHex("��", StandardCharsets.UTF_8);
System.out.println(kuijinkao);
// 借助 hutool 转成二进制
byte[] testBytes = HexUtil.decodeHex(kuijinkao);
// 使用 GBK 解码
String testResult = new String(testBytes, Charset.forName("GBK"));
// 输出"锟斤拷"
System.out.println(testResult);

在 Unicode 中,� 是一个特殊的符号,它用来表示无法显示,它的十六进制是 0xEF 0xBF 0xBD 二进制是11101111,10111111,10111101

两个�� = 11101111,10111111,10111101,11101111,10111111,10111101

再使用GBK解码,由于GBK默认用2个字节表示一个汉字字符,于是上述二进制码解码为3个汉字:“锟斤拷”。

0.4.3 UTF-8,16,32

  • UTF: Unicode Transformation Formats,它才是Unicode的编码存储方式。
UTF-8:可变长度编码方式
  • 表现形式(编解码规范):

0xxxxxxx:一个字节(开始为0); (与ASCII码一致)

110xxxxx 10xxxxxx:两个字节编码形式(开始两个 1);

1110xxxx 10xxxxxx 10xxxxxx:三字节编码形式(开始三个 1);

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx:四字节编码形式(开始四个 1)。

  • 优势:包罗万象。可以包含已知的所有字符,还有空间用于开发者自定义字符扩展。
  • 劣势:浪费空间

比如,汉字同一用了3个字节(2^24)来表示。但常用汉字也就几千个。

0.4.4 GB2312,GBK

  • 中华人民共和国国家标准 简体中文字符集。(GB:国标)
  • 共收录 6763 个汉字。同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的 682 个字符。
  • 对于人名、古汉语等方面出现的罕用字和繁体字,GB2312 不能处理,就有了 GBK(K 为“扩展”的意思)。

0.5 时间复杂度

o(1) 无关

从数组中根据下标获取数据

o(n) 倍数

遍历一个数组。

o(logn) 对数
  • 数据规模大幅增加,执行时间少量增加。

对数组进行二分查找

o(n^2) 平方

对数据进行嵌套遍历。冒泡排序。

o(2^n) 指数

递归求解。计算斐波那契数列。

0.6 一些算法知识

0.6.1 HashMap 数组长度为什么是 2的n次方?

对key进行hash运算,得到的int值还需要与 length进行取模运算。

int t = hash(key);
int idx = t & (len - 1); // idx决定数据在数据上的索引位

// 通常取模(即求余数):t % len
// 这里通过 '&' 与运算来进行取模运算,效率比 '%' 更高。
// 而当 len = 2^n 时, (len - 1)用二进制表示为:1111...1
// 这时 ,进行'&'与位运算会非常快,且可以减少碰撞几率,使数据在数组上分布更均匀。

0.7 一些数据结构知识

1. JVM

1.1 Tomcat容器/JVM

1.1.1 Tomcat模型

image-20230710212516977

public static void startTomcat(String hostname, int port) {

        try {
            // 设置tomcat及各种组件
            Tomcat tomcat = new Tomcat();

            Server server = tomcat.getServer();

            Service service = server.findService("Tomcat");

            Connector connector = new Connector();
            connector.setPort(port);

            StandardEngine engine = new StandardEngine();
            engine.setDefaultHost(hostname);

            StandardHost host = new StandardHost();
            host.setName(hostname);

            String contextPath = "";
            StandardContext containerContext = new StandardContext();
            containerContext.setPath(contextPath);
            containerContext.addLifecycleListener(new Tomcat.FixContextListener());

            host.addChild(containerContext);
            engine.addChild(host);
            service.setContainer(engine);
            service.addConnector(connector);

            // tomcat使用servlet来处理请求
            String servletName = "dispatcher";
            // 绑定servlet
            tomcat.addServlet(contextPath, servletName, new HttpServlet() {}); 
            // 绑定地址
            containerContext.addServletMappingDecoded("/*", servletName); 

            // 启动tomcat服务,开始接收请求
            server.await();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

1.2 JVM内存模型 (JMM)

1.2.1 内存模型

img

程序计数器:

​ 每个线程一个。保证CPU在切换线程后,能恢复到正确的执行位置(字节码行)。

​ 仅占用非常小的内存空间,且不会发生OOM。

虚拟机栈:

​ 栈中每一个栈帧对应一个被调用的方法。

本地方法栈:

​ x

堆:

​ 存储对象。所有线程共享。垃圾收集器管理的主要区域。

元空间:

​ JDK 8 的时候,原有的方法区(更准确的说应该是永久代)被彻底移除,取而代之的是元空间

​ 元空间使用本地内存,不受JVM内存限制。

image-20230528154556917

1.2.1.1 垃圾回收
“垃圾对象”定义:

​ 可达性分析算法

GC Root对象:

  • 虚拟机栈中引用的对象
  • 本地方法栈中引用的对象
  • 类静态属性引用的对象
  • 常量引用的对象

1.2.2 类加载

1.2.2.1 类加载过程

image-20230528145912544

1.2.2.2 类加载器

**对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在 JVM 中的唯一性。**也就是说,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等(比如两个类的 Class 对象不 equals)。

三种类加载器:
  • 启动类加载器(Bootstrap Class-Loader),加载 jre/lib 包下面的 jar 文件,比如说常见的 rt.jar。 (所有类加载器的顶级父类。)
  • 扩展类加载器(Extension or Ext Class-Loader),加载 jre/lib/ext 包下面的 jar 文件。
  • 应用类加载器(Application or App Clas-Loader),根据程序的类路径(classpath)来加载 Java 类。
  • 自定义类加载器(继承 java.lang.ClassLoader 类)。
1.2.2.3 双亲委派模型
概念:

如果一个类加载器收到了加载类的请求,它会先把请求委托给上层加载器去完成,上层加载器又会委托上上层加载器,一直到最顶层的类加载器;如果上层加载器无法完成类的加载工作时,当前类加载器才会尝试自己去加载这个类。

作用:

双亲委派模型能够保证同一个类最终会被特定的类加载器加载。(保证唯一性)

1.2.3 字符串常量池

String a = new String("哈哈");
  • 1.在堆中创建“哈哈”对象 —— ”new“关键字一定会在堆中创建新对象。
  • 2.在字符串常量池中创建“哈哈”对象(已有则不创建)
  • 3.返回堆中的对象地址给变量a
String a = "哈哈"; (更常用的赋值形式,也大大节省字符串占用空间)
  • 1.在字符串常量池中找“哈哈” ——双引号的方式,直接走常量池,不经过堆
  • 2.找到,则直接返回常量池中的地址;
  • 3.未找到,则直接在常量池中创建“哈哈”,返回该地址。

1.3 JVM配置参数

1.4 JVM监控/调优

1.5 强引用 - 弱引用?

1. 强引用 StrongReference

​ 垃圾回收器不会回收被引用的对象,哪怕内存不足时,JVM 也会直接抛出 OutOfMemoryError,除非赋值为 null。

2. 软引用 SoftReference

​ 软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。

3. 弱引用 WeakReference

​ 无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。

  • ThreadLocal 有一个静态内部类 ThreadLocalMap,ThreadLocalMap 又包含了一个 Entry 数组,Entry 本身是一个弱引用,他的 key 是指向 ThreadLocal 的弱引用,弱引用的目的是为了防止内存泄露,如果是强引用那么除非线程结束,否则无法终止,可能会有内存泄漏的风险。
4. PhantomReference

​ 虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用,NIO 的堆外内存就是靠其管理。

2. Java

2.1 核心思想(面向对象 object)

2.1.0 包 package

Java 内建的package机制是为了避免class命名冲突;

JDK 的核心类使用java.lang包,编译器会自动导入;

JDK 的其它常用类定义在java.util.*java.math.*java.text.*,……;

2.1.1 类 Object (*哈希,*克隆

所有类的root类。即:java中任何类的实例,都是一个Object对象。

Class<?> getClass();

boolean equals(Object obj);

String toString();

  • getClass().getName() + "@" + Integer.toHexString(hashCode());
每一个对象中,都有一个 monitor 值。用于 Thread lock.

void notify(); void notifyAll();

wait(long timeout, int nanos) throws InterruptedException;

哈希值:int hashCode();

native方法,由hotspot虚拟机提供高效实现。

存在意义:

用于支持哈希表hashTable的数据结构,继而实现对象的快速查找、插入和删除操作。

哈希算法:
  • 将任意长度的输入,变换为固定长度的输出(int整数:哈希码:对象在内存中的一种近似表示)。

  • MD5, SHA1

  • String中的哈希算法:

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;
    
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
  • HashMap中的哈希算法:

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
浅拷贝:clone()
一个类必须实现 Cloneable 接口,才能调用 clone()方法。

如下:

  • x.clone() != x
  • x.clone().getClass() == x.getClass()
浅拷贝说明:
  • a1.clone()返回的新对象a2,只拷贝当前a1对象的一层属性。

    即:如果a1对象持有其它对象b,则a2也持有同一个对象b。(内部对象不会被拷贝出新对象——浅拷贝

    于是:当a2改变b的属性时,a1.b的属性值也同时被改变!!

引申:
  • 通过序列化与反序列化,可以实现 深拷贝

2.1.2 类 Class(*反射))

该类描述一个对象的 runtime class类型。
1.) implements java.io.Serializable 一个Class< T>对象可以被序列化和反序列化存储。
2.) implements GenericDeclaration ????

TypeVariable<?>[] getTypeParameters();

3.) implements AnnotatedElement 可以获取类上的注解 Annotation

<T extends Annotation> T getAnnotation(Class<T> annotationClass);

4.) implements Type

default String getTypeName() { return toString(); }

5.) 获取一个类的各种描述信息

Field[] getDeclaredFields();

Method[] getDeclaredMethods();

Class<? super T> getSuperclass();

boolean isAssignableFrom(Class<?> cls);

......

6.) 反射能力

基本使用:

// 
static Class<?> forName(String className);

clazz.getClassLoader();
// 弃用,改为用构造器的方式
clazz.newInstance();

// 通过反射执行方法
obj = clazz.getConstructor(..) .newInstance();
method = clazz.getMethod('method', paramType);
method.setAccessible(true)
method.invoke(obj, param);

// 通过反射访问私有的 Field/Method
xxx = clazz.getDeclaredXXX();
xxx.setAccessible(true);
java.lang.reflect中的几个final类:
// 属性
public final class Field extends AccessibleObject implements Member {
    // ..
}

// (构造器)方法
public final class Constructor<T> extends Executable {
    // ..
}

// 方法
public final class Method extends Executable {

    // invoke()
    @CallerSensitive
    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            // 通过 reflectionFactory 获取 MethodAccessor
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }
    
}

reflectionFactory 返回 MethodAccessor 的代理对象DelegatingMethodAccessorImpl

image-20230520104227862

而代理对象,实际是调用的NativeMethodAccessorImpl对象来执行反射调用。

Inflation 机制。初次加载字节码实现反射,使用 Method.invoke() 和 Constructor.newInstance() 加载花费的时间是使用原生代码加载花费时间的 3 - 4 倍。这使得那些频繁使用反射的应用需要花费更长的启动时间。

实际的 MethodAccessor 实现有两个版本,一个是 Native 版本实现,一个是 Java 版本实现。
  • Native 版本一开始启动快,但是随着运行时间边长,速度变慢。Java 版本一开始加载慢,但是随着运行时间边长,速度变快。

于是在 【代理模式】DelegatingMethodAccessor 的 invoke() 方法中增加逻辑:

1.初始时使用 Native版本;

2.当调用次数 > 15次后,则改为 Java版本实现:MethodAccessorImpl

2.1.2 抽象类 ClassLoader

[通常]给定binary类名,ClassLoader从文件系统中读取类文件,构造出类定义数据。
  • 可以通过其它方式(而非文件系统)加载类数据,比如网络:NetworkClassLoader

  • jvm内置顶级父ClassLoader:"bootstrap class loader"

每个 Class 对象都包含对定义它的 ClassLoader 的引用。

clazz.getClassloader();

2.1.3 代理类 Proxy

动态代理:JDK,CGLIB
JDK

代理类必须至少实现一个接口。(基于 implements)

CGLIB

被代理类不能为final,且只拦截公共方法调用!(基于 extends)

2.2 基础

2.2.0 基础数据类型 - 引用数据类型

基础数据类型:
  • 数据值存储在栈上,变量名直接指向具体的值。
引用数据类型:
  • 变量名指向的是对象的内存地址,在栈上。
  • 内存地址指向的存储对象,则在堆上。

2.2.+ 值传递 - 引用传递?

引用传递:
  • 当一个参数按照引用传递的方式在两个方法之间传递时,调用者和被调用者其实用的是同一个变量,当该变量被修改时,双方都是可见的。
值传递:
  • 一个参数在两个方法间传递时,两个方法持有的是两个不同的变量(对象)。之后在两个方法内部的两个对象互不影响。
所以,java是值传递!!(与数据类型无关)

2.2.1 char - String - array[]

char 为基础数据类型,其对象可以是任何一个 Unicode 字符,用单引号表示。包装类为:Charactor
JVM中,一个字符char占2个字节(byte)。

char a = 'X';

int a = 'X'; // 此时发生自动类型转换,char自动转为int

int a = 66; char b = (char) a; // int可强转为char

String,最典型的引用数据类型。

java中的String类,表示 UTF-16 格式的字符串。

长度限制:

  • 编译期:2^16-1 = 65535
  • 运行时:2^31-1,且不能超过jvm提供的内存值。

内部实现:

  • java8-:char[]
  • java9+:byte[] // 占用更少内存空间

hashCode

h = 31 * h + val[i];	// “31倍哈希值”
数组,也是引用数据类型,其父类也是Object。数组对象也存储在堆中。 int[] a = new int[];

int[]:基础数据类型的数组对象没有classLoader;

Integer[]:引用数据类型的数组对象,其classLoader与其元素的classLoader一致。

封装类 Array:
// The Array class provides static methods to dynamically create and access Java arrays.
public finalclass Array {
    // 内部实现都是 native 方法
}
* Arrays工具类

2.2.2 volatile

volatile修饰的变量,在一个线程内其值被修改,可以立即在其它线程内修改可见。

volatile 保证多线程操作共享变量的可见性以及禁止指令重排序

线程本地内存
  • 线程之间的共享变量存在主内存中,每个线程都有一个私有的本地内存,存储了该线程以读、写共享变量的副本。本地内存是Java内存模型的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器等。
JMM抽象示意图
  1. 所有的共享变量都存在主内存中。
  2. 每个线程都保存了一份该线程使用到的共享变量的副本。
  3. 如果线程A与线程B之间要通信的话,必须经历下面2个步骤:
    1. 线程A将本地内存A中更新过的共享变量刷新到主内存中去。
    2. 线程B到主内存中去读取线程A之前已经更新过的共享变量。

所以,线程A无法直接访问线程B的工作内存,线程间通信必须经过主内存。

注意,根据JMM的规定,线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主内存中读取

所以线程B并不是直接去主内存中读取共享变量的值,而是先在本地内存B中找到这个共享变量,发现这个共享变量已经被更新了,然后本地内存B去主内存中读取这个共享变量的新值,并拷贝到本地内存B中,最后线程B再读取本地内存B中的新值。

那么怎么知道这个共享变量的被其他线程更新了呢?这就是JMM的功劳了,也是JMM存在的必要性之一。JMM通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性保证

2.2.3 transient

修饰的字段在对象序列化时会被忽略。

同样被忽略的还有 static 字段。

2.2.4 ==与equals

能用==时不用equals。如枚举的比较。

“==”运算符比较的时候,如果两个对象都为 null,并不会发生 NullPointerException,而 equals() 方法则会。

“==”运算符会在编译时进行检查,如果两侧的类型不匹配,会提示错误,而 equals() 方法则不会。

2.2.5 枚举类

public enum PlayerType {
    TENNIS,
    FOOTBALL,
    BASKETBALL
}

反编译为字节码后,如下:

// 默认继承 Enum
public final class PlayerType extends Enum
{

    // 默认方法 values()
    public static PlayerType[] values()
    {
        return (PlayerType[])$VALUES.clone();
    }

    // 默认方法 valueOf()
    public static PlayerType valueOf(String name)
    {
        // 调父类 Enum 的方法
        return (PlayerType)Enum.valueOf(com/cmower/baeldung/enum1/PlayerType, name);
    }

    // 默认私有构造器,带两个参数:名称、索引
    private PlayerType(String name, int ordinal)
    {
        super(name, ordinal);
    }

    // static值
    public static final PlayerType TENNIS;
    public static final PlayerType FOOTBALL;
    public static final PlayerType BASKETBALL;
    // static值集合,用于values()方法
    private static final PlayerType $VALUES[];

    // static块,初始化
    static 
    {
        TENNIS = new PlayerType("TENNIS", 0);
        FOOTBALL = new PlayerType("FOOTBALL", 1);
        BASKETBALL = new PlayerType("BASKETBALL", 2);
        
        $VALUES = (new PlayerType[] {
            TENNIS, FOOTBALL, BASKETBALL
        });
    }
}
  • 由于java类的单继承的缘故,所以枚举类(默认extends Enum)无法继承其它类。但是它可以实现接口(implements)

2.2.6 Iterable - Iterator 思想

Iterable 表示一种能力。这种能力就是:可以获取一个迭代器(Iterator)来完成迭代功能。

所以,Iterabor 是实现迭代能力的工具类。

2.2.7 Comparable - Comparator 思想

  • 原则上,它们也可以按上面的 Iterable - Iterator 思想来设计。但是并没有。

    因为:

    实现Comparable 的类,是需要将其自身与其它同类对象进行比较。所以它可以直接实现一个compareTo()方法直接调用即可。

    而实现 Iterable 的类,则是需要对其持有的内部元素集合进行迭代(而非对象自身),将这种能力抽象为一个具体的工具类更为合适。

    所以:

    2.2.6 迭代器的两个接口是有联系的。

    2.2.7 比较器的两个接口,则是无关系的。Comparator 则是独立的设计。

    但是:

    比较器也可以按迭代器的思想来设计,实现Comparable 的类提供返回一个Comparator 对象的方法,由该对象来决定排序规则。

    但,显然复杂了!

    不过:

    一个实现了Comparable 的类,重写compareTo()方法,只能提供一种排序规则。

    如果一个类需要进行多种排序方式,则直接定义多种Comparator方式,是更好的选择。

2.3 集合

2.3.1 Collection

2.3.2 List < Vector < Stack

ArrayList

基于 数组

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final int DEFAULT_CAPACITY = 10; // 默认容量为 10
    transient Object[] elementData; // 存储元素的数组,数组类型为 Object
    private int size; // 列表的大小,即列表中元素的个数
}
LikedList

基于 双端链表

Vector

与ArrayList类似,不过所有操作都加了sychronized关键字,线程安全。(效率低,不推荐使用)

Stack

栈。继承自Vector,所以也是线程安全的。(同样效率低,被双端队列Deque取代)

增加了pop()peek()等方法。FILO,后进先出。

2.3.3 Map, Set

HashMap

数组+链表+红黑树

public static void main(String[] args) {
 HashMap<Object, Object> map = new HashMap<>();
 map.put(1, 1);
 map.put(null, null);	// HashMap 中的键和值都可以为 null。如果键为 null,则将该键映射到哈希表的第一个位置。
 System.out.println(map.size());		// 2
}
负载因子 0.75
  • 默认的初始容量是 16,负载因子是 0.75 (泊松分布)。
  • 为了保持 hash冲突空间利用率 上的一个平衡。—— 散列表越小,hash冲突越大;散列表越大,越浪费空间。
  • 按默认,则添加 12 个元素之后开始扩容。x2
LinkedHashMap

HashMap 中元素按 hash值 在数组中放置,没有顺序。

LinkedHashMap 则使用数组存放数据,同时用链表来记录了元素的插入顺序

LinkedHashMap 还能维护访问顺序:

  • 每次get()一个数据后,这个数据会被放到队尾。
// 第三个参数设为 true,表示维护访问属性。否则(默认),维护插入顺序
LinkedHashMap<String, String> map = new LinkedHashMap<>(16, .75f, true);
TreeMap

红黑树

2.3.4 Queue, Deque

  • FIFO,先进先出原则
ArrayDeque

基于数组的双端队列

  • ArrayDeque与``LinkedList`实现的功能基本一致。不过内部实现不同,基于数组方便随机检索,基于链表则方便增删元素,需根据实际使用场景合理选择
PriorityQueue

基于Comparator的元素有序队列。执行 remove 或者 poll 方法,返回的总是优先级最高的元素。

2.4 异常 Throwable

img

2.4.1 Error 致命错误

2.4.2 Exception 可控异常

2.5 并发 Thread/Pool/JUC

2.5.1 类 Thread

2.5.2 ThreadPool

参数:
  • 1.corePoolSize核心线程数,线程池中始终存活的线程数。

  • 2.maximumPoolSize: 最大线程数,线程池中允许的最大线程数。

  • 3.keepAliveTime: 存活时间,线程没有任务执行时最多保持多久时间会终止。

  • 4.unit: 单位,参数keepAliveTime的时间单位,7种可选。

  • 5.workQueue: 一个阻塞队列,用来存储等待执行的任务,均为线程安全,7种可选。

  • 6.threadFactory: 线程工厂,主要用来创建线程,默及正常优先级、非守护线程。

  • 7.handler拒绝策略,拒绝处理任务时的策略,4种可选,默认为AbortPolicy。

    • AbortPolicy:直接丢弃任务,抛出异常,这是默认策略
    • CallerRunsPolicy:只用调用者所在的线程来处理任务
    • DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执行当前任务
    • DiscardPolicy:直接丢弃任务,也不抛出异常
img

2.6 设计模式 DesignModel

2.6.1 单例

常用方式:饿汉、懒汉(双重校验锁)、静态内部类、枚举

静态内部类 方式
public class Singleton {  
	// 构造器私有化
    private Singleton (){}  
	// getInstance()
    public static final Singleton getInstance() {  
    	return SingletonHolder.INSTANCE;  
    }  
    
    // 静态内部类
    private static class SingletonHolder {  
    	private static final Singleton INSTANCE = new Singleton();  
    } 
} 

静态内部类并不会在类加载时进行装载(外部类的static属性如果指定了,则会在类加载时初始化)。只有在使用到是才会进行装载。

所以实现了懒加载线程安全的特性。

枚举 方式
public enum Singleton {  
    INSTANCE;  
}
最安全、简单的单例实现:
  • 反射安全。

    其它方式虽然构造器私有化了,但是可以通过反射来创建新的实例。而枚举类无法使用反射创建。

  • 序列化/反序列化安全。

    枚举单例在序列化和反序列化前后,为同一个对象,==。

2.7 读写 IO (*序列化

输入输出 (相对内存而言)

当程序需要读取数据的时候,就会开启一个通向数据源的(通道),这个数据源可以是文件,内存,或是网络连接。

类似的,当程序需要写入数据的时候,就会开启一个通向目的地的(通道)。

  • 流:要么输入,要么输出。

一张图汇总:

img
字节流

处理二进制数据,文本、图片、MP3、视频、PPT等一切文件。

InputStream
// 从第 off 位置开始读,读取 len 长度的字节,然后放入数组 b 中
int read(byte b[], int off, int len);
// 返回可读的字节数
int available();
OutputStream
// 将数组 b 中的从 off 位置开始,长度为 len 的字节写出
void write(byte b[], int off, int len);
// 强制刷新,将缓冲区的数据写出
void flush();
字符流

只能处理文本文件。(特殊的二进制文件,包含编码格式,便于人们阅读)

  • 本身自带缓冲区
Reader
Writer

按处理对象来区别Java中的IO类:

img

数组流

在内存中处理、转换文件数据的方式。

  • 优点:仅存在内存中,无需创建临时文件可提升程序效率。
  • 缺点:在内存中,存储数据量有限,过大会导致内存溢出。
管道流

可以用于线程间的通信、数据的传递等。

缓冲

CPU 很快,它比内存快 100 倍,比磁盘快百万倍。那也就意味着,程序和内存交互会很快,和硬盘交互相对就很慢。

为了减少程序和硬盘的交互,提升程序的效率,就引入了缓冲流,也就是类名前缀带有 Buffer 的那些。

打印流

Scanner

System.out

转换流 (读写用字节,内存中处理用字符)
  • InputStreamReader —— 字节流读取文件,并转换为字符流 进行处理。
  • OutputStreamWriter —— 将字符流转换为字节流,再写到目标位置。
2.7.1 序列化/反序列化

序列化:将一个对象转换为一个字节序列(包含对象的数据对象的类型对象中存储的属性等信息),以便在网络上传输或保存到文件中,或者在程序之间传递。

反序列化:将字节序列转换为一个对象,以便在程序中使用。

serialVersionUID 序列化 ID

反序列化时,Java 虚拟机会把字节流中的 serialVersionUID 与被序列化类中的 serialVersionUID 进行比较,如果相同则可以进行反序列化,否则就会抛出序列化版本不一致的异常。

JDK自带序列化问题:
  • 可移植性差:Java 特有的,无法跨语言进行序列化和反序列化。
  • 性能差:序列化后的字节体积大,增加了传输/保存成本。
  • 安全问题:攻击者可以通过构造恶意数据来实现远程代码执行,从而对系统造成严重的安全威胁。
kyro 序列化工具
<!-- 引入 Kryo 序列化工具 -->
<dependency>
     <groupId>com.esotericsoftware</groupId>
     <artifactId>kryo</artifactId>
     <version>5.4.0</version>
</dependency>

2.8 IO模型(NIO, BIO)

2.9 锁

2.9.1 synchonized

2.9.2 Lock

2.9.3 锁升级

2.9.4 CAS

缺点:
  • ABA 问题
  • 自旋开销大

2.9.5 Reentrentlock

3. Spring

官网主页

5.2.24.RELEASE 文档

3.1 设计思想(面向bean)

  • 【】它是 Bean 的框架。围绕Bean展开的一些列设计,构筑了 Spring Frameworker 框架。

    它把开发者对对象生命周期的控制,交由 IoC容器来管理。(控制反转)

    image-20230520174347052

    Spring框架的核心配置文件,即 bean的配置文件:services.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                               https://www.springframework.org/schema/beans/spring-beans.xsd">
    	
        <bean id="" class="">
            <property name="" ref=""/>
        </bean>
        
        <bean id="" class="">
            <property name="" ref=""/>
            <property name="" value=""/>
        </bean>
    
    </beans>
    // ApplicationContext 通过外部配置文件来 加载 bean
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:/config/services.xml");

3.2 八大模块

Spring Framework 八大模块:

img

3.3 功能涵盖

image-20230520181207560

3.4 核心剖析

3.4.1 Beans 与 IoC容器

核心包:org.springframework.beans , org.springframework.context ( 容器,即 ApplicationContext )

3.4.1.1 BeanFactory

BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象,为 Spring 的 IoC 功能提供了底层基础,是访问 Spring bean 容器的根接口。

BeanFactory和相关接口(如BeanFactoryAware, InitializingBean, DisposableBean)是其他框架组件的重要集成点。通过不需要任何注释甚至反射,它们允许容器与其组件之间进行非常有效的交互。

/**	请注意,核心BeanFactory API级别及其DefaultListableBeanFactory实现没有对要使用的配置格式或任何组件注释做出假设。
	所有这些风格都是通过 扩展 (如XmlBeanDefinitionReader和AutowiredAnnotationBeanPostProcessor)实现的,并将共享BeanDefinition对象作为核心元数据表示进行操作。
	这是使Spring的容器如此灵活和可扩展的本质。
*/
Note that the core BeanFactory API level and its DefaultListableBeanFactory implementation do not make assumptions about the configuration format or any component annotations to be used. All of these flavors come in through extensions (such as XmlBeanDefinitionReader and AutowiredAnnotationBeanPostProcessor) and operate on shared BeanDefinition objects as a core metadata representation. This is the essence of what makes Springs container so flexible and extensible.
3.4.1.+ Bean Lifecycle
Bean 工厂实现应尽可能支持【 标准的 bean 生命周期】 接口。
//// 全套初始化方法及其标准顺序是:

// Aware可感知,表示bean向容器表明它需要依赖某种基础设施,然后通过set()方法提供给bean
1. BeanNameAware `setBeanName`
2. BeanClassLoaderAware `setBeanClassLoader`
3. BeanFactoryAware `setBeanFactory`
4. EnvironmentAware `setEnvironment`
5. EmbeddedValueResolverAware `setEmbeddedValueResolver`

6. ResourceLoaderAware `setResourceLoader` 仅在应用程序上下文中运行时适用 // 文件,URL资源加载
7. ApplicationEventPublisherAware `setApplicationEventPublisher` 仅在应用程序上下文中运行时适用 // 事件发布
8. MessageSourceAware `setMessageSource` 仅在应用程序上下文中运行时适用 // i18n支持等
9. ApplicationContextAware `setApplicationContext` 仅在应用程序上下文中运行时适用
10. ServletContextAware `setServletContext` 仅适用于在 Web 应用程序上下文中运行时

11. BeanPostProcessors  `postProcessBeforeInitialization`前置处理	// 等效于 @PostConstruct
12. InitializingBean `afterPropertiesSet`		// do some initialization work
13. 自定义 init() 方法						// do some initialization work
14. BeanPostProcessors  `postProcessAfterInitialization` 后置	// do some work like proxy wrapper 包装为代理类


//// 关闭 bean 工厂时,将应用以下生命周期方法:
1. DestructionAwareBeanPostProcessors  `postProcessBeforeDestruction`	// 等效于 @PreDestroy
2. DisposableBean  `destroy`		// do some destruction work (like releasing pooled connections)
3. 自定义 destroy()方法				// do some destruction work (like releasing pooled connections)
  • @PostConstruct注解、@Bean(initMetho="init()") 、自定义init()效果相同。

  • @PreDestroy注解、@Bean(destroyMethod="destroy()") 、自定义destroy()效果相同。

3.4.1.2 ApplicationContext

ApplicationContextBeanFactory的子接口,完整超集。

ApplicationContext 是容器的直接体现,负责实例化、配置和组装 bean。

3.4.1.+ ApplicationContext 拓展点(*AOP)
3.4.1.2.1 BeanPostProcessor

实现BeanPostProcessor接口的类是特殊的,被容器区别对待。

  • 由于它工作在其它bean实例化前后,那么 BeanPostProcessor的实例对象应该更早的被注册到容器中(默认为:ApplicationContext自动类型检测,且在程序启动(startup)时随着ApplicationContext一起实例化,属于容器实例化过程内 (普通bean则是在容器实例化之后。))。

  • 但是,如果需要在 BeanPostProcessor实例化之前进行一些条件校验,则可以使用ConfigurableBeanFactoryaddBeanPostProcessor 方法以编程方式注册它们。

    但是请注意:
    1. BeanPostProcessor以编程方式添加的实例不遵循Ordered接口。在这里,注册的顺序决定了执行的顺序。
    2. 另请注意,BeanPostProcessor无论任何显式排序如何,以编程方式注册的实例始终 在通过自动检测注册的实例之前处理。
著名实现类:AutowiredAnnotationBeanPostProcessor

自定义示例:

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

// 该类功能:在 “每一个” bean 实例化后 打印 beanName
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}
3.4.1.2.2 AOP
  • Spring AOP 功能,由动态代理实现。(AspectJ不是。)
  • AOP 自动代理是通过BeanPostProcessor自身实现的。
  • 所以BeanPostProcessor实例对象,以及它的依赖项bean,均无法使用AOP功能。
规则:

默认:有接口的类使用JDK代理,并实现所有接口方法。无接口时使用CGLIB代理。

若强制使用CGLIB,设置:proxy-target-class = true

AspectJ
  • AspectJ不是使用动态代理实现的,而是在 JVM 加载类文件过程中将切面 织入类文件 的方式实现的。即:动态织入

  • @EnableAspectJAutoProxy 启用AspectJ代理切面

3.4.1.2.3 BeanFactoryPostProcessor

实现BeanPostProcessor接口的类是特殊的,被容器区别对待。与BeanPostProcessor类似。

BeanFactoryPostProcessor对 bean 配置元数据进行操作 。

著名实现类:PropertySourcesPlaceholderConfigurer

该类使用标准 JavaProperties格式将 bean 定义的属性值外部化到单独的文件中(即我们通常的properties配置文件)。在程序运行时,读取配置文件并通过替换占位符 (${jdbc.url})来设置bean的属性值。

不仅在指定的文件中查找属性Properties 。默认情况下,如果它在指定的属性文件中找不到属性,它会检查 SpringEnvironment属性和常规 JavaSystem属性。

著名实现类:PropertyOverrideConfigurer

功能与上基本相似。

3.4.1.2.4 FactoryBean

该接口用于被工厂类去实现,以实现复杂的bean初始化过程。

boolean isSingleton(); // 定义该Factory生成的类是否为单例。默认:true
T getObject();			// 获取实例
Class<?> getObjectType(); // 返回实例的类型,为null表示提前不知道类型
3.4.1.3 BeanDefinition

在Spring中,BeanDefinition 描述了一个 bean 实例,它具有属性值、构造函数、参数值、Scope,以及具体实现提供的更多信息。

bean scope:
  • 通常:Singleton , Prototype

  • Web应用:Request, Session, Application, WebSocket

    @RequestScope  //将组件分配给作用域request。同类的还有:@SessionScope, @ApplicationScope
    @Component
    public class LoginAction {
        // ...
    }
          
3.4.1.4 Running bean (bean运行时)

特定 bean 的运行时类型很难确定。bean 元数据定义中的指定 class 类只是一个初始类引用。

FactoryBean

3.4.2 依赖注入 DI

ApplicationContext对于它管理的 bean,它支持基于 构造函数 和基于setter() 的 DI。

循环依赖问题
  1. 带有参数验证的构造函数注入通常是更可取的。
  2. 但是也会造成循环依赖的场景,A(B b), B(A a),此时会在运行时抛出异常。
  3. 此种情况,则推荐通过setter注入来配置循环依赖。
bean初始化时机
  1. Spring在创建好容器时会验证每个bean的配置,然后会实例化那些 作用域被定义为单例、其为 not lazy 的bean对象。

  2. bean的属性会在实际bean创建后才会设置。

  3. 每个依赖的 bean 会在被注入之前,被完全配置。

  4. 非单例的bean,以及 lazy bean,将第一次请求时才创建实例(比如3中,被其它单例bean依赖时)。

3.4.3 ApplicationContext 附加功能

3.4.3.1 国际化 i18n
3.4.3.2 Spring ApplicationEvent

ApplicationEvent

@EventListener事件监听,@Async异步监听,@Order定义监听器顺序。

image-20230521130810575

x

x

3.4.4 资源加载 Resource

3.4.4.1 ResourceLoader

所有应用程序上下文都实现了该ResourceLoader接口(ApplicationContextResourceLoader)。因此,所有的应用程序上下文都可以用来获取Resource实例。

3.4.4.2 Resource

image-20230521143630560

3.4.4.3 ResourceLoaderAware

实现ApplicationContextAware接口完全可以做 ResourceLoaderAware的事情。

**但是:**一般来说,如果您只需要专用接口,则最好使用专用接口。

3.4.5 配置全局类型转换

例如时间格式转换:DateTimeFormatterRegistrar, DateFormatterRegistrar, ``JodaTimeFormatterRegistrar`

使用:
@Bean
public FormattingConversionService conversionService() {

 // Use the DefaultFormattingConversionService but do not register defaults
 DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);

 // Ensure @NumberFormat is still supported
 conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());

 // Register JSR-310 date conversion with a specific global format
 DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
 registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
 registrar.registerFormatters(conversionService);

 // Register date conversion with a specific global format
 DateFormatterRegistrar registrar = new DateFormatterRegistrar();
 registrar.setFormatter(new DateFormatter("yyyyMMdd"));
 registrar.registerFormatters(conversionService);

 return conversionService;
}

3.4.6 配置Bean验证

3.5 常用注解

组合注解

如:@RestController@Controller@ResponseBody组合。

组合注解可以选择重新声明 元注解 中的属性以允许 自定义。

@Bean

@Required

表示某个bean强制依赖另一个bean实例。

通过RequiredAnnotationBeanPostProcessor实现。即:这个bean必须被注册到容器,@Required注解才会生效。

@Autowired,@Inject [JSR-330]

当用构造器注入时,如果类定义了一个构造器,则@Autowired可省略。如果定义多个构造器,则必须通过@Autowired注释指定容器使用哪个。

可应用在任意方法上。(并非只能是 set 方法)

可应用在任意字段上。(支持指定泛型)

可用于数组、集合、Map<beanName, beanInstance>。但至少需要一个匹配的元素,除非:@Autowired(required=false)。(支持指定泛型)

可用于:``BeanFactoryApplicationContext`、`Environment`、`ResourceLoader`、 `ApplicationEventPublisher`和 `MessageSource`。

@Primary

用在@Bean定义时

@Qualifier

用在 DI 处

@Resource [JSR-250]

先按beanName查找,无果再按 bean Class查找。

@Value

@Value("${a.b.c:defaultValue}")

通常配合@PropertySource("classpath:some.properties")使用

支持SpEL表达式

@ComponentScan

指定扫描的包,并按实际需要注册包内的 bean。(只有包内被需要的类才会被注册到bean容器)

AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor

3.6 SpEL

4. SpringBoot

4.1 设计思想 ( 约定大于配置 )

4.2 自动配置

4.2.1 自动包扫描

@SpringBootApplication

默认基于当前类所在的包路径进行扫描。

如果导入的外部jar包,包名为该路径同级或为子路径,则包内定义的配置类会被扫描到。

4.2.2 导入配置的方式

1. @Configuration + @Bean

启动类所在包内,所有的配置均会自动扫描。@Bean会被注册为bean单例。

2. @Import
  • @Configuration类
  • ImportSelector,DeferredImportSelector 实现类
  • ImportBeanDefinitionRegistrar实现类
  • AnnotationConfigRegistry —————————— ???
3. @ImportResource

导入 XML ,或其他 非@Configuration bean 定义的资源。

4. /MATA-INF/spring.factories文件

导入的外部包,如果包含该文件,则该文件内的配置类会被加载。

  • 只有定义在该文件中的配置类,其类上的 @ConfigurationBefore() @ConfigurationAfter()注解才会生效。

4.2.3 容器中添加Bean实例的方式

1. 使用spring.xml文件中定义<bean name="xx"></bean>
2. @Bean注解
3. 实现FactoryBean工厂bean接口,动态创建bean实例
4. 实现ImportBeanDefinitionRegistrar接口,动态注入bean实例

4.3 SpringBootApplication启动过程

4.3.+ ApplicationContext生态

ApplicationContext生态.png

4.3.1 SpringApplication.run();

image-20230531225121387

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  // 资源加载器,这里为null
		this.resourceLoader = resourceLoader;
  
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  
  // 推断应用类型,是否为:servletWebApp、reactiveWebApp、非web应用
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
  
  // 收集 BootstrapRegistryInitializer
  // 该接口类作用于 BootstrapContext(引导上下文)
		this.bootstrapRegistryInitializers = new ArrayList<>(
				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
  
  // 收集 ApplicationContextInitializer
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  // 收集 ApplicationListener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  
  // 推断主启动类,’main()‘方法所在类
		this.mainApplicationClass = deduceMainApplicationClass();
	}
- run()

image-20230531230412432

public ConfigurableApplicationContext run(String... args) {
  
		long startTime = System.nanoTime();
  
  // 创建 BootstrapContext,并调用所有的 BootstrapRegistryInitializer
  // BootstrapContext是一个简单的 "引导上下文",在 程序启动 和 Environment后置处理直到准备好ApplicationContext 这期间可用。它提供对单例的惰性访问,这些单例的创建成本可能很高,或者需要在ApplicationContext可用之前共享。
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
  
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
  
  // 收集 SpringApplicationRunListener
  // SpringApplicationRunListener用于对当前run()方法的执行进度进行侦听,在不同的位置调用,以执行相应的行为。
		SpringApplicationRunListeners listeners = getRunListeners(args);
  
  // 执行 starting 侦听事件
		listeners.starting(bootstrapContext, this.mainApplicationClass);
  
		try {
      // 启动参数封装成对象,该对象中定义了一些读取参数的方法
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      
      
      // 创建环境 ConfigurableEnvironment ---------------------------------------------------------------【核心】
      // 1.根据 webApplicationType 创建环境对象
      // 2.添加、删除或重新排序此应用程序环境中的任何PropertySource
      // 3.通过spring.profiles.active属性激活其他配置文件
      // 4.执行 environmentPrepared 侦听事件
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      // (忽略)
			configureIgnoreBeanInfo(environment);
      // 打印 Banner
			Banner printedBanner = printBanner(environment);
      
      
      // 创建应用上下文 
      // 通过 DefaultApplicationContextFactory 根据 webApplicationType 创建上下文
      // 三种:
      // 1.default 	- AnnotationConfigApplicationContext
      // 2.servlet 	- AnnotationConfigServletWebServerApplicationContext
      // 3.reactive - AnnotationConfigReactiveWebServerApplicationContext
      //【重要】如果是spring-web应用,则会根据spring-web模块的 META-INF/services/javax.servlet.ServletContainerInitializer 来对servlet容器进行配置。其中就通过 SpringBootServletInitializer 设置了 ApplicationContextFactory的类型。
			context = createApplicationContext();
      
      // applicationStartup 为应用上下文在启动期间的步骤与指标记录
			context.setApplicationStartup(this.applicationStartup);
      
      
      // 准备应用上下文 ----------------------------------------------------------------------【核心】
      // 这一阶段会将所有 bean资源 加载到上下文。  >>> 完成 contextPrepared 和 contextLoaded 两个阶段。
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      
      // 更新上下文 ----------------------------------------------------------------------【核心】
			refreshContext(context);
      // 更新完成后,默认空实现。
			afterRefresh(context, applicationArguments);
      
			Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
			}
      
      // 执行 started 侦听事件
			listeners.started(context, timeTakenToStartup);
      
      // 调用 ApplicaitonRunner
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
      // 启动异常处理
			handleRunFailure(context, ex, listeners);
      // throw
			throw new IllegalStateException(ex);
		}
  
		try {
			Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
      // 启动完成,执行 ready 侦听事件
			listeners.ready(context, timeTakenToReady);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
  
		return context;
	}
- prepareContext()

image-20230601220909352

// 【prepareContext】
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
  
  1.-------------------- [执行 ApplicationContextInitializer 钩子] ------------------------
    
		context.setEnvironment(environment);
  // ApplicationContext 后置处理
  // 这里将 ResourceLoader 和 ClassLoader 设置到context中
		postProcessApplicationContext(context);
  
  // 调用所有 ApplicationContextInitializer 钩子 (在应用上下文 prepared 之后,refresh 之前)
		applyInitializers(context);
  
  // 执行 context prepared 侦听事件
		listeners.contextPrepared(context);
  
  // ApplicationContext 已准备好,则关闭 BootstrapContext
		bootstrapContext.close(context);
  
  // 打印启动日志、active Profile文件信息
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
  
  2.-------------------- [设置 beanFactory属性添加两个 beanFactory后置处理器 ] --------------------------------
  
  // 这里配置了一些bean工厂的属性
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
      // 默认不允许“循环引用”,遇到循环引用会报错提示
			((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
			if (beanFactory instanceof DefaultListableBeanFactory) {
        // 默认不允许“定义同名bean来覆盖已有bean”
				((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
			}
		}
  
  // bean默认不会延迟初始化。
		if (this.lazyInitialization) {
      // 如果设置为延迟初始化,则增加一个 beanFactory 后置处理器,用于处理bean延迟加载
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
  
  // 增加一个 beanFactory 后置处理器,用于对 @PropertySource资源对象 重新排序
		context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
  
  3.------------------------  [将各种 资源 加载为 bean实例 到上下文中] --------------------------------------
    
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
  
  // 通过 BeanDefinitionLoader 加载资源
  // BeanDefinitionLoader 将加载任务分别委托给以下四个对象,使用 BeanDefinitionRegistry 注册bean实例:
  // 		1.AnnotatedBeanDefinitionReader 
  // 		2.XmlBeanDefinitionReader
  // 		3.GroovyBeanDefinitionReader
  // 		4.ClassPathBeanDefinitionScanner
		load(context, sources.toArray(new Object[0]));
  
  // 执行 contextLoaded 侦听事件
		listeners.contextLoaded(context);
	}
-- loadResource()

根据 Resource资源的类型,使用不同的Reader/Scanner进行加载。

image-20230601235143338

- refreshContext()

image-20230602000516876

// 【refresh】
@Override
	public void refresh() throws BeansException, IllegalStateException {
    // 锁
		synchronized (this.startupShutdownMonitor) {
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

			// Prepare this context for refreshing.
      // 1.启动刷新状态;
      // 2.初始化属性源 PropertySources,验证所有标记为 required 属性都是可解析的
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
      // 1.创建 DefaultListableBeanFactory实例
      // 2.注册 component组件,扫描 basePackages下的类
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
      // 简单的一些beanFactory配置:
      // 1.添加一个bean后置处理器:ApplicationContextAwareProcessor
      // 2.忽略Aware接口的自动注入,而是交给各种后置处理器来处理
      // 3....
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
        // 默认实现为空,供子拓展。主要作用为供子类添加所需的 BeanPostProcessors。
        // GenericWebApplicationContext做了拓展:
        // 1.添加一个bean后置处理器: ServletContextAwareProcessor,它将 ServletContext 传递给实现ServletContextAware接口的 bean。
        // 2.registerScope: requestScope, sessionScope, servletContextScope
        // 3....
				postProcessBeanFactory(beanFactory);

				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
        
        
				// Invoke factory processors registered as beans in the context.
        // 本方法会实例化和调用所有 BeanFactoryPostProcessor(包括其子类 BeanDefinitionRegistryPostProcessor)。
				invokeBeanFactoryPostProcessors(beanFactory);

        
				// Register bean processors that intercept bean creation.
        // 实例化并注册所有 BeanPostProcessor 类
				registerBeanPostProcessors(beanFactory);
				beanPostProcess.end();

				// Initialize message source for this context.
        // 初始化消息源  messageSource 实例。
				initMessageSource();

				// Initialize event multicaster for this context.
        // 初始化 spring事件发布器。默认为:SimpleApplicationEventMulticaster
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
        // 默认实现为空,供子类覆盖实现。
        // 1.Servlet..Context实现:创建 WebServer实例。
        // 2.Reactive..Context实现:创建 WebServerManager实例。
				onRefresh();

				// Check for listener beans and register them.
        // 将 ApplicationListener 添加给 EventMulticaster
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
        // 初始化所有剩余的单例bean。
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
        // 此 refresh 过程完成。
        // 发布 ContextRefreshed 事件。
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
				contextRefresh.end();
			}
		}
	}
-

4.3.2 常用拓展接口详解(钩子)

ApplicationContextInitializier
BeanFactoryPostProcessor

BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory 时对外暴露的扩展点,Spring IoC 容器允许 BeanFactoryPostProcessor 在容器实例化任何 bean 之前读取 BeanDefinition,并可以修改它。

优先级:

​ 实现了PriorityOrdered接口的Processor >> 实现了 Ordered 接口的Processor >> 普通Processor

image-20230604230349312

  • 参数为:beanFactory。而springContext实现了beanFactory,可见,beanFactoryPostProcessor 作用为自定义 应用上下文(修改bean定义)。
BeanDefinitionRegistryPostProcessor

BeanDefinitionRegistryPostProcessor 继承自 BeanFactoryPostProcessor,比 BeanFactoryPostProcessor 具有更高的优先级,主要用来在常规的 BeanFactoryPostProcessor 检测开始之前注册其他 bean 定义。特别是,你可以通过 BeanDefinitionRegistryPostProcessor 来注册一些常规的 BeanFactoryPostProcessor,因为此时所有常规的 BeanFactoryPostProcessor 都还没开始被处理。

应用例子:

MapperScannerConfigurer

BeanPostProcessor

BeanPostProcessor允许自定义修改生成的bean 实例——例如,检查标记接口或使用代理包装 beans。 通常,通过标记接口等填充 bean 的后处理器将实现postProcessBeforeInitialization ,而使用代理包装 bean 的后处理器通常将实现postProcessAfterInitialization 。

image-20230604231132672

  • 参数为:bean。可见beanPostProcessor应用在bean实例上,用来在bean实例的初始化前后,对bean进行前置/后置处理。
值得注意的是:

实现了BeanPostProcessor 接口的类是特殊的,它们会被容器不同地对待。

所有实现了BeanPostProcessor 的Bean和它们直接引用的Bean都将被提前实例化(在其他AOP处理方法之前),因为Spring的AOP自动代理也是通过实现BeanPostProcessor接口来做的,所以 BeanPostProcessor的实现类和它们直接引用的bean不满足AOP自动代理的条件,因此它们导致AOP失效问题。

ApplicaitonRunner

5. Spring Cloud

5.1 网关

5.2 服务注册、发现

5.3 限流

5.4 熔断

6. MySql

6.1 索引

6.1.1 为什么是B+树?

  • Hash

    等值查询时o(1)。

    但不支持范围查询、排序、like模糊查询。

  • 平衡二叉树

    每个节点只存一个数据。

    每次查找下一个节点都需要进行一次磁盘读取。

  • B树

    每个节点可以存储多个数据,可以降低树的高度,减少磁盘IO次数。

    每个节点都存储数据。

  • B+🌲

    只有叶子节点才存储数据。——相比B树,B+树的结构可以在一页中存储更多的索引值,进一步减少IO次数。

    叶子节点的数据使用链表按顺序排列。—— 更利于范围查询、排序、分组。

6.1.2 索引分类与规则

6.2 查询优化

6.2.1 索引匹配原则

6.2.2 explain

type
possiable_key
key
rows
filtered
extra

6.3 事务

一个事务内的多个sql操作,要么全部执行成功,要么全部失败回滚。

事务是通过事务日志来实现的,事务日志包括:redo log和undo log。

6.3.1 特点

四大特性:AICD (原子/隔离/一致/持久)
  • A (原子性 - Atomicity)

    要么全部成功,要么全部失败。不可分割。

  • I (隔离性 - Isolation)

    多个事务之间相互隔离,互不影响。(多个并发事务同时对数据进行读写)

    隔离级别

    1. 读未提交 - RU(Read uncommitted)
    2. 读提交 - RC(Read committed)
    3. 可重复读 - RR(Repeatable read)
    4. 串行化(Serializable)
  • C (一致性 - Consistency)

  • D (持久性 - Durability)

6.3.2 隔离级别

6.3.2.1 四种隔离级别
  • RU 读未提交

    不会加锁。会读到未commit的数据。——【脏读】

  • RC 读已提交

    只对记录加锁。

    不加间隙锁。—— 这会导致重复进行同一个范围查询操作时,前后两次读到的数据不一致。——【不可重复读】

  • RR 可重复度 (mysql默认)

    记录锁 + 间隙锁。

    间隙锁解决了“不可重复读”的问题。

    新的问题: —— 【幻读】

  • 串行

    全部查询都加共享锁。

6.3.2.2 实现原理
  • 读写锁

  • 多版本与快照

    Mysql - MVCC 多版本并发控制

7. Mybatis-plus

8. Maven

9. Git

10. Redis

10.1 基础

10.2 部署

10.2.1 主从 + 哨兵

自动故障转移。

10.2.2 集群部署

10.3 客户端框架

10.4 缓存同步与数据一致性

10.4.1 缓存同步

10.4.2 数据一致性

几种方案对比:
A.双写
  • 先写mysql, 再写redis

    不可取!不能保证后一步,一定成功。失败则导致缓存中的数据未更新,导致不一致。

  • 先写redis, 再写mysql

    不可取!同样,不能保证后一步一定成功。失败则导致数据不一致。

B.先删再写
  • 先删redis, 再写mysql

  • 先删redis, 再写mysql, 再删redis —— “缓存双删”

    **双删:**写入mysql后再次删除redis缓存,为的是防止在 第一次删除redis缓存 与 写入mysql 期间,其它线程查库后(这时数据还是旧的)将旧的数据重新写会缓存。于是额外进行一次删除操作。

    但是,第二次删除操作依然无法保证发生在其它线程回写数据之后,所以“双删”其实仍存在问题。

C.先写再删
  • 先写mysql,再删redis

    与“双删”比较类似。但更简单,也能满足绝大多数场景。【推荐】

    【*】对于超高并发的“秒杀”等场景,可以直接走缓存。缓存清零则秒杀结束。同时,将秒杀内容入队列,来异步处理mysql清库存等操作。

D.异步更新
  • 先写mysql,再通过binlog异步更新redis

    前提:查询请求不会将结果回写redis,缓存数据仅通过异步操作来同步。

    这样就只能保证最终一致性(同步过程中的不一致)。

10.5 缓存失效问题

10.5.1 雪崩 —— 大面积失效

如雪崩一样,缓存大面积失效。导致大量请求打到数据库,数据库压力过大崩溃。

场景:

​ 大量缓存在同一时间过期。

解决:

10.5.2 击穿 —— 单个失效

某个热点数据缓存失效。

由于是热点数据,肯定会有大量请求。仅这样一个热点数据缓存失效就会导致大量请求打到数据库,造成崩溃。

场景:

​ 热点key过期,或被误删除。

解决:

10.5.3 穿透 —— 未命中缓存

用户请求了缓存和数据库中 均不存在 的数据。导致始终无法走缓存,请求永远都直接打到数据库。

解决:
  1. 必须的合理的接口参数校验。
  2. 对不存在的key也进行缓存一个默认值。
  3. 布隆过滤器。

10.5.4 布隆过滤器

11. MQ

12. Nginx

特点:
  • 内存占用少
  • 并发能力强(可支持大约 50000 个并发连接)
  • 配置超简洁
  • bug 非常少
  • 安装超简单
  • 服务特别稳(几个月也不需要重启)

https://www.bilibili.com/video/BV1F5411J7vK

https://www.bilibili.com/video/BV1ov41187bq

https://juejin.cn/post/6844904144235413512

正向代理:
  • 代理客户端,来访问服务器

    img
反向代理:
  • 代理服务器,被客户端访问

    img

13. Docker

14. K8S

15.Dubbo

20.八股文

20.1 并发

20.1.1 synchronize、Lock、Semaphore、ReentrantLock

对比 synchronize 与 ReentrantLock

image-20230606224358301


// **************************Synchronized的使用方式**************************
// 1.用于代码块
synchronized (this) {}
// 2.用于对象
synchronized (object) {}
// 3.用于方法
public synchronized void test () {}
// 4.可重入
for (int i = 0; i < 100; i++) {
	synchronized (this) {}
}


// **************************ReentrantLock的使用方式**************************
public void test () throw Exception {
	// 1.初始化选择公平锁、非公平锁
	ReentrantLock lock = new ReentrantLock(true);
	// 2.可用于代码块
	lock.lock();
	try {
		try {
			// 3.支持多种加锁方式,比较灵活; 
      // 具有可重入特性
			if(lock.tryLock(100, TimeUnit.MILLISECONDS)){ }
		} finally {
			// 4.手动释放锁
			lock.unlock()
		}
	} finally {
    // 手动释放
		lock.unlock();
	}
}
对比 公平锁、非公平锁的加锁过程
// java.util.concurrent.locks.ReentrantLock#NonfairSync

// 非公平锁
static final class NonfairSync extends Sync {
	...
	final void lock() {
		if (compareAndSetState(0, 1))
      // 若通过CAS设置变量State(同步状态)成功,也就是获取锁成功,则将当前线程设置为独占线程。
      // 一进方法就尝试获取锁,无视其它排队等待的线程,所以是“非公平的”。
			setExclusiveOwnerThread(Thread.currentThread());
		else
      // 未获取到锁,则进行排队
      // 使用 AbstractQueuedSynchronizer.acquire 机制 (AQS) ——公平排队
			acquire(1);
		}
  ...
}


// java.util.concurrent.locks.ReentrantLock#FairSync

// 公平锁
static final class FairSync extends Sync {
  ...  
	final void lock() {
    // 使用 AbstractQueuedSynchronizer.acquire 机制 (AQS) ——公平排队
		acquire(1);
	}
  ...
}

20.1.2 AQS源码、CLH队列 - AbstractQueuedSynchronizer

AQS:(基于队列的同步器抽象)是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。

CLH:Craig、Landin and Hagersten队列,是单向链表。

AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程封装成一个节点以排队的方式来实现锁的分配。

AQS使用一个Volatile的int类型的成员变量state来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,通过CAS完成对State值的修改。

img

线程的两种锁模式 (Node.mode):
  • SHARED 线程以共享的模式等待锁
  • EXCLUSIVE 线程以独占的模式等待锁
AQS核心源码解析

获取独占锁:AQS.acquire()

// java.util.concurrent.locks.AbstractQueuedSynchronizer
// 抽象类 AQS
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
  
  // Unsafe:compareAndSet 的本地方法实现
  private static final Unsafe unsafe = Unsafe.getUnsafe();
  
    /**
     * 等待队列的头,延迟初始化。除了初始化之外,它只能通过方法 setHead 进行修改。注意:如果head存在,则保证其waitStatus不会被CANCELLED
     */
    private transient volatile Node head;

    /**
     * 等待队列的尾部,延迟初始化。仅通过方法 enq 修改以添加新的等待节点。
     */
    private transient volatile Node tail;

    /**
     * 同步状态
     */
    private volatile int state;
  
  
  	// 更新同步状态
    protected final boolean compareAndSetState(int expect, int update) {
      	// 如果对象this. stateOffset = expect,则将 stateOffset 更新为 update
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
  
  
  
  
  	/**
  	 * 【!!!!!】 acquire
  	 *
  	 * 获取独占锁
  	 * final方法,无法被重写
  	 */
    public final void acquire(int arg) {
      // 首先 tryAcquire 尝试获取锁,成功则返回true,方法就直接结束了
      // tryAcquire在当前类中为空实现,需要由具体的Lock子类来实现获取锁的方式。
      if (!tryAcquire(arg) 
          // 如果获取锁失败,则 addWaiter 加入队列排队
          // 并调用acquireQueued,让排队的线程不停尝试获取锁。成功获取锁,返回false;如排队时被打断,会返回true
          && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        	// 
          selfInterrupt();
  	}
  
  
  
  	/**
  	 * 线程尝试获取独占锁
  	 * 成功获取锁,返回true.
  	 * 
  	 * 空实现。由调用AQS的自定义同步器进行具体实现。
  	 */
  	protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
  
  
  
    /**
     * addWaiter
     * 添加排队节点
     */
    private Node addWaiter(Node mode) {
      // 创建node,
        Node node = new Node(Thread.currentThread(), mode);
       
      // CAS,将node加入队列尾部
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
      
      // tail为空,或者CAS失败,则调 enq 方法
        enq(node);
        return node;
    }
  
  
   
  	/**
  	 * enq
     * 将目标节点node加入队列。队列为空时,先进行初始化
     */
    private Node enq(final Node node) {
      // 无限循环,直到CAS成功后 return.
      for (;;) {
        Node t = tail;
        
        if (t == null) {
          // 如果tail=null,表示队列尚未初始化,则通过CAS初始化一个空的头节点
          if (compareAndSetHead(new Node())) 
            // 初始化成功,将队列的 tail 也指向头节点
            tail = head; 
          
        } else {
          // “尝试”将当前节点的 prev指向“尾”节点。
          // 此刻,变量t也并不一定是真正的尾节点了,可能在 37 行赋值之后,就被其它线程修改了 tail的值。
          node.prev = t;
          // 这里通过 CAS 来设置 tail指向当前node。(其它线程可能通过这里修改了tail值)
          if (compareAndSetTail(t, node)) {
            // CAS: 只有 tail 仍然等于 t(无其它线程修改),才将 tail值指向当前 node节点。否则,继续无线循环,直到 CAS 成功。
            // 成功修改tail=node后,此时 t 为 tail 的前一个节点,于是将 t.next 指向 node,也即指向tail.
            t.next = node;
            return t;
          }
        }
      }
    }
  
  
  
  
  	/**
  	 * acquireQueued
  	 * 让排队中的该node线程节点不间断尝试获取独占锁。
  	 *
  	 */
  	final boolean acquireQueued(final Node node, int arg) {
      	// 是否失败
        boolean failed = true;
        try {
          	// 是否中的
            boolean interrupted = false;
          
          	// 无线循环。直到 node 排队成功获取到锁
            for (;;) {
              	// 获取当前node的前一个节点
                final Node p = node.predecessor();
              
              	// 如果前一个节点 p=head,则当前节点为第二个节点(有效),可以开始获取锁了。
              	// 相反,如果前一个节点不是head,则表示该节点前面还有其它等待的线程节点,则不进行获取锁操作。
                if (p == head && tryAcquire(arg)) {
                  
                  	// 如果当前节点获取到了锁,则将该node放到队列头
                  	// setHead方法中清除了 node.thread、prev属性,让node变成了一个空节点,满足head的要求。
                    setHead(node);
                  	
                    p.next = null; // help GC
                  
                  	// 表示获取成功了
                    failed = false;
                  	
                  	// 即使获取锁成功了,如果线程中断了,后续也会执行中断方法。所以这里返回的是中断状态值。
                    return interrupted;
                }
              	
              	// 未获取到锁时,判断当前线程是否需要挂起(防止无限循环浪费资源)
              	// shouldParkAfterFailedAcquire ??? 
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
          
        } finally {
          	// 如果循环过程中有任何异常,且最终未成功获取到锁,则取消排队。
            if (failed)
              	// cancelAcquire 方法会将 node.waitStatus 设置为:CANCELLED 取消。取消状态的节点会从队列中释放
                cancelAcquire(node);
        }
    }
  
  
  
}
  • park:挂起(阻塞)

20.1.3 ReentrantLock源码

可重入锁
公平锁/非公平锁(独享锁)
内部使用了AQS机制来进行加锁、释放锁。
测试:
package xx

import java.util.concurrent.locks.ReentrantLock;

public class LockTest {

    // 共享资源
    static int a = 0;

    public static void main(String[] args) throws InterruptedException {

        // 抢占一把锁
        ReentrantLock lock = new ReentrantLock(true);

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + "抢到锁");
                    int i = 0;
                    while (i < 10000) {
                        i++;
                        a++;
                    }
                } finally {
                    System.out.println(Thread.currentThread().getName() + "执行完毕,释放锁");
                    if (lock.isLocked()) {
                        lock.unlock();
                    }
                }
            }
        };

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        Thread t3 = new Thread(runnable);
        Thread t4 = new Thread(runnable);
        t1.start();
        t4.start();
        t3.start();
        t2.start();

        // 无限等待,直到线程执行完毕
        t1.join();
        t2.join();
        t3.join();
        t4.join();

        System.out.println(a);
    }

}
调试:

202306072056196

执行结果与上面顺序一致:

image-20230607210115295

源码

image-20230607213148748

在AQS中维护了一个 volatile类型的 state字段,但并没有使用该字段值,而只是提供了修改该字段值的方法:getState(), setState(x), compareAndSetState(x);

ReentrantLock则利用了state来实现可重入机制。

state = 0,表示当前锁未被占用,可以被线程抢占。

state > 0,表示当前锁已被占用。

  • lock()获取锁时,如果 state > 0,则首先判断持有锁的是否为当前线程,如果是当前线程,则 state += 1,并且当前线程再次获取锁成功,谓之可重入
  • 而每一次调unlock()释放锁时,同样判断是否为当前线程。如果不是当前线程,则抛出异常(因为锁是被其它线程占用)。而如果是当前线程,则将 state - 1,如果减1后 state != 0,则当前线程继续持有锁,且释放锁失败。知道该线程多次调用unlock()使得 state 值为0,才会成功释放。
  • state 为0,其它线程才可以抢占该锁。

具体可以看FairSyncUnfairSynctryAcquire()方法的实现。

其它一些同步器原理(均利用了AQS.state ):

image-20230607220535940

20.1.4 ReentrantReadWriteLock源码

可重入锁
读锁(共享锁)、写锁(独享锁)
同样使用AQS来实现。

20.1.5 CAS

通过Unsafe类的native方法实现。

// Unsafe.java中的一个方法源码 (其实就是 AtomicInteger.incrementAndGet() 的实现)

public final int getAndAddInt(Object o, long offset, int delta) {
  int v;
  do {
    v = getIntVolatile(o, offset);
  } while (!compareAndSwapInt(o, offset, v, v + delta)); // CAS + 无限循环
  return v;
}

// 1.获取 对象o的 offset 内存值,赋给 v
// 2.尝试更新该值,成功的前提是:更新的那一刻,v与o.offset仍然相等,则将 offset更新为 v+delta.
// 3.如果2中的前提不成立,也就是在第5行获取值,到第6行尝试更新值,这期间有别的线程修改了offset的值。那么更新失败,则继续循环1-2-3。
// 4.直到2中的前提成立,则更新成功,退出循环,返回更新前的值v。

CompareAndSwap - 比较交换:将要被更新的对象,值与期望的一致时,才会更新为新的值,否则不做任何操作。

底层由CPU执行完成,属于原子操作。

它是一种乐观锁算法(无锁算法)。AtomicInteger类即是通过volatileCAS自旋算法实现的。

CAS的问题:
1. ABA问题:

​ 解决:增加版本号,由A-B-A 变成 1A-2B-3A

2.开销大:

​ 如果CAS一直不成功,就会一直自旋,给CPU带来大的开销。

AtomicRefrence

20.1.6 锁的实现原理

20.1.6.1 锁的分类

图片

20.1.6.2 乐观锁

最常用的实现:CAS

20.1.6.3 自旋锁
“稍等一会。”

​ 即:线程在竞争锁资源失败时,并不会立即阻塞,而是“自旋”一会儿,在自旋期间不停尝试获取锁资源。

合理性:

阻塞唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。

在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁。

而为了让当前线程“稍等一下”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。

流程:
图片
问题:

自旋过久。

自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。

反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。

所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。

20.1.6.4 适应性自旋锁

针对自旋锁的问题而生。

原理:

同一个锁,在上一次自旋10次(默认)以内被成功获取。则这一次程序会认为也能获取成功,于是这一次也进行自旋获取。

同一个锁,如果多次通过自旋获取锁均不成功,则程序认为该锁所处的场景不适合自旋。于是下一次有线程再来获取这个锁,程序将直接放弃自旋方式,而选择直接阻塞,避免自旋浪费处理器资源。

20.1.6.5 synchronize 之 无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁

synchonize实现同步的方式::

  • java对象头
  • Monitor
锁升级:
  • 无锁(CAS) ->

  • 偏向锁(ThreadId) ->

    对象头信息中会记录获取到锁的线程的 id。

    下一次仍然是这个线程在获取锁,则直接获取成功(无需执行CAS操作)。(持有偏向锁的线程不会主动释放偏向锁)

    下一次如果不是这个线程(即存在多线程竞争),则升级为 轻量级锁。

    偏向锁的意义:

    ​ 虽然是在多线程环境中,但一段同步代码一直被一个线程所访问。那么该线程会自动获取锁,降低获取锁的代价。

  • 轻量级锁(自旋) ->

    当自旋超过一定的次数。或者有2个以上的线程同时在竞争锁资源。锁升级为 重量级锁。

  • 重量级锁(阻塞)

  • 线程Thread特征

    • start()
    • join()
  • 线程池实现原理

  • 阻塞队列实现原理

  • 并发工具类

20.2 JVM

  • 内存模型
  • 垃圾回收机制
  • 调优

20.3 Spring

  • IOC
  • AOP
  • bean生命周期
  • 框架实现原理

20.4 Redis

  • 数据类型及应用场景
  • 线程模型
  • 持久化机制
  • 主从复制原理
  • 高可用原理
  • cluster
  • 分布式锁
  • hash一致性算法
  • 与mysql数据一致性
  • 缓存击穿、穿透、雪崩

20.5 MQ

  • 消息可靠性保证,消息丢失
  • 幂等性
  • 高可用性,消息堆积
  • 持久化机制
  • 使用场景

20.6 Zookeeper

  • 使用场景
  • 分布式锁实现

20.7 Dubbo

  • 底层通信原理
  • 负载均衡方式
  • 集群容错方式
  • spi机制
  • 代理方式

20.8 Mysql, Mybatis

  • mybatis框架原理
  • mysql分库分表
  • 索引
  • 数据库事务
  • mysql锁

20.9 分布式

  • cap原理
  • 负载均衡
  • 限流
  • 降级
  • 熔断

20.10 RPC

  • 有哪些rpc框架,实现原理是什么

20.11 设计模式

  • 单例模式
  • 抽象工厂模式
  • 原型模式
  • 代理模式

20.12 网络模型

  • BIO,NIO,AIO

空文件

简介

do something, do your best! 展开 收起
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/chenchaoran/sky-ladder.git
git@gitee.com:chenchaoran/sky-ladder.git
chenchaoran
sky-ladder
sky-ladder
master

搜索帮助