@Author chencr
@Date 2023.05
- 用户设备: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地址和子网掩码就可以计算网络号。如果两台计算机计算出的网络号相同,说明两台计算机在同一个网络,可以直接通信。如果两台计算机计算出的网络号不同,那么两台计算机不在同一个网络,不能直接通信。
不在同一网络下的两个计算机,它们之间必须通过路由器或者交换机这样的网络设备间接通信,我们把这种设备称为网关。
“飞鸽传书”。
1byte = 8bit 1字节 = 8比特(位)
注意:数据结构中的堆栈 与JVM中的堆栈,不是一回事!!
常用字符编码包括 ASCII、UTF-8、UTF-16、GBK、GB2312 等。
由于二进制太长,通常记录时用十六进制,可以方便阅读。(4位二进制对应1位十六进制[0x??
],1字节 = 0x??,0x??
)
目前收录了约 13万 个字符(各种国家语言),且仍在不断增加。
Unicode字符对应表:http://www.unicode.org
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个汉字:“锟斤拷”。
UTF-8:可变长度编码方式
- 表现形式(编解码规范):
0xxxxxxx:一个字节(开始为0); (与ASCII码一致)
110xxxxx 10xxxxxx:两个字节编码形式(开始两个 1);
1110xxxx 10xxxxxx 10xxxxxx:三字节编码形式(开始三个 1);
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx:四字节编码形式(开始四个 1)。
- 优势:包罗万象。可以包含已知的所有字符,还有空间用于开发者自定义字符扩展。
- 劣势:浪费空间
比如,汉字同一用了3个字节(2^24)来表示。但常用汉字也就几千个。
o(1) 无关
从数组中根据下标获取数据
o(n) 倍数
遍历一个数组。
o(logn) 对数
- 数据规模大幅增加,执行时间少量增加。
对数组进行二分查找
o(n^2) 平方
对数据进行嵌套遍历。冒泡排序。
o(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 // 这时 ,进行'&'与位运算会非常快,且可以减少碰撞几率,使数据在数组上分布更均匀。
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); } }
程序计数器:
每个线程一个。保证CPU在切换线程后,能恢复到正确的执行位置(字节码行)。
仅占用非常小的内存空间,且不会发生OOM。
虚拟机栈:
栈中每一个栈帧对应一个被调用的方法。
本地方法栈:
x
堆:
存储对象。所有线程共享。垃圾收集器管理的主要区域。
元空间:
JDK 8 的时候,原有的方法区(更准确的说应该是永久代)被彻底移除,取而代之的是元空间。
元空间使用本地内存,不受JVM内存限制。
“垃圾对象”定义:
可达性分析算法
GC Root对象:
- 虚拟机栈中引用的对象
- 本地方法栈中引用的对象
- 类静态属性引用的对象
- 常量引用的对象
**对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在 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
类)。
概念:
如果一个类加载器收到了加载类的请求,它会先把请求委托给上层加载器去完成,上层加载器又会委托上上层加载器,一直到最顶层的类加载器;如果上层加载器无法完成类的加载工作时,当前类加载器才会尝试自己去加载这个类。
作用:
双亲委派模型能够保证同一个类最终会被特定的类加载器加载。(保证唯一性)
String a = new String("哈哈");
- 1.在堆中创建“哈哈”对象 —— ”new“关键字一定会在堆中创建新对象。
- 2.在字符串常量池中创建“哈哈”对象(已有则不创建)
- 3.返回堆中的对象地址给变量a
String a = "哈哈"; (更常用的赋值形式,也大大节省字符串占用空间)
- 1.在字符串常量池中找“哈哈” ——双引号的方式,直接走常量池,不经过堆
- 2.找到,则直接返回常量池中的地址;
- 3.未找到,则直接在常量池中创建“哈哈”,返回该地址。
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 的堆外内存就是靠其管理。
Java 内建的
package
机制是为了避免class
命名冲突;JDK 的核心类使用
java.lang
包,编译器会自动导入;JDK 的其它常用类定义在
java.util.*
,java.math.*
,java.text.*
,……;
所有类的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的属性值也同时被改变!!
引申:
- 通过序列化与反序列化,可以实现 深拷贝。
该类描述一个对象的 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
:而代理对象,实际是调用的
NativeMethodAccessorImpl
对象来执行反射调用。Inflation 机制。初次加载字节码实现反射,使用 Method.invoke() 和 Constructor.newInstance() 加载花费的时间是使用原生代码加载花费时间的 3 - 4 倍。这使得那些频繁使用反射的应用需要花费更长的启动时间。
实际的 MethodAccessor 实现有两个版本,一个是 Native 版本实现,一个是 Java 版本实现。
- Native 版本一开始启动快,但是随着运行时间边长,速度变慢。Java 版本一开始加载慢,但是随着运行时间边长,速度变快。
于是在 【代理模式】DelegatingMethodAccessor 的 invoke() 方法中增加逻辑:
1.初始时使用 Native版本;
2.当调用次数 > 15次后,则改为 Java版本实现:
MethodAccessorImpl
。
[通常]给定binary类名,ClassLoader从文件系统中读取类文件,构造出类定义数据。
可以通过其它方式(而非文件系统)加载类数据,比如网络:NetworkClassLoader
jvm内置顶级父ClassLoader:"bootstrap class loader"
每个 Class 对象都包含对定义它的 ClassLoader 的引用。
clazz.getClassloader();
动态代理:JDK,CGLIB
JDK
代理类必须至少实现一个接口。(基于 implements)
CGLIB
被代理类不能为
final
,且只拦截公共方法调用!(基于 extends)
基础数据类型:
- 数据值存储在栈上,变量名直接指向具体的值。
引用数据类型:
- 变量名指向的是对象的内存地址,在栈上。
- 内存地址指向的存储对象,则在堆上。
引用传递:
- 当一个参数按照
引用传递
的方式在两个方法之间传递时,调用者和被调用者其实用的是同一个变量,当该变量被修改时,双方都是可见的。值传递:
- 一个参数在两个方法间传递时,两个方法持有的是两个不同的变量(对象)。之后在两个方法内部的两个对象互不影响。
所以,java是值传递!!(与数据类型无关)
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工具类
volatile修饰的变量,在一个线程内其值被修改,可以立即在其它线程内修改可见。
volatile 保证多线程操作共享变量的可见性以及禁止指令重排序
线程本地内存
- 线程之间的共享变量存在主内存中,每个线程都有一个私有的本地内存,存储了该线程以读、写共享变量的副本。本地内存是Java内存模型的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器等。
- 所有的共享变量都存在主内存中。
- 每个线程都保存了一份该线程使用到的共享变量的副本。
- 如果线程A与线程B之间要通信的话,必须经历下面2个步骤:
- 线程A将本地内存A中更新过的共享变量刷新到主内存中去。
- 线程B到主内存中去读取线程A之前已经更新过的共享变量。
所以,线程A无法直接访问线程B的工作内存,线程间通信必须经过主内存。
注意,根据JMM的规定,线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主内存中读取。
所以线程B并不是直接去主内存中读取共享变量的值,而是先在本地内存B中找到这个共享变量,发现这个共享变量已经被更新了,然后本地内存B去主内存中读取这个共享变量的新值,并拷贝到本地内存B中,最后线程B再读取本地内存B中的新值。
那么怎么知道这个共享变量的被其他线程更新了呢?这就是JMM的功劳了,也是JMM存在的必要性之一。JMM通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性保证。
修饰的字段在对象序列化时会被忽略。
同样被忽略的还有 static 字段。
能用==时不用equals。如枚举的比较。
“==”运算符比较的时候,如果两个对象都为 null,并不会发生
NullPointerException
,而equals()
方法则会。“==”运算符会在编译时进行检查,如果两侧的类型不匹配,会提示错误,而
equals()
方法则不会。
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)。
Iterable 表示一种能力。这种能力就是:可以获取一个迭代器(Iterator)来完成迭代功能。
所以,Iterabor 是实现迭代能力的工具类。
原则上,它们也可以按上面的 Iterable - Iterator 思想来设计。但是并没有。
因为:
实现
Comparable
的类,是需要将其自身与其它同类对象进行比较。所以它可以直接实现一个compareTo()
方法直接调用即可。而实现
Iterable
的类,则是需要对其持有的内部元素集合进行迭代(而非对象自身),将这种能力抽象为一个具体的工具类更为合适。所以:
2.2.6 迭代器的两个接口是有联系的。
2.2.7 比较器的两个接口,则是无关系的。Comparator 则是独立的设计。
但是:
比较器也可以按迭代器的思想来设计,实现Comparable 的类提供返回一个Comparator 对象的方法,由该对象来决定排序规则。
但,显然复杂了!
不过:
一个实现了
Comparable
的类,重写compareTo()
方法,只能提供一种排序规则。如果一个类需要进行多种排序方式,则直接定义多种
Comparator
方式,是更好的选择。
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,后进先出。
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
红黑树
ArrayDeque
基于数组的双端队列
ArrayDeque
与``LinkedList`实现的功能基本一致。不过内部实现不同,基于数组方便随机检索,基于链表则方便增删元素,需根据实际使用场景合理选择PriorityQueue
基于
Comparator
的元素有序队列。执行 remove 或者 poll 方法,返回的总是优先级最高的元素。
参数:
1.corePoolSize:核心线程数,线程池中始终存活的线程数。
2.maximumPoolSize: 最大线程数,线程池中允许的最大线程数。
3.keepAliveTime: 存活时间,线程没有任务执行时最多保持多久时间会终止。
4.unit: 单位,参数keepAliveTime的时间单位,7种可选。
5.workQueue: 一个阻塞队列,用来存储等待执行的任务,均为线程安全,7种可选。
6.threadFactory: 线程工厂,主要用来创建线程,默及正常优先级、非守护线程。
7.handler:拒绝策略,拒绝处理任务时的策略,4种可选,默认为AbortPolicy。
- AbortPolicy:直接丢弃任务,抛出异常,这是默认策略
- CallerRunsPolicy:只用调用者所在的线程来处理任务
- DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执行当前任务
- DiscardPolicy:直接丢弃任务,也不抛出异常
常用方式:饿汉、懒汉(双重校验锁)、静态内部类、枚举
静态内部类 方式
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; }最安全、简单的单例实现:
反射安全。
其它方式虽然构造器私有化了,但是可以通过反射来创建新的实例。而枚举类无法使用反射创建。
序列化/反序列化安全。
枚举单例在序列化和反序列化前后,为同一个对象,==。
输入输出 (相对内存而言)
当程序需要读取数据的时候,就会开启一个通向数据源的流(通道),这个数据源可以是文件,内存,或是网络连接。
类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流(通道)。
- 流:要么输入,要么输出。
一张图汇总:
字节流
处理二进制数据,文本、图片、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类:
数组流
在内存中处理、转换文件数据的方式。
- 优点:仅存在内存中,无需创建临时文件可提升程序效率。
- 缺点:在内存中,存储数据量有限,过大会导致内存溢出。
管道流
可以用于线程间的通信、数据的传递等。
缓冲
CPU 很快,它比内存快 100 倍,比磁盘快百万倍。那也就意味着,程序和内存交互会很快,和硬盘交互相对就很慢。
为了减少程序和硬盘的交互,提升程序的效率,就引入了缓冲流,也就是类名前缀带有
Buffer
的那些。打印流
Scanner
System.out
转换流 (读写用字节,内存中处理用字符)
- InputStreamReader —— 字节流读取文件,并转换为字符流 进行处理。
- OutputStreamWriter —— 将字符流转换为字节流,再写到目标位置。
序列化:将一个对象转换为一个字节序列(包含
对象的数据
、对象的类型
、对象中存储的属性
等信息),以便在网络上传输或保存到文件中,或者在程序之间传递。反序列化:将字节序列转换为一个对象,以便在程序中使用。
serialVersionUID
序列化 ID反序列化时,Java 虚拟机会把字节流中的
serialVersionUID
与被序列化类中的serialVersionUID
进行比较,如果相同则可以进行反序列化,否则就会抛出序列化版本不一致的异常。JDK自带序列化问题:
- 可移植性差:Java 特有的,无法跨语言进行序列化和反序列化。
- 性能差:序列化后的字节体积大,增加了传输/保存成本。
- 安全问题:攻击者可以通过构造恶意数据来实现远程代码执行,从而对系统造成严重的安全威胁。
kyro 序列化工具
<!-- 引入 Kryo 序列化工具 --> <dependency> <groupId>com.esotericsoftware</groupId> <artifactId>kryo</artifactId> <version>5.4.0</version> </dependency>
缺点:
- ABA 问题
- 自旋开销大
【】它是 Bean 的框架。围绕Bean展开的一些列设计,构筑了 Spring Frameworker 框架。
它把开发者对对象生命周期的控制,交由 IoC容器来管理。(控制反转)
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");
Spring Framework 八大模块:
核心包:
org.springframework.beans
,org.springframework.context
( 容器,即ApplicationContext
)
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 Spring’s container so flexible and extensible.
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()效果相同。
ApplicationContext
是BeanFactory
的子接口,完整超集。ApplicationContext 是容器的直接体现,负责实例化、配置和组装 bean。
实现
BeanPostProcessor
接口的类是特殊的,被容器区别对待。
由于它工作在其它bean实例化前后,那么
BeanPostProcessor
的实例对象应该更早的被注册到容器中(默认为:ApplicationContext自动类型检测,且在程序启动(startup)
时随着ApplicationContext
一起实例化,属于容器实例化过程内 (普通bean则是在容器实例化之后。))。但是,如果需要在
BeanPostProcessor
实例化之前进行一些条件校验,则可以使用ConfigurableBeanFactory
的addBeanPostProcessor
方法以编程方式注册它们。但是请注意:
BeanPostProcessor
以编程方式添加的实例不遵循Ordered
接口。在这里,注册的顺序决定了执行的顺序。- 另请注意,
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; } }
- Spring AOP 功能,由动态代理实现。(
AspectJ
不是。)- AOP 自动代理是通过
BeanPostProcessor
自身实现的。- 所以
BeanPostProcessor
实例对象,以及它的依赖项bean,均无法使用AOP功能。规则:
默认:有接口的类使用
JDK
代理,并实现所有接口方法。无接口时使用CGLIB
代理。若强制使用
CGLIB
,设置:proxy-target-class = true
AspectJ
AspectJ
不是使用动态代理实现的,而是在 JVM 加载类文件过程中将切面 织入类文件 的方式实现的。即:动态织入。@EnableAspectJAutoProxy 启用
AspectJ
代理切面
实现
BeanPostProcessor
接口的类是特殊的,被容器区别对待。与BeanPostProcessor
类似。
BeanFactoryPostProcessor
对 bean 配置元数据进行操作 。著名实现类:PropertySourcesPlaceholderConfigurer
该类使用标准 Java
Properties
格式将 bean 定义的属性值外部化到单独的文件中(即我们通常的properties配置文件)。在程序运行时,读取配置文件并通过替换占位符 (${jdbc.url})
来设置bean的属性值。不仅在指定的文件中查找属性
Properties
。默认情况下,如果它在指定的属性文件中找不到属性,它会检查 SpringEnvironment
属性和常规 JavaSystem
属性。著名实现类:PropertyOverrideConfigurer
功能与上基本相似。
该接口用于被工厂类去实现,以实现复杂的bean初始化过程。
boolean isSingleton(); // 定义该Factory生成的类是否为单例。默认:true T getObject(); // 获取实例 Class<?> getObjectType(); // 返回实例的类型,为null表示提前不知道类型
在Spring中,
BeanDefinition
描述了一个 bean 实例,它具有属性值、构造函数、参数值、Scope,以及具体实现提供的更多信息。bean scope:
通常:Singleton , Prototype
Web应用:Request, Session, Application, WebSocket
@RequestScope //将组件分配给作用域request。同类的还有:@SessionScope, @ApplicationScope @Component public class LoginAction { // ... }
特定 bean 的运行时类型很难确定。bean 元数据定义中的指定 class 类只是一个初始类引用。
见
FactoryBean
ApplicationContext
对于它管理的 bean,它支持基于构造函数
和基于setter()
的 DI。
循环依赖问题
- 带有参数验证的构造函数注入通常是更可取的。
- 但是也会造成循环依赖的场景,A(B b), B(A a),此时会在运行时抛出异常。
- 此种情况,则推荐通过setter注入来配置循环依赖。
bean初始化时机
Spring在创建好容器时会验证每个bean的配置,然后会实例化那些 作用域被定义为单例、其为 not lazy 的bean对象。
bean的属性会在实际bean创建后才会设置。
每个依赖的 bean 会在被注入之前,被完全配置。
非单例的bean,以及 lazy bean,将第一次请求时才创建实例(比如3中,被其它单例bean依赖时)。
ApplicationEvent
@EventListener
事件监听,@Async
异步监听,@Order
定义监听器顺序。x
x
所有应用程序上下文都实现了该
ResourceLoader
接口(ApplicationContext
是ResourceLoader
)。因此,所有的应用程序上下文都可以用来获取Resource
实例。
实现
ApplicationContextAware
接口完全可以做ResourceLoaderAware
的事情。**但是:**一般来说,如果您只需要专用接口,则最好使用专用接口。
例如时间格式转换:
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; }
组合注解
如:
@RestController
由@Controller
和@ResponseBody
组合。组合注解可以选择重新声明 元注解 中的属性以允许 自定义。
@Bean
@Required
表示某个bean强制依赖另一个bean实例。
通过
RequiredAnnotationBeanPostProcessor
实现。即:这个bean必须被注册到容器,@Required
注解才会生效。@Autowired,@Inject [JSR-330]
当用构造器注入时,如果类定义了一个构造器,则
@Autowired
可省略。如果定义多个构造器,则必须通过@Autowired
注释指定容器使用哪个。可应用在任意方法上。(并非只能是
set
方法)可应用在任意字段上。(支持指定泛型)
可用于数组、集合、Map<beanName, beanInstance>。但至少需要一个匹配的元素,除非:
@Autowired(required=false)
。(支持指定泛型)可用于:``BeanFactory
、
ApplicationContext`、`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
@SpringBootApplication
默认基于当前类所在的包路径进行扫描。
如果导入的外部jar包,包名为该路径同级或为子路径,则包内定义的配置类会被扫描到。
1. @Configuration + @Bean
启动类所在包内,所有的配置均会自动扫描。@Bean会被注册为bean单例。
2. @Import
- @Configuration类
- ImportSelector,DeferredImportSelector 实现类
- ImportBeanDefinitionRegistrar实现类
- AnnotationConfigRegistry —————————— ???
3. @ImportResource
导入 XML ,或其他 非@Configuration bean 定义的资源。
4. /MATA-INF/spring.factories文件
导入的外部包,如果包含该文件,则该文件内的配置类会被加载。
- 只有定义在该文件中的配置类,其类上的
@ConfigurationBefore()
@ConfigurationAfter()
注解才会生效。
1. 使用
spring.xml
文件中定义<bean name="xx"></bean>
2. @Bean注解
3. 实现
FactoryBean
工厂bean接口,动态创建bean实例4. 实现
ImportBeanDefinitionRegistrar
接口,动态注入bean实例
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();
}
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】
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);
}
根据 Resource
资源的类型,使用不同的Reader
/Scanner
进行加载。
// 【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();
}
}
}
BeanFactoryPostProcessor
接口是 Spring 初始化 BeanFactory 时对外暴露的扩展点,Spring IoC 容器允许 BeanFactoryPostProcessor 在容器实例化任何 bean 之前读取 BeanDefinition,并可以修改它。优先级:
实现了
PriorityOrdered
接口的Processor >> 实现了Ordered
接口的Processor >> 普通Processor
- 参数为:beanFactory。而springContext实现了beanFactory,可见,beanFactoryPostProcessor 作用为自定义 应用上下文(修改bean定义)。
BeanDefinitionRegistryPostProcessor
继承自 BeanFactoryPostProcessor,比 BeanFactoryPostProcessor 具有更高的优先级,主要用来在常规的 BeanFactoryPostProcessor 检测开始之前注册其他 bean 定义。特别是,你可以通过 BeanDefinitionRegistryPostProcessor 来注册一些常规的 BeanFactoryPostProcessor,因为此时所有常规的 BeanFactoryPostProcessor 都还没开始被处理。应用例子:
MapperScannerConfigurer
BeanPostProcessor
允许自定义修改生成的bean 实例——例如,检查标记接口或使用代理包装 beans。 通常,通过标记接口等填充 bean 的后处理器将实现postProcessBeforeInitialization ,而使用代理包装 bean 的后处理器通常将实现postProcessAfterInitialization 。
- 参数为:bean。可见beanPostProcessor应用在bean实例上,用来在bean实例的初始化前后,对bean进行前置/后置处理。
值得注意的是:
实现了BeanPostProcessor 接口的类是特殊的,它们会被容器不同地对待。
所有实现了BeanPostProcessor 的Bean和它们直接引用的Bean都将被提前实例化(在其他AOP处理方法之前),因为Spring的AOP自动代理也是通过实现BeanPostProcessor接口来做的,所以 BeanPostProcessor的实现类和它们直接引用的bean不满足AOP自动代理的条件,因此它们导致AOP失效问题。
5.1 网关
5.2 服务注册、发现
5.3 限流
5.4 熔断
Hash
等值查询时o(1)。
但不支持范围查询、排序、like模糊查询。
平衡二叉树
每个节点只存一个数据。
每次查找下一个节点都需要进行一次磁盘读取。
B树
每个节点可以存储多个数据,可以降低树的高度,减少磁盘IO次数。
每个节点都存储数据。
B+🌲
只有叶子节点才存储数据。——相比B树,B+树的结构可以在一页中存储更多的索引值,进一步减少IO次数。
叶子节点的数据使用链表按顺序排列。—— 更利于范围查询、排序、分组。
type
possiable_key
key
rows
filtered
extra
一个事务内的多个sql操作,要么全部执行成功,要么全部失败回滚。
事务是通过事务日志来实现的,事务日志包括:redo log和undo log。
四大特性:AICD (原子/隔离/一致/持久)
A (原子性 - Atomicity)
要么全部成功,要么全部失败。不可分割。
I (隔离性 - Isolation)
多个事务之间相互隔离,互不影响。(多个并发事务同时对数据进行读写)
隔离级别:
1. 读未提交 - RU(Read uncommitted) 2. 读提交 - RC(Read committed) 3. 可重复读 - RR(Repeatable read) 4. 串行化(Serializable)C (一致性 - Consistency)
D (持久性 - Durability)
RU 读未提交
不会加锁。会读到未commit的数据。——【脏读】
RC 读已提交
只对记录加锁。
不加间隙锁。—— 这会导致重复进行同一个范围查询操作时,前后两次读到的数据不一致。——【不可重复读】
RR 可重复度 (mysql默认)
记录锁 + 间隙锁。
间隙锁解决了“不可重复读”的问题。
新的问题: —— 【幻读】
串行
全部查询都加共享锁。
读写锁
多版本与快照
Mysql - MVCC 多版本并发控制
自动故障转移。
几种方案对比:
A.双写
先写mysql, 再写redis
不可取!不能保证后一步,一定成功。失败则导致缓存中的数据未更新,导致不一致。
先写redis, 再写mysql
不可取!同样,不能保证后一步一定成功。失败则导致数据不一致。
B.先删再写
先删redis, 再写mysql
先删redis, 再写mysql, 再删redis —— “缓存双删”
**双删:**写入mysql后再次删除redis缓存,为的是防止在 第一次删除redis缓存 与 写入mysql 期间,其它线程查库后(这时数据还是旧的)将旧的数据重新写会缓存。于是额外进行一次删除操作。
但是,第二次删除操作依然无法保证发生在其它线程回写数据之后,所以“双删”其实仍存在问题。
C.先写再删
先写mysql,再删redis
与“双删”比较类似。但更简单,也能满足绝大多数场景。【推荐】
【*】对于超高并发的“秒杀”等场景,可以直接走缓存。缓存清零则秒杀结束。同时,将秒杀内容入队列,来异步处理mysql清库存等操作。
D.异步更新
先写mysql,再通过binlog异步更新redis
前提:查询请求不会将结果回写redis,缓存数据仅通过异步操作来同步。
这样就只能保证最终一致性(同步过程中的不一致)。
如雪崩一样,缓存大面积失效。导致大量请求打到数据库,数据库压力过大崩溃。
场景:
大量缓存在同一时间过期。
解决:
某个热点数据缓存失效。
由于是热点数据,肯定会有大量请求。仅这样一个热点数据缓存失效就会导致大量请求打到数据库,造成崩溃。
场景:
热点key过期,或被误删除。
解决:
用户请求了缓存和数据库中 均不存在 的数据。导致始终无法走缓存,请求永远都直接打到数据库。
解决:
- 必须的合理的接口参数校验。
- 对不存在的key也进行缓存一个默认值。
- 布隆过滤器。
特点:
- 内存占用少
- 并发能力强(可支持大约 50000 个并发连接)
- 配置超简洁
- bug 非常少
- 安装超简单
- 服务特别稳(几个月也不需要重启)
https://www.bilibili.com/video/BV1F5411J7vK
正向代理:
代理客户端,来访问服务器
反向代理:
代理服务器,被客户端访问
- 可重入锁ReentrantLock的原理和AQS原理:https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html
- Java“锁”事: https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651749434&idx=3&sn=5ffa63ad47fe166f2f1a9f604ed10091&chksm=bd12a5778a652c61509d9e718ab086ff27ad8768586ea9b38c3dcf9e017a8e49bcae3df9bcc8&scene=38#wechat_redirect
对比 synchronize 与 ReentrantLock
// **************************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); } ... }
AbstractQueuedSynchronizer
AQS:(基于队列的同步器抽象)是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。
CLH:Craig、Landin and Hagersten队列,是单向链表。
AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程封装成一个节点以排队的方式来实现锁的分配。
AQS使用一个Volatile的int类型的成员变量state来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,通过CAS完成对State值的修改。
线程的两种锁模式 (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:挂起(阻塞)
可重入锁
公平锁/非公平锁(独享锁)
内部使用了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); } }调试:
执行结果与上面顺序一致:
源码
在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,其它线程才可以抢占该锁。
具体可以看
FairSync
和UnfairSync
中tryAcquire()
方法的实现。其它一些同步器原理(均利用了AQS.state ):
可重入锁
读锁(共享锁)、写锁(独享锁)
同样使用AQS来实现。
通过
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
类即是通过volatile
和CAS自旋
算法实现的。CAS的问题:
1. ABA问题:
解决:增加版本号,由A-B-A 变成 1A-2B-3A
2.开销大:
如果CAS一直不成功,就会一直自旋,给CPU带来大的开销。
AtomicRefrence
最常用的实现:CAS
“稍等一会。”
即:线程在竞争锁资源失败时,并不会立即阻塞,而是“自旋”一会儿,在自旋期间不停尝试获取锁资源。
合理性:
阻塞
或唤醒
一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁。
而为了让当前线程“稍等一下”,我们需让当前线程进行
自旋
,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。流程:
问题:
自旋过久。
自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。
反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。
所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。
针对自旋锁的问题而生。
原理:
同一个锁,在上一次自旋10次(默认)以内被成功获取。则这一次程序会认为也能获取成功,于是这一次也进行自旋获取。
同一个锁,如果多次通过自旋获取锁均不成功,则程序认为该锁所处的场景不适合自旋。于是下一次有线程再来获取这个锁,程序将直接放弃自旋方式,而选择直接阻塞,避免自旋浪费处理器资源。
synchonize
实现同步的方式::
- java对象头
- Monitor
锁升级:
线程Thread特征
- start()
- join()
线程池实现原理
阻塞队列实现原理
并发工具类
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。