Java技术体系
2025-10-09 13:39:55 9 举报
AI智能生成
Java技术体系是一套成熟的技术堆栈,涵盖了从底层虚拟机(JVM)、开发工具到企业级应用框架的广泛技术。核心内容包括了Java语言规范、Java类库、以及Java开发工具包(JDK),支持跨平台、面向对象的编程范式。文件类型通常包括.java源代码文件、.class字节码文件和应用发布的.jar包。Java因其“一次编写,到处运行”的特性而广受欢迎,同时它的安全机制和大型生态系统为开发可靠、扩展性强的应用提供了坚实基础。不仅如此,Java在企业级应用领域中还享有极高的地位,从大型系统到微服务架构,Java都扮演着不可或缺的角色。
作者其他创作
大纲/内容
硬件
计算机组成原理
编程
前端
参考资料
WebRTC
WebRTC(实时传输)传输技术
WebRTC详解
《WebRTC技术详解》
WebRTC的优缺点和趋势
后端
Java
Java语言基础
Java平台结构图
Java的发展过程
语法
数据类型
基本类型
整数类型
byte
8位,有符号
-128(-2^7)~127(2^7-1)
默认值0
byte a = 100,byte b = -50
short
16位,有符号
-32768(-2^15)~32767(2^15-1)
默认值0
short s = 1000,short r = -20000
int
32位,有符号
-2,147,483,648(-2^31)~2,147,483,647(2^31-1)
默认值0
int a = 100000, int b = -200000
long
64位,有符号
-9,223,372,036,854,775,808(-2^63)~9,223,372,036,854,775,807(2^63-1)
默认值0L("L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写)
long a = 100000L,Long b = -200000L
补充
Date时间的数据类型底层是long类型
浮点类型
float
单精度、32位、符合IEEE 754标准的浮点数
默认值 0.0f
浮点数不能用来表示精确的值,如货币
float f1 = 234.5f
double
双精度、64 位、符合 IEEE 754 标准的浮点数
浮点数的默认类型为 double 类型
默认值 0.0d
double d5 = 12.9867,doubled1=7D
补充
BigDecimal
java.math.BigDecimal
存放精确浮点数,如货币金额等。
使用String方式构造精确值,使用数值不精确。
BigDecimal bigDecimal1 = new BigDecimal(0.1);
BigDecimal bigDecimal2 = new BigDecimal("0.1");
System.out.println(bigDecimal1);
System.out.println(bigDecimal2);
0.1000000000000000055511151231257827021181583404541015625
0.1
使用数值进行初始化导致数据不准确
BigDecimal bigDecimal2 = new BigDecimal("0.1");
System.out.println(bigDecimal1);
System.out.println(bigDecimal2);
0.1000000000000000055511151231257827021181583404541015625
0.1
使用数值进行初始化导致数据不准确
比较大小用类自带compareTo方法。
字符类型
char
单一的 16 位 Unicode 字符。本质也可以理解为整数类型。
\u0000(十进制等效值为 0)~\uffff(即为65535)
char 数据类型可以储存任何字符
char letter = 'A';
补充
字符集
ASCII
固定一个字节
前127个字符包含了阿拉伯数字和英文以及常用英文字符,
128~256之间没有用。
128~256之间没有用。
GB2312
第一个字节小于128,则为对应的ASCII码,即半角。
连续两个字节都大于127,则为中文及中文字符等。
GBK
一个或两个字节
GB2312的扩展
GB18030
Unicode
2个字节
表示ASCII码时,前8位是0,存在空间浪费。
字符编码
UTF-8
可变长编码方式,1-6字节
不同计算机高低位可能不同
高位在位 “FEFF”
低位在位 “FFFE”
Unicode的编码方式之一
规则
1个字节
第一位为0,后七位对应Unicode码(对应ASCII码)
n(2~6)字节
第一个字节前n位为1,n+1位为0,后续字节开头全为10,
剩下的由原本的Unicode码从后往前补。
剩下的由原本的Unicode码从后往前补。
Java默认UTF-8编码
ASCII码在UTF-8中占一个字节,中文占3个字节
UTF-16
UTF-32
参考资料
字符,字符集,字符编码
字符、字符集、字符编码的基础知识科普
字符集和字符编码(Charset & Encoding)
布尔类型
boolean
boolean数据类型表示一位的信息
只有两个取值:true 和 false
默认值 false
boolean one = true
补充
特殊的基本类型
void
包装类java.lang.Void
一般不能进行操作,所以不计入常规运算基本类型。
包装类
Byte
Short
Integer
Long
Float
Double
Character
Boolean
引用类型
类、接口、数组等都是引用类型
默认值null
常量
常量在程序运行时是不能被修改的。
使用 final 关键字来修饰常量,声明方式和变量类似
final double PI = 3.1415927
转义字符
Java语言支持一些特殊的转义字符序列
类型转换
型、实型(常量)、字符型数据可以混合运算。运算中,
不同类型的数据先转化为同一类型,然后进行运算。
不同类型的数据先转化为同一类型,然后进行运算。
规则
不能对boolean类型进行类型转换。
不能把对象类型转换成不相关类的对象。
在把容量大的类型转换为容量小的类型时必须使用强制类型转换。
转换过程中可能导致溢出或损失精度。
int i =128;
byte b = (byte)i;
浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入。
(int)23.7 == 23;
(int)-45.89f == -45
自动类型转换
必须满足转换前的数据类型的位数低于转换后的数据类型
低 ------------------------------------> 高
byte,short,char—> int —> long—> float —> double
byte,short,char—> int —> long—> float —> double
强制类型转换
转换的数据类型必须是兼容的
运算符
算数运算符
+
-
*
/
%
++
--
位运算符
&(按位与)
如果相对应位都是1,则结果为1,否则为0。
(A&B),得到12,即0000 1100
|(按位或)
如果相对应位都是 0,则结果为 0,否则为 1。
(A | B)得到61,即 0011 1101
^(按位同)
如果相对应位值相同,则结果为0,否则为1。
(A ^ B)得到49,即 0011 0001
~(按位反)
按位取反运算符翻转操作数的每一位,即0变成1,1变成0。
(〜A)得到-61,即1100 0011
<<(按位左移)
按位左移运算符。左操作数按位左移右操作数指定的位数。
A << 2得到240,即 1111 0000
>>(按位右移)
按位右移运算符。左操作数按位右移右操作数指定的位数。
A >> 2得到15即 1111
>>>(按位右移补零)
按位右移补零操作符。左操作数的值按右操作数指定的位数右移,
移动得到的空位以零填充。
移动得到的空位以零填充。
A>>>2得到15即0000 1111
A=0011 1100
B=0000 1101
B=0000 1101
关系运算符
==
!=
>
<
>=
<=
逻辑运算符
&&
||
!
赋值运算符
直接赋值
=
运算赋值
算数运算赋值
+=
-=
*=
/=
%=
位运算赋值
<<=
>>=
&=
|=
^=
其他运算符
条件运算符
(?:)
b=(a==1)?20:30
类型运算符
boolean result = name instanceof String
运算符优先级
表中具有最高优先级的运算符在的表的最上面,最低优先级的在表的底部。
流程控制
分支控制
if...else
if...else if...else
switch...case...default
支持可以自动转int的类型
支持枚举
JDK1.7版本以后支持String类型
循环控制
for
while
do...while
foreach
底层是迭代器
跳出循环关键字
continue
适用于任何循环控制结构中。作用是让程序立刻跳转到下一次循环的迭代。
在 for 循环中,continue 语句使程序立即跳转到更新语句。
在 while 或者 do…while 循环中,程序立即跳转到布尔表达式的判断语句。
break
主要用在循环语句或者 switch 语句中,用来跳出整个语句块。
跳出最里层的循环,并且继续执行该循环下面的语句。
关键字
关键字是Java语言的有特定用途或者特别预留的保留字,
保留字不能用于常量、变量、和任何标识符的名称。
保留字不能用于常量、变量、和任何标识符的名称。
分类
访问控制
private、protected、public
包相关
package、import
类方法变量相关
abstract、class、interface、extends、final、implements、native、new、static、strictfp、synchronized、volatile、transient
程序控制语句
if、else、switch、case、for、while、continue、break、return、do、instanceof、default
错误处理
assert、catch、finally、throw、throws、try
变量引用
super、this、void
数据基本类型
byte、short、int、long、float、double、char、boolean
预留关键字
goto、const、null
注释
单行注释 //
// 这是单行注释的示例
多行注释 /**/
/*
第一行
第二行
*/
第一行
第二行
*/
文档注释 /** */
/*
* 这是第一个Java程序
* 它将输出 Hello World
* 这是一个多行注释的示例
*/
* 这是第一个Java程序
* 它将输出 Hello World
* 这是一个多行注释的示例
*/
文档注释标签
标识符
类名、变量名以及方法名都被称为标识符。
规则
所有的标识符都应该以字母(A-Z 或者 a-z),美元符($)、或者下划线(_)开始
关键字不能用作标识符
标识符是大小写敏感的
例
合法
age、$salary、_value、__1_value
非法
123abc、-salary
方法
Java方法是语句的集合,它们在一起执行一个功能。
方法定义
修饰符 返回值类型 方法名(参数类型 参数名){
...
方法体
...
return 返回值;
}
...
方法体
...
return 返回值;
}
方法重载
一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
最常用的就是构造器的重载。
重载规则
被重载的方法必须改变参数列表(参数个数或类型不一样);
被重载的方法可以改变返回类型;
被重载的方法可以改变访问修饰符;
被重载的方法可以声明新的或更广的检查异常;
方法能够在同一个类中或者在一个子类中被重载。
无法以返回值类型作为重载函数的区分标准。
方法重写
方法名和参数都相同
一般发生在子类继承父类对父类的方法重写
构造方法
当一个对象被创建时候,构造方法用来初始化该对象。
构造方法和它所在类的名字相同,但构造方法没有返回值。
构造方法和它所在类的名字相同,但构造方法没有返回值。
不管你是否自定义构造方法,所有的类都有构造方法。
因为 Java 自动提供了一个默认构造方法,
默认构造方法的访问修饰符和类的访问修饰符相同(类为 public,构造函数也为 public;
类改为 protected,构造函数也改为 protected)。
因为 Java 自动提供了一个默认构造方法,
默认构造方法的访问修饰符和类的访问修饰符相同(类为 public,构造函数也为 public;
类改为 protected,构造函数也改为 protected)。
一旦你定义了自己的构造方法,默认构造方法就会失效。
类的构造函数可以重载
构造函数不能被继承,所以不能重写。
可变参数(JDK1.5)
在方法声明中,在指定参数类型后加一个省略号(...) 。
一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。
任何普通的参数必须在它之前声明。
任何普通的参数必须在它之前声明。
例
public static void printMax( double... numbers) {
}
}
finalize()方法
Java 允许定义这样的方法,它在对象被垃圾收集器析构(回收)之前调用,
这个方法叫做 finalize( ),它用来清除回收对象。
这个方法叫做 finalize( ),它用来清除回收对象。
一般格式
protected void finalize()
{
// 在这里终结代码
}
关键字 protected 是一个限定符,它确保 finalize() 方法不会被该类以外的代码调用。
Java 的内存回收可以由 JVM 来自动完成。如果你手动使用,则可以使用上面的方法。
面向对象OO
(ObjectOriented)
(ObjectOriented)
三大基本特征
封装
在面向对象程式设计方法中,封装(英语:Encapsulation)
是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。
是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。
类即是一种封装。
优点
减少耦合。
类内部的结构可以自由修改。
可以对成员变量进行更精确的控制。
隐藏信息,实现细节。
继承
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,
或子类从父类继承方法,使得子类具有父类相同的行为。
或子类从父类继承方法,使得子类具有父类相同的行为。
在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的。
class 父类 {
}
class 子类 extends 父类 {
}
继承的类型
继承
单继承
多重继承
不同类继承同一个类
多继承(不支持,只能通过接口实现)
继承的特性
子类拥有父类非 private 的属性、方法。
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法。
提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
关键字
extends
implements
super
通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this
指向当前对象自己的引用。
final
final 关键字声明类可以把类定义为不能继承的,即最终类;
或者用于修饰方法,该方法不能被子类重写:
或者用于修饰方法,该方法不能被子类重写:
构造器
构造器是私有的,不能继承,更不可能重写。
多态
多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作。
优点
消除类型之间的耦合关系
可替换性
可扩充性
接口性
灵活性
简化性
多态三个必要条件
继承
重写
父类引用指向子类对象:Parent p = new Child();
虚函数
虚函数的存在是为了多态。
Java 中其实没有虚函数的概念,它的普通函数就相当于 C++ 的虚函数,动态绑定是Java的默认行为。
如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。
如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。
重写
子类能够重写父类的方法。
当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。
要想调用父类中被重写的方法,则必须使用关键字 super。
当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。
要想调用父类中被重写的方法,则必须使用关键字 super。
重写规则
构造方法不能被重写。
参数列表与被重写方法的参数列表必须完全相同。
返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类
(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
访问权限不能比父类中被重写的方法的访问权限更低。
例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
父类的成员方法只能被它的子类重写。
声明为 final 的方法不能被重写。
声明为 static 的方法不能被重写,但是能够被再次声明。
子类可以重写父类所有方法,
除了声明为 private 和 final或static 的方法。
除了声明为 private 和 final或static 的方法。
如果不能继承一个类,则不能重写该类的方法。
多态的实现方式
重写
接口
抽象类和抽象方法
类
类是一个模板,它描述一类对象的行为和状态。
构成
构造方法
方法
属性
包含变量类型
局部变量
在方法、构造方法或者语句块中定义的变量被称为局部变量。
成员变量
成员变量是定义在类中,方法体之外的变量。
类变量
类变量也声明在类中,方法体之外,但必须声明为 static 类型。
内部类
四种内部类
成员内部类
局部内部类
匿名内部类
静态内部类
抽象
抽象类
什么是抽象类
如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类特征
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
关键字
abstract
public abstract class Employee
{
private String name;
private String address;
private int number;
//其余代码
}
{
private String name;
private String address;
private int number;
//其余代码
}
抽象方法
抽象方法只包含一个方法名,而没有方法体。
例
public abstract class Employee
{
private String name;
private String address;
private int number;
public abstract double computePay();
//其余代码
}
特征
如果一个类包含抽象方法,那么该类必须是抽象类。
任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
抽象类总结
抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
接口
在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。
特性
接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract
(只能是 public abstract,其他修饰符都会报错)。
(只能是 public abstract,其他修饰符都会报错)。
接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
(并且只能是 public,用 private 修饰会报编译错误)。
(并且只能是 public,用 private 修饰会报编译错误)。
接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
注
JDK 1.8 以后,接口里可以有静态方法和方法体了。
JDK 1.8 以后,接口允许包含具体实现的方法,
该方法称为"默认方法",默认方法使用 default 关键字修饰。
该方法称为"默认方法",默认方法使用 default 关键字修饰。
JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。
枚举
Java 枚举是一个特殊的类,一般表示一组常量。
比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等。
Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割。
enum Color
{
RED, GREEN, BLUE;
}
values(), ordinal() 和 valueOf() 方法
enum 定义的枚举类默认继承了 java.lang.Enum 类,
并实现了 java.lang.Seriablizable 和 java.lang.Comparable 两个接口。
并实现了 java.lang.Seriablizable 和 java.lang.Comparable 两个接口。
values() 返回枚举类中所有的值。
ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。
valueOf()方法返回指定字符串值的枚举常量。
枚举类成员
枚举跟普通类一样可以用自己的变量、方法和构造函数,
构造函数只能使用 private 访问修饰符,所以外部无法调用。
构造函数只能使用 private 访问修饰符,所以外部无法调用。
枚举既可以包含具体方法,也可以包含抽象方法。
如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。
如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。
对象
对象是类的一个实例(对象不是找个女朋友),有状态和行为。
例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
包
为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。
包的作用
把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
如同文件夹一样,包也采用了树形目录的存储方式。
同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,
当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,
当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
参考资料
Java面向对象三大特性
异常
类结构
Throwable
Error
(错误)
(错误)
VitulMachineError
StackOverFlowError(堆栈溢出)
当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误
OutOfMemoryError(内存不足)
当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误
AWTError
LinkageError
NoClassDefFoundError
IncompatibleClassChangeError
IllegalAccessError(违法访问错误)
当试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。
InstantiationError(实例化错误)
当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.
Exception
(异常)
(异常)
受检查异常
(非RuntimeException均是受检异常)
(非RuntimeException均是受检异常)
IOException
EOFException
FileNotFoundException
ReflectiveOperationException
ClassNotFoundException(找不到类)
1.根绝指定路径没有找到指定类;2.该类已被加载。
试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
InstantiationException(实例化异常)
当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常
NoSuchFieldException(属性不存在异常)
当访问某个类的不存在的属性时抛出该异常
NoSuchMethodException(方法不存在异常)
当访问某个类的不存在的方法时抛出该异常
RuntimeException
(不受检异常)
(不受检异常)
NullPointException(空指针)
ArithmeticException(算术条件异常)
IndexOutOfBoundsException(索引越界)
StringIndexOutOfBoundsException(字符串越界)
ArrayIndexOutOfBoundsException(数组越界)
ClassCastException(类转换异常)
ArrayStoreException(数据存储异常,操作数组时类型不一致)
IO操作的BufferOverflowException异常
IllegalArgumentException
NumberFormatException(数字格式异常)
当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常
异常处理
声明异常
throws
抛出异常
throw
捕获异常
try
catch
finally
要点
即使 catch 中包含了 return 语句,finally 子句依然会执行。
若 finally 中也包含 return 语句,finally 中的 return 会覆盖前面的 return.
若 finally 中也包含 return 语句,finally 中的 return 会覆盖前面的 return.
try-with-resource
try 代码块退出时,会自动调用 scanner.close 方法,和把 scanner.close 方法放在 finally 代码块中不同的是,
若 scanner.close 抛出异常,则会被抑制,抛出的仍然为原始异常。
被抑制的异常会由 addSusppressed 方法添加到原来的异常,如果想要获取被抑制的异常列表,可以调用 getSuppressed 方法来获取。
若 scanner.close 抛出异常,则会被抑制,抛出的仍然为原始异常。
被抑制的异常会由 addSusppressed 方法添加到原来的异常,如果想要获取被抑制的异常列表,可以调用 getSuppressed 方法来获取。
抛出的异常越明确越好
永远记住,你的同事或者几个月之后的你,将会调用你的方法并且处理异常。
对异常进行文档说明
使用描述性消息抛出异常
优先捕获最具体的异常
不要捕获 Throwable 类
不要记录并抛出异常
不要重复处理异常
包装异常时不要抛弃原始的异常
注解(JDK5.0)
参考资料
IO
OIO
面向流(BufferOriented)
(BufferOriented)
阻塞的
NIO
概述
面向缓冲区(BufferOriented)
非阻塞的
通道和多路复用技术
Buffer类
Channel类
Selector类
集合
HashMap
多线程
分类
用户线程
守候线程
进程
一个进程包括由操作系统分配的内存空间,包含一个或多个线程。
线程
一条线程指的是进程中一个单一顺序的控制流,
一个进程中可以并发多个线程,每条线程并行执行不同的任务。
一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程创建
java.lang包
实现Runnable接口
重写Runnable接口run()方法
例
实现Runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
//线程开启不一定立即执行,由cpu调度执行
public class TestThread implements Runnable{
@Override
public void run() {
// run()方法线程体
for (int i = 0; i <10 ; i++) {
System.out.println("子线程xxxxx");
}
}
public static void main(String[] args) {
//创建runnable接口的实现类对象
TestThread testThread = new TestThread();
//创建线程对象,通过线程对象来开启我们的线程,代理模式
Thread thread = new Thread(testThread);
//调用start()方法开启线程
thread.start();
for (int i = 0; i <100 ; i++) {
System.out.println("主线程xxxx");
}
}
}
//线程开启不一定立即执行,由cpu调度执行
public class TestThread implements Runnable{
@Override
public void run() {
// run()方法线程体
for (int i = 0; i <10 ; i++) {
System.out.println("子线程xxxxx");
}
}
public static void main(String[] args) {
//创建runnable接口的实现类对象
TestThread testThread = new TestThread();
//创建线程对象,通过线程对象来开启我们的线程,代理模式
Thread thread = new Thread(testThread);
//调用start()方法开启线程
thread.start();
for (int i = 0; i <100 ; i++) {
System.out.println("主线程xxxx");
}
}
}
继承Thread类
Thread实现自Runnable接口,相当于提供了一个基础的线程规范。
例
继承Thread类,重写run()方法,调用start开启线程
//线程开启不一定立即执行,由cpu调度执行
public class TestThread extends Thread{
@Override
public void run() {
// run()方法线程体
for (int i = 0; i <10 ; i++) {
System.out.println("子线程xxxxx");
}
}
public static void main(String[] args) {
//main线程,主线程
//创建一个线程对象
TestThread testThread = new TestThread();
//调用start()方法开启线程
testThread.start();
for (int i = 0; i <100 ; i++) {
System.out.println("主线程xxxx");
}
}
}
//线程开启不一定立即执行,由cpu调度执行
public class TestThread extends Thread{
@Override
public void run() {
// run()方法线程体
for (int i = 0; i <10 ; i++) {
System.out.println("子线程xxxxx");
}
}
public static void main(String[] args) {
//main线程,主线程
//创建一个线程对象
TestThread testThread = new TestThread();
//调用start()方法开启线程
testThread.start();
for (int i = 0; i <100 ; i++) {
System.out.println("主线程xxxx");
}
}
}
结论
底层代码Thread是实现Runnable接口。即Thread是Runnable的实现类。
直接实现Runnable接口的方式,
最后仍旧是通过Thread的构造方法实现线程。
可理解过java.lang包线程的实现,核心实现类就是Thread。
最后仍旧是通过Thread的构造方法实现线程。
可理解过java.lang包线程的实现,核心实现类就是Thread。
可以理解过Thread定义了Java线程的规范样本。
常见问题
java.util.concurrent包
实现Callable和Future接口
步骤
1. 创建 Callable 接口的实现类,并实现 call() 方法,
该 call() 方法将作为线程执行体,并且有返回值。
该 call() 方法将作为线程执行体,并且有返回值。
2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,
该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
实例
使用线程池Executor框架
ExecutorService
真正的线程池接口。
底层逻辑实现分别实现了Runnable接口,以及Callable和Future接口,
而Future是以Thread对象的target创建线程。
底层逻辑实现分别实现了Runnable接口,以及Callable和Future接口,
而Future是以Thread对象的target创建线程。
常见子类
ThreadPoolExecutor
void execute(Runnable command)
执行任务/命令,没有返回值,一般用来执行Runnable
<T> Future submit(Callable task)
执行任务,有返回值,一般又来执行Callable
void shutdown()
关闭连接池
Executors
工具类、线程池的工厂类,用于创建并返回不同类型的线程池。底层依赖的是ExecutorService。
常用方法
Executors.newCachedThreadPool()
创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n)
创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor()
创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n)
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
优点
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
对比
采用实现 Runnable、Callable 接口的方式创建多线程时,
线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
使用继承 Thread 类的方式创建多线程时,编写简单,
如果需要访问当前线程,
则无需使用 Thread.currentThread() 方法,
直接使用 this 即可获得当前线程。
如果需要访问当前线程,
则无需使用 Thread.currentThread() 方法,
直接使用 this 即可获得当前线程。
线程栈模型与线程的变量
线程栈是指某时刻时内存中线程调度的栈信息,当前调用的方法总是位于栈顶。
线程栈的内容是随着程序的运行动态变化的,
因此研究线程栈必须选择一个运行的时刻(实际上指代码运行到什么地方)。
线程栈的内容是随着程序的运行动态变化的,
因此研究线程栈必须选择一个运行的时刻(实际上指代码运行到什么地方)。
线程状态
五大状态
新建状态
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,
该线程对象就处于新建状态。
它保持这个状态直到程序 start() 这个线程。
该线程对象就处于新建状态。
它保持这个状态直到程序 start() 这个线程。
就绪状态
当线程对象调用了start()方法之后,该线程就进入就绪状态。
就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。
处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态
等待阻塞
运行状态中的线程执行 wait() 方法,
使线程进入到等待阻塞状态。
使线程进入到等待阻塞状态。
同步阻塞
线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞
通过调用线程的 sleep() 或 join()或suspend() 发出了 I/O 请求时,线程就会进入到阻塞状态。
当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,
线程重新转入就绪状态。
当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,
线程重新转入就绪状态。
死亡状态
一个运行状态的线程完成任务或者其他终止条件发生时,
该线程就切换到终止状态。
该线程就切换到终止状态。
线程优先级
Java 线程的优先级是一个整数
取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )
优先级默认值
NORM_PRIORITY(5)
方法
Thread.getPriority()
返回线程优先值
Thread.setPriority(int newPriority)
改变线程的优先级
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。
但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
网络编程
反射
类装载器Classloader
根装载器
ExtClassLoader(扩展类装载器)
AppClassLoader(系统类装载器)
泛型
参考资料
Java平台结构图
Java基础
Java基础教程{https://www.runoob.com/}
Java集合图示
Java集合导图
阿里巴巴Java开发手册
Java Stream流
源码URL
Openjdk仓库地址
Github非Oracle官方
Java版本历史wiki
版本迭代
Java19
协程
书籍
《Java编程思想》
《EffectiveJava》
《代码整洁之道》
1
2
《架构整洁之道》
1
JVM
《深入理解Java虚拟机?
垃圾回收
垃圾判定规则
引用计数法
根可达算法
垃圾回收算法
垃圾回收器
G1
参考资料
URL
GC各类图示比较全
GC算法和收集器详解
G1垃圾收集器
CMS和G1的异同
GC面试复习题
JVM 垃圾收集算法与垃圾收集器
G1和CMS处理器如何选择和各自的适用场景
详解ZGC垃圾收集器
垃圾回收算法、垃圾回收器CMS、G1、ZGC详解
垃圾回收算法详解
垃圾收集器图示归纳
书籍
《垃圾回收的算法与实现》
参考资料
JVM示意图
示意图各有特色
JVM示意图
JVM全景图(比较全)
JDK1.8 JVM内存结构
JVM调优
Java编译器
JDK自带JVM分析工具
直接内存
Java并发
多线程
操作系统底层
CPU缓存结构
三级缓存结构
空间大小
内存>L3>L2>L1>寄存器
速度快慢
寄存器>L1>L2>L3>内存
CPU为什么需要高速缓存
时间局部性
如果一个信息项被访问,
那么近期它可能再次被访问。
那么近期它可能再次被访问。
空间局部性
如果存储器的位置被引用,
那么久它附近的位置也可能会被引用 。
那么久它附近的位置也可能会被引用 。
CPU运行安全等级
Intel的x86处理器是通过Ring级别来进行访问控制的,
级别共分4层,RING0,RING1,RING2,RING3。
级别共分4层,RING0,RING1,RING2,RING3。
ring0到ring3,安全权限级别依次降低。
Linux和Windows只使用其中的两个级别RING0和RING3。
可理解为ring0为内核级,ring3为用户级。
可理解为ring0为内核级,ring3为用户级。
按照Intel原有的构想,应用程序工作在RING3层,只能访问RING3层的数据,
操作系统工作在RING0层,可以访问所有层的数据,
而其他驱动程序位于RING1、RING2层,每一层只能访问本层以及权限更低层的数据。
如果普通应用程序企图执行RING0指令,则Windows会显示“非法指令”错误信息。
操作系统工作在RING0层,可以访问所有层的数据,
而其他驱动程序位于RING1、RING2层,每一层只能访问本层以及权限更低层的数据。
如果普通应用程序企图执行RING0指令,则Windows会显示“非法指令”错误信息。
JVM创建线程CPU的工作过程
1.CPU从ring3切换到ring0创建线程。
2.线程创建完毕,CPU从ring0切换回ring3。
3.线程执行JVM程序。
4.线程执行完毕,销毁后切换回ring0。
JMM Java内存模型
(Java Memory Model)
(Java Memory Model)
Java内存模型,用来屏蔽掉各种硬件和操作系统的内存访问差异,
以实现让Java程序在各平台下多线程编程都能够达到一致的内存访问效果。
以实现让Java程序在各平台下多线程编程都能够达到一致的内存访问效果。
结构图
JMM与Java内存结构并不是同一个层次的内存划分,两者基本没有关系。
如果一定要勉强对应,那从变量、主内存、工作内存的定义看,
主内存主要对应Java堆中的对象实例数据部分,工作内存则对应虚拟机栈的部分区域。
如果一定要勉强对应,那从变量、主内存、工作内存的定义看,
主内存主要对应Java堆中的对象实例数据部分,工作内存则对应虚拟机栈的部分区域。
主内存是线程共享区域
堆
方法区
工作内存是线程私有区域
程序计数器
本地方法栈
虚拟机栈
线程对变量的操作(读取赋值等)必须在工作内存中进行,
首先要将变量从主内存拷贝的自己的工作内存空间,
然后对变量进行操作,操作完成后再将变量写回主内存,
不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝。
首先要将变量从主内存拷贝的自己的工作内存空间,
然后对变量进行操作,操作完成后再将变量写回主内存,
不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝。
JMM内存同步
(8种原子操作)
(8种原子操作)
示意图
读过程
(作用于主内存变量)
(作用于主内存变量)
lock锁定
把一个变量标识为一条线程独占的状态。
read读取
变量读取到工作内存。此时还未保存到工作内存。
load载入
将读取到工作内存的变量,放入变量副本。
use使用
将变量副本的值传递给执行引擎,
每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
写过程
(作用于工作内存变量)
(作用于工作内存变量)
assign赋值
把从执行引擎接收的值赋值给工作内存变量,
每当虚拟朵遇到一个给变量赋值的字节码指令时执行这个操作。
每当虚拟朵遇到一个给变量赋值的字节码指令时执行这个操作。
store储存
把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
write写入
把store操作从工作内存中得到的变量的值放入主内存的变量中。
unlock解锁
把store操作从工作内存中得到的变量的值放入主内存的变量中。
如果要把一个变量从主内存复制到工作内存,那就要按顺序地执行read和load操作;
如果要把变量从工作内存同步回主内存,就要按顺序执行store和write操作。
如果要把变量从工作内存同步回主内存,就要按顺序执行store和write操作。
前述操作必须顺序执行,但不一定连续执行。
同步规则
不允许read和load、store和write操作之一单独出现,
即不允许一个变量从主内存读取了但工作内存不接受,
或者从工作内存发起回写了但主内存不接受的情况出现。
即不允许一个变量从主内存读取了但工作内存不接受,
或者从工作内存发起回写了但主内存不接受的情况出现。
不允许一个线程丢弃它的最近的assign操作,
即变量在工作内存中改变了之后必须把该变化同步回主内存。
即变量在工作内存中改变了之后必须把该变化同步回主内存。
不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存。
一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。
即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作
即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作
一个变量在同一时刻只允许一条线程对其进行lock操作,
但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会解锁。
lock和unlock必须成对出现.
但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会解锁。
lock和unlock必须成对出现.
如果对一个变量执行lock操作,将会清空工作内存中此变量的值,
在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作,
也不允许去unlock一个被其他线程锁定的变量。
也不允许去unlock一个被其他线程锁定的变量。
对一个变量执行unlock操作之前,
必须把此变量同步回主内存中(执行store和write操作)。
必须把此变量同步回主内存中(执行store和write操作)。
结论
8种原子操作完成对一个变量的完整读写。
虽然线程不同变量的原子操作可以穿插执行,
但是同一个变量的8个原子操作一定是完整的。
虽然线程不同变量的原子操作可以穿插执行,
但是同一个变量的8个原子操作一定是完整的。
long和double型非原子协定
JVM内存模型允许64位数据(long或者double)可以不保证load,store,read,write这4个操作的原子性。
如果多线程的情况下double或long类型并未声明为volatile,
可能会出现“半个变量”的数值,也就是既非原值,也非修改后的值。
可能会出现“半个变量”的数值,也就是既非原值,也非修改后的值。
虽然Java规范允许上面的实现,但商用虚拟机中基本都采用了原子性的操作,
因此在日常使用中几乎不会出现读取到“半个变量”的情况。
因此在日常使用中几乎不会出现读取到“半个变量”的情况。
JMM并发三大问题
并发三大问题
原子性(Atomicity)
原子性指一个操作是不可中断的。
即使是多线程环境,一个操作从开始到结束都不会受其他操作影响。
即使是多线程环境,一个操作从开始到结束都不会受其他操作影响。
可见性(Visibility)
可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
有序性(Ordering)
有序性是指对于单线程执行的代码,我们总是认为代码是按顺序执行的。
JMM解决方案
原子性
synchronized
可见性
volatile
synchronized
有序性
volatile
synchronized
happens-before原则
as-if-serial原则
指令重排序
并发原则
happens-before原则
在JMM中,如果一个操作执行的结果需要对另一个操作可见,
那么这两个操作之间必须要存在happens-before关系。
那么这两个操作之间必须要存在happens-before关系。
部分规则(8种)
程序顺序规则
(Program Order Rule)
(Program Order Rule)
在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。
准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。
准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。
管程锁定规则
(Monitor Lock Rule)
(Monitor Lock Rule)
一个unlock操作先行发生于后面对同一个锁的lock操作。
这里必须强调的是同一个锁,而“后面”是指时间上的先后顺序。
这里必须强调的是同一个锁,而“后面”是指时间上的先后顺序。
volatile变量规则
(Volatile Variable Rule)
(Volatile Variable Rule)
对一个volatile变量的写操作先行发生于后面对这个变量的读操作,
这里的“后面”同样是指时间上的先后顺序。
这里的“后面”同样是指时间上的先后顺序。
volatile与happens-before的关系
线程启动规则
(Thread Start Rule)
(Thread Start Rule)
Thread对象的start()方法先行发生于此线程的每一个动作。
线程中断规则
(Thread Interruption Rule)
(Thread Interruption Rule)
对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,
可以通过Thread.interrupted()方法检测到是否有中断发生。
可以通过Thread.interrupted()方法检测到是否有中断发生。
线程终止规则
(Thread Termination Rule)
(Thread Termination Rule)
线程中的所有操作都先行发生于对此线程的终止检测,
我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
对象终结规则
(Finalizer Rule)
(Finalizer Rule)
一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
传递性
(Transitivity)
(Transitivity)
如果操作A先行发生于操作B,操作B先行发生于操作C,
那就可以得出操作A先行发生于操作C的结论。
那就可以得出操作A先行发生于操作C的结论。
as-if-serial原则
不管指令怎么重排序,在单线程下执行结果不能被改变。
依赖关系(2种)
数据依赖
例
int a = 1; // 1
int b = 2; // 2
int c = a + b; // 3
a和b不存在依赖关系,所以1、2可以进行重排序;
c依赖 a和b ,所以3必须在1、2的后面执行。
c依赖 a和b ,所以3必须在1、2的后面执行。
控制依赖
例
public void use(boolean flag, int a, int b) {
if (flag) { // 1
int i = a * b; // 2
}
}
flag和i存在控制依赖关系。当指令重排序后,
2这一步会将结果值写入重排序缓冲(Reorder Buffer,ROB)的硬件缓存中,
当判断为true时,再把结果值写入变量i中。
2这一步会将结果值写入重排序缓冲(Reorder Buffer,ROB)的硬件缓存中,
当判断为true时,再把结果值写入变量i中。
为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序。
但是as-if-serial规则允许对有控制依赖关系的指令做重排序。
在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果,但是多线程下确有可能会改变结果。
但是as-if-serial规则允许对有控制依赖关系的指令做重排序。
在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果,但是多线程下确有可能会改变结果。
指令重排序
重排序类型(3种)
编译器优化重排序
指令级并行重排序
内存系统重排序
final重排序规则
保证一个对象的构建方法结束前,所有final成员变量都必须完成初始化(前提是没有this引用溢出)。
构建方法内部的final成员变量的存储,并且,假如final成员变量本身是一个引用的话,
这个final成员变量可以引用到的一切存储操作,都不能与构建方法外的将当期构建对象赋值于多线程共享变量的存储操作重排序。
这个final成员变量可以引用到的一切存储操作,都不能与构建方法外的将当期构建对象赋值于多线程共享变量的存储操作重排序。
初始读取共享对象与初始读取该共享对象的final成员变量之间不能重排序。
volatile重排序规则
对volatile语义的扩展保证了volatile变量在一些情况下不会重排序,
volatile的64位变量double和long的读取和赋值操作都是原子的。
volatile的64位变量double和long的读取和赋值操作都是原子的。
volatile读同步块入口之前,volatile写同步块出口之后,
普通读写,可以重排序,否则不能。
普通读写,可以重排序,否则不能。
内存重排序规则
内存屏障
(Memory Barriers)
(Memory Barriers)
内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,
用于控制特定条件下的重排序和内存可见性问题。
用于控制特定条件下的重排序和内存可见性问题。
类型
LoadLoad屏障
对于这样的语句Load1; LoadLoad; Load2,
在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障
对于这样的语句Store1; StoreStore; Store2,
在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障
对于这样的语句Load1; LoadStore; Store2,
在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障
对于这样的语句Store1; StoreLoad; Load2,
在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
它的开销是四种屏障中最大的。在大多数处理器的实现中,
这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
参考资料
Java内存访问重排序研究
锁
锁的内存语义
当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。
线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A 对共享变量所做修改的)消息。
线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A 对共享变量所做修改的)消息。
当线程获取锁时,JMM会把该线程对应的本地内存置为无效。
从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。线程B获取一个锁,
实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共 享变量所做修改的)消息。
从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。线程B获取一个锁,
实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共 享变量所做修改的)消息。
并发关键字
volatile
内存语义
当一条线程修改了volatile变量的值,
新值对于其他线程来说是可以立即得知的。
新值对于其他线程来说是可以立即得知的。
可见性
通过两点实现
volatile变量修改后会强制刷新到主内存中。
volatile变量修改并刷新到主内存后,
其他线程工作内存中的对应volatile变量失效。
因此再读取该变量要从主内存读取。
其他线程工作内存中的对应volatile变量失效。
因此再读取该变量要从主内存读取。
MESI缓存一致性协议
多个cpu从主内存读取数据到高速缓存中,
如果其中一个cpu修改了数据,会通过总线立即回写到主内存中,
其他cpu会通过总线嗅探机制感知到缓存中数据的变化并将工作内存中的数据失效,
再去读取主内存中的数据。
如果其中一个cpu修改了数据,会通过总线立即回写到主内存中,
其他cpu会通过总线嗅探机制感知到缓存中数据的变化并将工作内存中的数据失效,
再去读取主内存中的数据。
禁止指令重排序优化。
有序性
通过在指令前后插入内存屏障实现。
不能保证原子性
i++是两步操作,不具备原子性。
使用
修饰变量。简单变量或者对象等。
happens-before关系
volatile变量原则
写先发生于读
参考资料
一文搞懂volatile的可见性原理
synchronized
synchronized 是 Java 中的关键字,是利用锁的机制来实现同步的。
使用
使用场景
类锁
对象锁
方法锁
静态方法
非静态方法
代码块锁
synchronized(this|object) {}
synchronized(类.class) {}
Java的对象锁和类锁
对象锁
在 Java 中,每个对象都会有一个 monitor 对象,
这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。
类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。
这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。
类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。
类锁
在 Java 中,针对每个类也有一个锁,可以称为“类锁”,
类锁实际上是通过对象锁实现的,即类的 Class 对象锁。
每个类只有一个 Class 对象,所以每个类只有一个类锁。
类锁实际上是通过对象锁实现的,即类的 Class 对象锁。
每个类只有一个 Class 对象,所以每个类只有一个类锁。
使用规则
synchronized关键字不能继承。
对于父类中的 synchronized 修饰方法,子类在覆盖该方法时,
默认情况下不是同步的,必须显示的使用 synchronized 关键字修饰才行。
默认情况下不是同步的,必须显示的使用 synchronized 关键字修饰才行。
在定义接口方法时不能使用synchronized关键字。
构造方法不能使用synchronized关键字,
但可以使用synchronized代码块来进行同步。
但可以使用synchronized代码块来进行同步。
注意
锁对象不能为空,因为锁的信息都保存在对象头里
作用域不宜过大,影响程序执行的速度,控制范围过大,编写代码也容易出错
避免死锁
尽量避免用效率低的锁
synchronized锁的原理
加锁释放锁原理
synchronized通过监视器monitor的命令实现
monitorenter指令
加锁
monitorexit指令
释放锁
对象监视器与执行线程关系
可重入原理
Synchronized先天具有重入性。每个对象拥有一个计数器,
当线程获取该对象锁后,计数器就会加一,释放锁后就会将计数器减一。
当线程获取该对象锁后,计数器就会加一,释放锁后就会将计数器减一。
synchronized并发特性
原子性
因为synchronized是给共享变量加锁了,以阻塞的机制去同步,
在对共享变量进行读/写操作的时候是原子性的。
在对共享变量进行读/写操作的时候是原子性的。
可见性
Synchronized的happens-before规则
监视器锁规则:对同一个监视器的解锁,happens-before于对该监视器的加锁。
示意图
有序性
因为synchronized是给共享变量加锁,即使用阻塞的同步机制,
共享变量只能同时被一个线程操作,
所以JMM不用像volatile那样考虑加内存屏障去保证synchronized多线程情况下的有序性,
因为CPU在单线程情况下是保证了有序性的。
共享变量只能同时被一个线程操作,
所以JMM不用像volatile那样考虑加内存屏障去保证synchronized多线程情况下的有序性,
因为CPU在单线程情况下是保证了有序性的。
synchronized优化
Mutex Lock
JVM中monitorenter和monitorexit字节码依赖于底层的操作系统的Mutex Lock来实现
该操作会发生用户态到内核态的切换,所以synchronized性能消耗大。
synchronized锁优化(JDK1.6)
依赖技术
锁粗化(Lock Coarsening)
减少不必要的紧连在一起的unlock,lock操作,将多个连续的锁扩展成一个范围更大的锁。
锁消除(Lock Elimination)
通过运行时JIT编译器的逃逸分析来消除一些
没有在当前同步块以外被其他线程共享的数据的锁保护。
没有在当前同步块以外被其他线程共享的数据的锁保护。
逃逸分析
它的目的就是判断哪些对象是可以存储在栈内存中而不用存储在堆内存中的,
从而让其随着线程的消逝而消逝,进而减少了 GC 发生的频率,
这也是常见的 JVM 优化技巧之一。
从而让其随着线程的消逝而消逝,进而减少了 GC 发生的频率,
这也是常见的 JVM 优化技巧之一。
参考资料
详解 JVM 逃逸分析
通过逃逸分析也可以在线程本地Stack上
进行对象空间的分配(同时还可以减少Heap上的垃圾收集开销)。
进行对象空间的分配(同时还可以减少Heap上的垃圾收集开销)。
轻量级锁(Lightweight Locking)
偏向锁(Biased Locking)
适应性自旋(Adaptive Spinning)
Java对象头
对象的Hashcode
对象分代年龄
是否是偏向锁标志位
锁标志位
synchronized锁的特性
悲观锁、不阻塞、非公平锁、可重入锁、排它锁
synchronized锁膨胀过程
无锁
偏向锁
轻量级锁
重量级锁
锁膨胀过程参看流程图,具体查看参考资料。
1
Synchronized与Lock对比
synchronized的缺点
效率低
存在用户态和内核态切换。
不够灵活
加锁和释放的时机单一,不能中断或超时退出,可能造成死锁。
无法知道是否获得锁
没有获知锁状态的方法。
Lock
四个方法
lock()
unlock()
tryLock()
尝试获取锁,返回一个boolean值
tryLock(long,TimeUtil)
尝试获取锁,可以设置超时
优点
弥补了synchronized的缺点。
参考资料
synchronized使用场景详解
synchronized详解
深入剖析synchronized
更底层,分析汇编层面synchronized实现,着重详解锁优化的过程。
深入分析synchronized原理和锁膨胀过程
final
使用
修饰类
不能继承
修饰方法
private是隐式的final
可以重载
修饰参数
无法更改参数指向的对象
主要用来想匿名内部类传递数据。
修饰变量
初始化后无法修改。编译期允许未初始化final变量,除非加static修饰则编译期初始化。
重排序规则
见前述重排序
补充
Synchronized与volatile区别
volatile只能修饰变量,而synchronized可以修改变量,方法以及代码块。
volatile在多线程中不会存在阻塞问题,synchronized会存在阻塞问题。
volatile能保证数据的可见性,但不能完全保证数据的原子性,
synchronized即保证了数据的可见性也保证了原子性。
synchronized即保证了数据的可见性也保证了原子性。
volatile解决的是变量在多个线程之间的可见性,
而sychroized解决的是多个线程之间访问资源的同步性。
而sychroized解决的是多个线程之间访问资源的同步性。
参考资料
JMM详解
JUC
(java.util.concurrent)
(java.util.concurrent)
原子类(aotmic)
CAS比较替换
(Compare-And-Swap)
(Compare-And-Swap)
是什么
是一条CPU的原子指令
让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值。
是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的。
CAS操作是原子性的,所以多线程并发使用CAS更新数据时,可以不使用锁。
JDK中大量使用了CAS来更新数据而防止加锁(synchronized 重量级锁)来保持原子更新。
JDK中大量使用了CAS来更新数据而防止加锁(synchronized 重量级锁)来保持原子更新。
例
CAS问题
ABA问题
问题描述
一个值原来是A,更新为B,后来有更新回A。
CAS检查会认为没变化,实际发生了两次变化。
CAS检查会认为没变化,实际发生了两次变化。
解决方案
使用版本号
在变量前面追加上版本号,每次变量更新的时候把版本号加1,
那么A->B->A就会变成1A->2B->3A。
那么A->B->A就会变成1A->2B->3A。
AtomicStampedReference
(JDK1.5)
(JDK1.5)
循环时间长开销大
问题描述
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
解决方案
支持CPU提供的pause命令
只能保证一个共享变量原子操作
问题描述
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,
但是对多个共享变量操作时,循环CAS就无法保证操作的原子性。
但是对多个共享变量操作时,循环CAS就无法保证操作的原子性。
解决方案
加锁
AtomicReference类(JDK1.5)
Unsafe
是什么
Unsafe是位于sun.misc包下的一个类
主要提供一些用于执行低级别、不安全操作的方法,
如直接访问系统内存资源、自主管理内存资源等,
这些方法在提升Java运行效率、
增强Java语言底层资源操作能力方面起到了很大的作用。
如直接访问系统内存资源、自主管理内存资源等,
这些方法在提升Java运行效率、
增强Java语言底层资源操作能力方面起到了很大的作用。
能做什么
内存操作
分配、拷贝、扩充、释放堆外内存
设置、获得给定地址中的值
内存屏障
禁止loadstore重排序
CAS
Class相关
动态创建类
获取field的内存地址偏移量
检测、确保类初始化
对象操作
获取对象成员属性的内存偏移量
非常规对象实例化
存储、获取指定偏移地址的变量值
数组相关
返回数组元素内存大小
返回数组首元素偏移地址
系统相关
返回内存页大小
返回系统指针大小
线程调度
线程挂起、恢复
获取、释放资源
Unsafe与CAS
内部使用自旋的方式进行CAS更新(while循环进行CAS更新,如果更新失败,则循环再次重试)
Unsafe只提供了3种CAS方法:compareAndSwapObject、compareAndSwapInt和compareAndSwapLong。
都是native方法。
都是native方法。
Unsafe底层
通过 Atomic::cmpxchg 来实现比较和替换操作。
参考资料
Java魔法类:Unsafe应用解析
原子类
原理
volatile保证线程的可见性,多线程并发时,
一个线程修改数据,可以保证其它线程立马看到修改后的值
一个线程修改数据,可以保证其它线程立马看到修改后的值
CAS 保证数据更新的原子性。
种类(12个)
基本类型
AtomicBoolean,AtomicInteger,AtomicLong是类似的,分别针对bool,interger,long的原子类。
数组
AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray是数组原子类。
引用
AtomicReference,AtomicMarkedReference,AtomicStampedReference是引用相关的原子类。
原子更新字段类
AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater是FieldUpdater原子类。
锁(locks)
AQS
抽象基础类AbstractQueuedSynchronizer(抽象同步器类,简称为AQS)
锁与队列的关系
无论是单体服务应用内部的锁,还是分布式环境下多体服务应用所使用的分布式锁,为了减少由于无效争夺导致的资源浪费和性能恶化,一般都基于队列进行排队与削峰。
1.CLH锁的内部队列
CLH自旋锁
CLH(Craig,Landin,and Hagersten Lock Queue)是一个单向队列,也是一个FIFO队列。
图示
1
2.分布式锁的内部队列
在分布式锁的实现中,比较常见的是基于队列的方式进行不同节点中“等锁线程”的统一调度和管理。
以基于ZooKeeper的分布式锁为例
图示
1
3.AQS的内部队列
AQS是JUC提供的一个用于构建锁和同步容器的基础类。
JUC包内许多类都是基于AQS构建的,AQS解决了在实现同步容器时设计的大量细节问题。
ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock、FutureTask
AQS是CLH队列的一个变种,主要原理和CLH队列差不多。
图示
1
AQS的核心成员
AQS出于“分离变与不变”的原则,基于模板模式实现。
AQS为锁获取、锁释放的排队和出队过程提供了一系列的模板方法。
显式锁
来源
Java内置锁(synchronized)功能相对单一,不具备高级锁的功能。
基于Java内置锁(synchronized)的缺点,JDK 5版本引入了Lock接口,Lock是Java代码级别的锁。为了与Java对象锁相区分,Lock接口叫作显式锁接口,其对象实例叫作显式锁对象。
Lock接口
Lock接口位于java.util.concurrent.locks包中,是JUC显式锁的一个抽象。
抽象方法
优点
(1)可中断获取锁
Lock.lockInterruptibly()
(2)可非阻塞获取锁
Lock.tryLock()
(3)可限时抢锁
Lock.tryLock(long time,TimeUnit unit)
通过实现类显式锁还能实现具有其他功能的锁。
可重入锁ReentrantLock
定义
ReentrantLock类实现了Lock接口。
它拥有与synchronized相同的并发性和内存语义,但是拥有了限时抢占、可中断抢占等一些高级锁特性。
ReentrantLock基于内置的抽象队列同步器(Abstract Queued Synchronized,AQS)实现,在争用激烈的场景下,能表现出表内置锁更佳的性能。
悲观锁和乐观锁
公平锁与非公平锁
可中断锁与不可中断锁
共享锁与独占锁
读写锁
Java锁(14种)
线程要不要锁住同步资源?
锁住
悲观锁
悲观锁认为一个数据发生并发一定会发生修改,
所以要对数据的并发操作加锁,悲观锁是比较重量级的锁。
所以要对数据的并发操作加锁,悲观锁是比较重量级的锁。
不锁住
乐观锁
乐观锁认为一个数据发生并发不会发生修改,
可以通过版本机制,当发生修改的时候通过不断尝试修改。
可以通过版本机制,当发生修改的时候通过不断尝试修改。
锁住同步资源失败,线程要不要阻塞?
阻塞
不阻塞
自旋锁
适应性自旋锁
多个线程竞争锁时要不要排队?
排队
公平锁
先插队,插队失败再排队
非公平锁
一个线程的多个流程能不能获取同一把锁?
能
可重入锁
不能
不可重入锁
多个线程能不能共享一把锁?
能
共享锁
不能
排它锁
多个线程竞争同步资源的流程细节有没有区别?
不锁住资源,多个线程只有一个会成功,其他失败重试
无锁
同一个线程执行同步资源自动获取资源
偏向锁
多个线程竞争同步资源时,没有获取资源的线程自旋等待锁释放
轻量级锁
多个线程竞争同步资源时,没有获取资源的线程阻塞等待唤醒
重量级锁
参考资料
不可不说的Java“锁”事
并发集合
并发工具
线程池
参考资料
Java并发
Github脑图详细
1
2
3
书籍
《深入理解Java虚拟机》
《Java并发编程的艺术》
《Java高并发核心编程》(卷1)/(卷2)
Sping
SpringFreamwork
Spring编程模型
面向对象编程
契约接口
Aware
BeanPostProcessor
设计模式
对象继承
面向切面编程
动态代理
字节码提升
面向元编程
注解
配置
泛型
函数驱动
函数接口
模块驱动
MavenArtifacts
OSGI Bundles
Java9 Automatic Modules
Spring @Enable*
IoC
IoC简介
IoC主要实现策略
IoC的职责
IoC和DI的区别
IoC容器的实现
AOP
Spring事务
参考资料
Spring @Import注解全流程图
SpringMVC
SpringBoot
自动配置原理
SpringBoot 源码常用注解拾遗
@Value
【Spring 提供】
@ConfigurationProperties
【SpringBoot 提供】
@Import
【Spring 提供】
三种使用方式
直接导入普通的 Java 类。
配合自定义的 ImportSelector 使用。
配合 ImportBeanDefinitionRegistrar 使用。
@Conditional
【Spring提供】
@Conditional 注释可以实现只有在特定条件满足时才启用一些配置。
子主题
SpringBoot 启动过程
创建 SpringApplication 对象。
运行run()方法。
SpringBoot 自动配置原理
参考资料
Springboot启动流程图
类详细图
Springboot启动简易流程
springboot启动流程步骤图
Springboot启动流程图对照参考
Springboot启动流程图对照参考2
Springboot启动流程图对照参考3
Springboot启动流程图对照参考4
Springboot启动过程详细图三部曲
1
2
3
Springboot基础知识
SpringBoot百科全书
SpringCloud
参考资料
Spring基础
SpringCloud知识导图
Spring百科
Spring家族核心成员对比
Spring,SpringMVC,SpringBoot,SpringCloud有什么区别和联系?
MyBatis
参考资料
MyBatis原理
类流转图,详细
MyBatis逻辑图
逻辑图,很详细
MyBatis类图
Mybatis类方法流程图
MyBatis时序图
Mybatis基础知识
MyBatis基础知识
关键知识点有结构图易于理解
MyBatis知识总结
缓存知识点总结详细
MyBatis-Plus
参考资料
代码常用工具
Bean对象转换工具对比
VertX
开发工具
IDEA
下载
与JDK版本对应关系
其他语言
Go
Go语言基础
特点
天生高并发
参考资料
Go语法基础
Go、Java和Rust对比
Rust
参考资料
我为什么反对使用Rust?
文中指出的Rust的问题很专业,如同Rust的优点也很突出一样。时代是进步的,Rust也是。
2021年Rust行业调研报告
Python
Ruby
C
C++
Haskell
Lisp
SQL
存储
数据库
常规
MySQL
PolarDB
参考资料
PolarDB MySQL引擎 云原生数据库
阿里云官方文档
Oracle
DM
国内非主流数据库
大数据
数仓
数据湖
架构层次
数据传输层
数据计算层
数据存储层
平台治理层
数据库类型
关系数据库
MySQL
MariaDB
Percona Server(MySQL的代替品·)
PostgreSQL
Microsoft Access
Microsoft SQL Server
Google Fusion Tables
FileMaker
Oracle
Sybase
dBASE
Clipper
FoxPro
foshub
非关系数据库
NoSQL
列式
HBase
Cassandra
ClickHouse
BigTable(Google)
键值(K-V)数据库
Apache Cassandra(为Facebook所使用)
Dynamo
LevelDB(Google)
Redis
Memcached
文档
MongoDB
CouchDB
完全拥抱Web的数据库
时序
InfluxDB
Prometheus
搜索
Elasticsearch
分类账数据库
Amazon QLDB
图数据库
Amazon Neptune
Neo4J
NewSQL
NewSQL可以理解为是传统的关系型数据库与NoSQL结合的产物。它试图将传统关系数据库的数据一致性优势与NoSQL平台的可伸缩性结合起来。
参考资料
数据库种类有哪些,各有什么特点?
https://www.zhihu.com/question/414923789
众多的数据库类型,你该怎么选择?
数据库排名
https://db-engines.com/en/ranking
OLTP & OLAP
OLTP (On-Line Transaction Processing)
主要做实时事务处理
OLAP (On-Line Analytical Processing)
数据仓库,主要做历史数据分析,为商业决策提供支持
架构
Lambda架构设计
Kappa架构设计
混合架构设计
演化趋势HTAP架构
OLTP架构与OLAP架构融合趋势
参考资料
大数据架构详解
Hadoop生态技术
数仓优化
大数据技术知识体系
技术工具覆盖比较全
大数据生态技术组件
中间件
服务治理
Dubbo
参考资料
RPC 调用和 HTTP 调用的区别
RPC框架
RPC的底层原理
1
Dubbo 在 K8s 下的思考
Netty
背景
为什么选netty而不用JDK NIO?
Netty为什么选NIO而不是AIO?
BIO、NIO、AIO详解
同类框架,为什么选netty?
Sun Grizzly/Cindy/Jetty/Apple SwiftNIO/ACE/Apache Mina对比
技术要点
粘包和半包
粘包半包
粘包
多个数据被合并成一个发送
原因
写入的数据远小于缓冲区的大小,
TCP协议为了性能的考虑,合并后再进行发送。
TCP协议为了性能的考虑,合并后再进行发送。
半包
一个数据被拆分成多个发送
原因
写入的数据大于缓冲区的大小,
因此必须拆包后再进行传输(缓冲区已满,强制flush)
因此必须拆包后再进行传输(缓冲区已满,强制flush)
写入的数据大于协议的MTU(最大传输单元),
因此必须拆包后再进行传输。
因此必须拆包后再进行传输。
解决方案
短连接
一个请求一个TCP短连接
简单但是效率低下,不可取。
封装成帧
(Framing)
(Framing)
固定长度
在包尾增加分割符,
比如回车换行符进行分割, 例如 FTP 协议。
比如回车换行符进行分割, 例如 FTP 协议。
FixedLengthFrameDecoder
分割符
消息定长, 例如每个报文的大小为固定长度 200 字节,
如果不够, 空位补空格。
如果不够, 空位补空格。
DelimiterBasedFrameDecoder
LineBasedFrameDecoder+StringDecoder
固定长度字段表示
消息体长度信息
消息体长度信息
将消息分为消息头和消息体。
消息头中包含表示消息总长度(或者消息体长度)的字段,
通常消息头的第一个字段使用 int32 来表示消息的总长度。
消息头中包含表示消息总长度(或者消息体长度)的字段,
通常消息头的第一个字段使用 int32 来表示消息的总长度。
LengthFieldBasedFrameDecoder
更复杂的应用层协议
编解码
keepalive与idle监测
netty的锁
netty的内存使用
零拷贝
参考资料
深入剖析Linux IO原理和几种零拷贝机制的实现
Go 语言中的零拷贝优化
netty对reactor的支持
参考资料
Netty基础技术示意图
Netty高性能原理示意图
Netty源码分析示意图
网络请求的完整细节示意图
网络编程与高效IO
netty介绍
1
注册中心
Nacos
官网
缺点
配置管理功能比较简陋
不能分环境
导入功能限制多。容易出现"未读取到合法数据,请检查导入的数据文件。"错误
Consul
ZooKeeper
Eureka
注册中心对比
网关
Gateway
负载均衡
feign
ribbon
Nginx
SLB
分布式事务
Seata
Seata-go TCC 设计与实现
缓存
Redis
Dragonfly
github地址
Memcached
MongoDB
encache
参考资料
高并发场景下,6种方案,保证缓存和数据库的最终一致性!
旁路缓存模式
Cache-Aside
Cache-Aside
补偿机制
读穿透模式
Read-Through
Read-Through
直写模式
Write-Through
Write-Through
异步回写模式
Write behind
Write behind
定时任务
XxlJob
消息队列
MQ理论
MQ三大特性
异步解耦
上下游服务之间通过MQ传递消息达到异步解耦的目的。
图示
限流削峰
MQ可以将系统的超量请求暂存其中,以便系统后期可以慢慢进行处理,从而避免了请求的丢失或系统被压垮。
图示
子主题
子主题
数据收集
业务日志、监控数据、用户行为等海量数据通过MQ收集。
RocketMQ
官网
RabbitMQ
Kafka
高吞吐低延迟MQ,使用数据量的流式传输。
如日志、用户活动跟踪、运营指标等等。
如日志、用户活动跟踪、运营指标等等。
ActiveMQ
Apache开源MQ中间件,消息堆积能力不足,一般不考虑。
各消息队列对比
主流消息队列MQ对比
消息队列MQ介绍,主流MQ对比
MQ详解及四大MQ比较
搜索
ElasticSearch
参考资料
ES最全知识导图
SEO搜索引擎优化
参考资料
SEO学习框架及实操
导图内容比较详实
监控
参考资料
腾讯最完整的监控体系介绍,看这篇就够了!
系统架构设计——监控系统的架构设计
一篇文章全面了解监控知识体系
监控系统选型,一篇全搞定!
理论
数据结构
结构
逻辑结构
集合结构
数据元素同属一个集合,相互之间没有任何关联
线性结构
线性表
存储结构
顺序存储结构
顺序表
数组
特点
存储空间连续
查询速度快,插入删除慢
O(1)随机访问
平均O(n)的插入和删除
链式存储结构
链表
存储方式
静态链表
静态链表是用类似于数组方法实现的,是顺序的存储结构,
在物理地址上是连续的,而且需要预先分配地址空间大小。
在物理地址上是连续的,而且需要预先分配地址空间大小。
所以静态链表的初始长度一般是固定的,
在做插入和删除操作时不需要移动元素,仅需修改指针。
在做插入和删除操作时不需要移动元素,仅需修改指针。
动态链表
动态链表是用内存申请函数(malloc/new)动态申请内存的,
所以在链表的长度上没有限制。
所以在链表的长度上没有限制。
动态链表因为是动态申请内存的,
所以每个节点的物理地址不连续,
要通过指针来顺序访问。
所以每个节点的物理地址不连续,
要通过指针来顺序访问。
分类
单(向)链表
头插法
尾插法
双(向)链表
循环链表
单(向)循环链表
双(向)循环链表
矩阵压缩存储
压缩存储
指为多个值相同的元只分配一个存储空间,对零元不分配空间。
矩阵分类
特殊矩阵
定义
排布着有规律的非零元的矩阵称为特殊矩阵
(非零元排布有规律,也可以说是零元排布有规律)。
我们可以找出规律把矩阵(N维数组)转为一维数组来存储。
(非零元排布有规律,也可以说是零元排布有规律)。
我们可以找出规律把矩阵(N维数组)转为一维数组来存储。
对称矩阵
上/下三角矩阵
对角矩阵
稀疏矩阵
定义
稀疏矩阵含有的非零元很少且无规律。
参考资料
矩阵的压缩存储
特殊矩阵的压缩存储
栈(特殊线性表)
特点
先进后出
存储结构
顺序栈
链式栈
分类
共享栈
共享栈,即是两个栈使用同一段存储空间。
两个栈指针重合为栈满。
共享栈的好处在于提高栈空间利用率。
可能存在一个栈快溢出,另一个栈还没怎么用的情况。
可能存在一个栈快溢出,另一个栈还没怎么用的情况。
卡特兰数
参考资料
卡特兰数
应用
括号匹配
表达式求值
递归
队列(特殊线性表)
特点
先进先出(FIFO)
存储结构
顺序队列
链式队列
分类
普通队列
循环队列
双端队列
阻塞队列
并发队列
阻塞并发队列
应用
缓冲区队列
进程调度
层次遍历
串(字符串)
存储结构
定长顺序存储
堆分配存储
块链存储
匹配算法
树形结构
树
二叉树
存储结构
顺序存储
链式存储
逻辑结构
满二叉树
定义
一颗深度为k且有2^k-1个结点的二叉树称为满二叉树。
除叶子结点外的所有结点均有两个子结点。节点数达到最大值。
所有叶子结点必须在同一层上。
所有叶子结点必须在同一层上。
完全二叉树
定义
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,
第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
剪枝
操作
遍历
例图
广度优先搜索
层次遍历
只需按层次遍历即可。
深度优先搜索
前序遍历
根结点 ---> 左子树 ---> 右子树
中序遍历
左子树---> 根结点 ---> 右子树
后序遍历
左子树 ---> 右子树 ---> 根结点
新增
修改
删除
应用
哈夫曼树
(最优二叉树)
(最优二叉树)
给定n个权值作为n的叶子结点,
构造一棵二叉树,若带权路径长度达到最小,
称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman tree)。
构造一棵二叉树,若带权路径长度达到最小,
称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman tree)。
二叉排序树
(二叉查找树)
(二叉查找树)
二叉排序树或者是一棵空树
或者是具有下列性质的二叉树
若左子树不空,则左子树上所有结点的值均小于它的根结点的值。
若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值。
左、右子树也分别为二叉排序树。
没有键值相等的节点。
平衡二叉树
定义
任意节点的子树的高度差都小于等于1。
AVL树
应用
Windows进程地址空间管理
参考资料
彻底搞懂AVL树
红黑树
五性质
1.每个结点要么是红的要么是黑的。
2.根结点是黑的。
3.每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。
4.如果一个结点是红的,那么它的两个儿子都是黑的。
5.对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。
例图
要点
旋转
左旋
右旋
插入
插入步骤
1.查找插入位置
2.插入后自平衡
删除
应用
HashMap
线索二叉树
参考资料
理解线索二叉树
多叉树
分类
B树
B-树
2-3树
2-3-4树
Trie
(字典树或前缀树)
(字典树或前缀树)
应用
trie树常用于搜索提示。
如当输入一个网址,可以自动搜索出可能的选择。
当没有完全匹配的搜索结果,可以返回前缀最相似的可能。
如当输入一个网址,可以自动搜索出可能的选择。
当没有完全匹配的搜索结果,可以返回前缀最相似的可能。
B+树
B树与B+树对比
以一个m阶树为例
关键字的数量不同
B+树中分支结点有m个关键字,其叶子结点也有m个,
其关键字只是起到了一个索引的作用,
但是B树虽然也有m个子结点,但是其只拥有m-1个关键字。
其关键字只是起到了一个索引的作用,
但是B树虽然也有m个子结点,但是其只拥有m-1个关键字。
存储的位置不同
B+树中的数据都存储在叶子结点上,
也就是其所有叶子结点的数据组合起来就是完整的数据,
但是B树的数据存储在每一个结点中,并不仅仅存储在叶子结点上。
也就是其所有叶子结点的数据组合起来就是完整的数据,
但是B树的数据存储在每一个结点中,并不仅仅存储在叶子结点上。
分支结点的构造不同
B+树的分支结点仅仅存储着关键字信息和儿子的指针
(这里的指针指的是磁盘块的偏移量),
也就是说内部结点仅仅包含着索引信息。
(这里的指针指的是磁盘块的偏移量),
也就是说内部结点仅仅包含着索引信息。
查询不同
B树在找到具体的数值以后,则结束,而B+树则需要通过索引找到叶子结点中的数据才结束,
也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。
也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。
为什么说B+树比B树更适合实际应用中操作系统的文件索引和数据库索引?
B+树的磁盘读写代价更低
B+的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B树更小。
如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。
一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。
一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
B+树的查询效率更加稳定
由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。
所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
数据库索引采用B+树的主要原因是 B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。
B+树只要遍历叶子节点就可以实现整棵树的遍历。
堆
定义
堆是一种完全二叉树
分类
最大堆
父节点的值比每一个子节点的值都要大
最小堆
父节点的值比每一个子节点的值都要小
实现
插入Insert(push)
删除pop
建堆
应用
优先级队列
排序
斐波那契堆
二项堆
参考资料
数据结构:堆(Heap)
森林
森林是m(m>0)棵互不相交的树的集合。
存储结构
双亲表示法
孩子表示法
孩子兄弟表示法
操作
与二叉树互转
树转换二叉树
森林转换二叉树
二叉树转换森林
遍历
先根遍历
后根遍历
参考资料
树(维基百科)
深入研究可以对维百这部分内容梳理一下。
图状结构
图
定义
图的存储
有向图
邻接多重表
十字链表
无向图
邻接矩阵法
邻接表法
图的遍历
BFS
单源最短路径问题
广度优先生成树、森林
DFS
深度优先生成树、森林
应用
最小生成树
Prim算法
Kruskal算法
最短路径
Dijkstra算法
Floyd算法
拓扑排序
AOV网
关键路径
AOE网
二分图
散列表
散列函数
冲突解决
链表法
开放寻址
动态扩容
位图
其他结构
位图
跳跃表
R树
参考资料
空间数据索引RTree(R树)完全解析及Java实现
补充
线性表,顺序表,链表,数组的区别与联系?
存储结构
顺序存储
随机访问
存储密度高
添加和删除麻烦
链式存储
随取随用
添加和删除麻烦
不能随机访问
索引存储
Hash存储
Key-Value存储
参考资料
《大话数据结构》
数据结构
内容结构详细
图示数据结构
数据结构与算法-图解版
算法
特性
有穷性
确定性
可行性
输入输出
算法分析
复杂度分析
时间复杂度(O(x))
分类
常数阶
O(1)
对数阶
O(logN)
线性阶
O(n)
nlogn阶
O(nlogn)
平方阶
O(n²))
立方阶
O(n³)
指数阶
O(2^n)
阶乘阶
O(n!)
n^n阶
O(n^n)
对比
O(1)<O(lgn)<O(n)<O(nlgn)<O(n²))<O(n³)<O(2^n)<O(n!)<O(n^n)
分析标准
最好时间复杂度
最坏时间复杂度
平均时间复杂度
均摊时间复杂度
摊还分析
聚合分析
核分析
势能分析
空间复杂度(S(x))
算法思想
枚举算法
将问题的所有可能答案一一列举,
根据判断条件判断此答案是否合适,一般用循环实现。
根据判断条件判断此答案是否合适,一般用循环实现。
运用
百钱买鸡
填写运算符
复杂度
时间复杂度
通常O(n)
具体看实际的场景
递推算法
顺推法
从已知条件出发,
逐步推算出要解决问题的方法。
逐步推算出要解决问题的方法。
运用
斐波那契数列
兔子问题
逆推法
从已知结果出发,
用迭代表达式逐步推算出问题开始的条件,
即顺推法的逆过程。
用迭代表达式逐步推算出问题开始的条件,
即顺推法的逆过程。
运用
银行存款
递归算法
递归算法实际上是把问题转化为规模缩小了的同类问题的子问题,
然后再递归调用函数或过程来表示问题的解。
然后再递归调用函数或过程来表示问题的解。
必须有一个明确的递归结束条件,
如果递归次数过多,容易造成栈溢出。
如果递归次数过多,容易造成栈溢出。
运用
汉诺塔问题
阶乘问题
迭代算法
(辗转算法)
(辗转算法)
是一种不断用变量的旧值递推新值的过程,解决问题时总是重复利用一种方法。
过程
确定迭代变量:直接或间接地不断由旧值递推出新值的变量。
建立迭代关系式:新值与旧值的公式或关系。(解决迭代问题的关系)。
对迭代过程进行控制:确定迭代过程什么时候结束。
所需的迭代次数是个确定值,可以计算出来,
可以构建一个固定次数的循环来实现对迭代过程的控制。
可以构建一个固定次数的循环来实现对迭代过程的控制。
所需的迭代次数无法确定,
需要进一步分析出用来结束迭代过程的条件。
需要进一步分析出用来结束迭代过程的条件。
运用
求平方根问题
贪心算法
从问题的一个初始解出发,
每一次都作出“当前最优”的选择,
直至遇到局部极值点。
每一次都作出“当前最优”的选择,
直至遇到局部极值点。
局限
不能保证最后的解是最优的;
不能求最大最小解问题;
只能求满足某些约束条件的可行解范围。
很容易陷入局部最优的情况。
过程
从问题的某一初始解出发
while能向给定总目标前进一步
求出可行解的一个解元素
由所有解元素组合成问题的一个可行解
运用
装箱问题
找零方案
活动选择问题
背包问题
多机调度问题
回溯算法
(试探算法)
(试探算法)
在试探算法中,放弃当前候选解,并继续寻找下一个候选解的过程称为回溯。
扩大当前候选解的规模,以继续试探的过程称为向前试探。
扩大当前候选解的规模,以继续试探的过程称为向前试探。
为求得问题的正确解,会先委婉地试探某一种可能情况。
在进行试探过程中,一旦发现原来选择的假设情况是不正确的,
马上会自觉地退回一步重新选择,然后继续向前试探。
反复进行,直到得到解或证明无解时才死心。
在进行试探过程中,一旦发现原来选择的假设情况是不正确的,
马上会自觉地退回一步重新选择,然后继续向前试探。
反复进行,直到得到解或证明无解时才死心。
步骤
针对所给问题,定义问题的解空间。
确定易于搜索的解空间结构。
以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
运用
八皇后问题
29选7彩票组合
0-1背包
N皇后问题
模拟算法
对真实事物或者过程的虚拟。
运用
猜数字游戏
掷骰子问题
分治算法
将一个规模为N的问题分解为K个规模较小的子问题,
这些子问题相互独立且与原问题性质相同,
只要求出子问题的解,就可得到原问题的解。
这些子问题相互独立且与原问题性质相同,
只要求出子问题的解,就可得到原问题的解。
步骤
分解
将要解决的问题划分成若干个规模较小的同类问题。
求解
当子问题划分得足够小时,用较简单的方法解决。
合兵
按原问题的要求,将子问题的解逐层合并构成原问题的解。
运用
大数相乘问题
比赛日程安排
归并排序
最大子段和问题
动态规划
关键词
递归(递归式)
表记录
已解决的子问题的答案
根据子问题求解原问题的解
子问题不独立
最优解(可选项)
步骤
找出最优解的性质,刻画其结构特征。
递归地定义最优解。
以自底向上的方式计算出最优值。
根据计算最优值时得到的信息,构造一个最优解。
无需求出最优值,本步可以省略;
若需求出问题的一个最优解,则必须执行本步骤。
若需求出问题的一个最优解,则必须执行本步骤。
适用场景
最优子结构。一个问题的最优解包含了其子问题的最优解。
重叠子问题。原问题的递归算法可以反复地解同样的子问题,而不是总是产生新的子问题。
动态规划主要就是用来解决多阶段决策的问题,
但是实际问题中往往很难有统一的处理方法,
必须结合问题的特点来进行算法的设计,
这也是这种算法很难真正掌握的原因。
但是实际问题中往往很难有统一的处理方法,
必须结合问题的特点来进行算法的设计,
这也是这种算法很难真正掌握的原因。
运用
0-1背包问题
矩阵链乘问题
最长公共子序列(LCS)
分支界限算法
关键字
解空间(广度优先、最小耗费优先)
界限函数(队列式、优先队列式)
步骤
针对所给问题,定义问题的解空间。
问题的解空间应至少包含问题的一个(最优)解。
问题的解空间应至少包含问题的一个(最优)解。
确定易于搜索的解空间结构。
通常将解空间表示为树、图;
解空间树的第i层到第i+1层边上的标号给出了变量的值;
从树根到叶子的任一路径表示解空间的一个元素。
通常将解空间表示为树、图;
解空间树的第i层到第i+1层边上的标号给出了变量的值;
从树根到叶子的任一路径表示解空间的一个元素。
以广度优先或最小耗费优先的方式搜索整个解空间。
每个活节点只有一次机会成为扩展节点,
活节点一旦成为扩展节点,
其余儿子节点被加入活节点表中。(以此方式递归搜索)
每个活节点只有一次机会成为扩展节点,
活节点一旦成为扩展节点,
其余儿子节点被加入活节点表中。(以此方式递归搜索)
界限函数
分支界限法的核心。
尽可能多、尽可能早地“杀掉”不可能产生最优解的活节点。
好的界限函数可以大大减少问题的搜索空间,大大提高算法的效率。
好的界限函数可以大大减少问题的搜索空间,大大提高算法的效率。
分类
队列式(FIFO)分支界限法
先进先出
优先队列式分支界限法
组织一个优先队列,按优先级选取。
通常用最大堆来实现最大优先队列,最小堆来实现最小优先队列。
通常用最大堆来实现最大优先队列,最小堆来实现最小优先队列。
概率算法
关键词
随机性选择
小概率错误
运行时间大幅减少
不同解
对同一问题求解两次,可能得到完全不同的解,
且所需时间、结果可能会有相当大的差别。
且所需时间、结果可能会有相当大的差别。
特征
输入(包括两部分)
原问题的输入。
供算法进行随机选择的随机数序列。
运行
包括一处或多处随机选择,根据随机值来决定算法的运行。
不同的运行过程中,对于相同的输入实例可以有不同的结果,执行时间也可能不同。
结果
结果不能保证一定是正确的,但可以限制出错率。
分类
数值概率算法
近似解。
近似解的精度随计算时间的增加不断提高。
常用于数值问题的求解。
近似解的精度随计算时间的增加不断提高。
常用于数值问题的求解。
蒙特卡罗(Monte Carlo)算法
精确解。
解未必是正确的,正确的概率依赖于算法所用的时间。
一般情况下,无法有效地判定所得到的解是否肯定正确。
解未必是正确的,正确的概率依赖于算法所用的时间。
一般情况下,无法有效地判定所得到的解是否肯定正确。
拉斯维加斯(LasVegas)算法
一旦找到解,一定是正确解。
找到的概率随计算时间的增加而提高。
对实例求解足够多次,使求解失效的概率任意小。
找到的概率随计算时间的增加而提高。
对实例求解足够多次,使求解失效的概率任意小。
舍伍德(Sherwood)算法
总能求得问题的一个解,且所求得的解总是正确的。
设法消除最坏情形与特定实例之间的关联性。
多用于最快情况下的计算复杂度与其在平均情况下的计算复杂度差别较大。
设法消除最坏情形与特定实例之间的关联性。
多用于最快情况下的计算复杂度与其在平均情况下的计算复杂度差别较大。
近似算法
关键词
近似解
解的容错界限
近似最优解与最优解之间相差的程度
不存在多项式时间算法
特征
放弃求最优解
用近似最优解替代最优解
使算法简化
时间复杂度降低
衡量标准
算法的时间复杂度
时间复杂度必须是多项式阶的。
解的近似程度
与近似算法本身、问题规模、不同的输入实例有关。
运用
NP问题
定点覆盖问题
TSP
子集和数问题
算法应用
排序算法
内部排序
O(n^2)
冒泡排序
插入排序
选择排序
希尔排序
O(nlogn)
归并排序
快速排序
堆排序
O(n)
计数排序
基数排序
桶排序
外部排序
多路归并排序
查找算法
线性表查找
散列表查找
树结构查找
搜索算法
深度优先搜索算法
广度优先搜索算法
启发式搜索
字符串匹配算法
朴素
KMP
Robin-Karp
Boyer-Moore
AC自动机
Trie
后缀数组
工作中的算法
LRU算法
负载均衡算法
一致性哈希算法
Snowflake算法
蓄水池抽样算法
大数据中的算法
TopK算法
加/解密算法
压缩算法
AI算法
知识关联
数论
计算几何
概率分析
并查集
拓扑网络
矩阵运算
线性规划
算法题
leetcode
题库
基本
数组
字符串
矩阵
模拟
枚举
字符串匹配
排序
一般排序
桶排序
基数排序
计数排序
算法
动态规划
贪心
回溯
递归
分治
归并排序
快速选择
二分查找
搜索
深度优先搜索
广度优先搜索
记忆化搜索
数据结构
基础数据结构
哈希表
树
二叉树
二叉搜索树
最小生成树
栈
单调栈
图
链表
双向链表
单向链表
141. 环形链表
206. 反转链表
集合
有序集合
队列
堆(优先队列)
单调队列
拓扑
拓扑排序
最短路
欧拉回路
向量
双连通分量
最强联通分量
高级数据结构
并查集
字典树
线段树
树状数组
后缀树组
数学
数学
几何
博弈
数论
组合数学
随机化
概率与统计
水塘抽样
拒绝采样
其他
数据库
设计
数据流
交互
脑筋急转弯
迭代器
多线程
Shell
问题类型
子序列问题
子序列(不连续)
子序列(连续)
编辑距离
回文
647.回文子串
5.最长回文子串
解题技巧
双指针
位运算
前缀和
计数
滑动窗口
时间复杂度:O(n)
状态压缩
哈希函数
滚动哈希
扫描线
子主题
参考资料
书籍
《算法 第4版》
URL
算法动态演示
Algorithm Visualizer
visualgo
Usfca大学 数据结构可视化
设计模式
面向对象七大设计原则
单一职责原则(SRP)
开放封闭原则(OCP)
对扩展开放,对修改关闭。
里式替换原则(LSP)
只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,
基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
接口隔离原则(ISP)
使用多个隔离的接口来降低耦合度。
依赖倒置原则(DIP)
这个是开闭原则的基础,对接口编程,
依赖于抽象而不依赖于具体。
依赖于抽象而不依赖于具体。
迪米特法则(最少知识原则)(LKP)
一个实体应当尽量少的与其他实体之间发生相互作用,
使得系统功能模块相对独立。
使得系统功能模块相对独立。
合成复用原则(CRP)
原则是尽量使用合成/聚合的方式,而不是使用继承。
继承实际上破坏了类的封装性,超类的方法可能会被子类修改。
继承实际上破坏了类的封装性,超类的方法可能会被子类修改。
23种设计模式
创造型模式
单例模式
Singleton
Singleton
某个类只能有一个实例,提供一个全局的访问点。
特点
单例类只能有一个实例
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
实现方式
懒汉式
特征
懒加载模式
在系统初始化的时候不创建对象实例。
线程不安全
代码
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
缺点
当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。
线程安全
代码
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
将整个 getInstance() 方法设为同步(synchronized)。
缺点
synchronized重量级锁,导致效率不高。
说明
是对懒汉式线程不安全方式的优化,做到了线程安全。
双重检验锁
特征
是一种使用同步块加锁的方法。
两次检查 instance == null,一次是在同步块外,一次是在同步块内。
代码
public static Singleton getSingleton() {
if (instance == null) { //Single Checked
synchronized (Singleton.class) {
if (instance == null) { //Double Checked
instance = new Singleton();
}
}
}
return instance ;
}
缺点
instance = new Singleton()这句,这并非是一个原子操作。
原因
JVM 中这句话大概做了下面 3 件事情
1.给 instance 分配内存
2.调用 Singleton 的构造函数来初始化成员变量
3.将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)。
JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
优化
将 instance 变量声明成 volatile
代码
public class Singleton {
private volatile static Singleton instance; //声明成 volatile
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
说明
是对懒汉式线程安全方式的优化,效率有所提升。
饿汉式
特征
在系统初始化的时候就创建对象实例。
代码
public class Singleton{
//类加载时就初始化
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
缺点
缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。
静态内部类
特征
《Effective Java》上所推荐的。
这种写法仍然使用JVM本身机制保证了线程安全问题。由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,**第一次调用getInstance()时将加载内部类SingletonHolder **,在该内部类中定义了一个static类型的变量INSTANCE ,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。
由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
代码
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举
特征
简洁
通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。
代码
public enum EasySingleton{
INSTANCE;
}
参考资料
单例模式
工厂方法模式
Factory Method
Factory Method
定义一个创建对象的接口,让子类决定实例化那个类。
抽象工厂模式
Abstract Factory
Abstract Factory
创建相关或依赖对象的家族,而无需明确指定具体类。
建造者模式
Builder
Builder
封装一个复杂对象的构建过程,并可以按步骤构造。
原型模式
Prototype
Prototype
通过复制现有的实例来创建新的实例。
结构型模式
适配器模式
将一个类的方法接口转换成客户希望的另外一个接口。
享元模式
通过共享技术来有效的支持大量细粒度的对象。
桥接模式
将抽象部分和它的实现部分分离,使它们都可以独立的变化。
组合模式
将对象组合成树形结构以表示“”部分-整体“”的层次结构。
装饰器模式
动态的给对象添加新的功能。
代理模式
为其他对象提供一个代理以便控制这个对象的访问。
外观模式
对外提供一个统一的方法,来访问子系统中的一群接口。
行为型模式
责任链模式
将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
访问者模式
在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
策略模式
定义一系列算法,把他们封装起来,并且使它们可以相互替换。
观察者模式
对象间的一对多的依赖关系。
状态模式
模板模式
定义一个算法结构,而将一些步骤延迟到子类实现。
备忘录模式
迭代器模式
解释器模式
给定一个语言,定义它的文法的一种表示,并定义一个解释器。
命令模式
中介者模式
参考资料
Java23种设计模式
操作系统
CPU
参考资料
运行级别和保护机制
知名操作系统
linux内核
windows
macOS
HarmonyOS
源码
github
gitee
参考资料
鸿蒙操作系统介绍(非常详细)
不吹不擂,一文揭秘鸿蒙操作系统
鸿蒙操作系统深度报告:第三代操作系统的大时代正在来临
EulerOS
参考资料
官网
EulerOS wiki
官方科普:一图读懂欧拉开源操作系统
欧拉操作系统 百度百科
参考资料
操作系统发展关系
操作系统wiki
编译原理
计算机网络
网络模型
网络分层图示
网络分层
2
TCP/IP通信
示意图
1
HTTP通信示意图
1
TCP与UDP
TCP
1.面向连接。
见面3次握手,断开4次挥手。
见面3次握手,断开4次挥手。
3次握手
3次握手
1.C端发起请求
2.S端接收C端请求,
回复确认收到C端请求。
回复确认收到C端请求。
3.C端接收S端确认消息,
建立C端TCP通道,
并发送Cd端确认通道建立信息。
建立C端TCP通道,
并发送Cd端确认通道建立信息。
4.S端收到C端确认建立TCP通道信息,
建立S端对应TCP通道。
建立S端对应TCP通道。
4次挥手
1.C端发起断开请求。
2.S端接收请求,并回复确认信息。
3.S端继续发送处理信息。
4.S端发送断开请求。
5.C端回复确认信息。
S端通道关闭,C端等待2ms后通道关闭。
S端通道关闭,C端等待2ms后通道关闭。
问题
为什么建立TCP连接要3次握手,而不是2次?
为什么断开需要挥手4次,而不是3次?
2.仅支持1对1通信。
3.面向字节流。
4.可靠传输
序列号
校验和
确认应答信号
重发机制
窗口控制
拥塞控制
TCP中拆包粘包现象
UDP
1.无连接协议,无需握手
2.除了1对1单通通信,还可以1对多广播。
3.面向报文
TCP与UDP对比
对比
HTTP3
QUIC协议
参考资料
HTTP 3.0彻底放弃TCP,TCP到底做错了什么?
网络IO
计算机世界里的速度分级
内存
纳秒级
CPU
百纳秒级
网卡
微秒级
磁盘
毫秒级
IO阶段
两个阶段
等待数据阶段
外部数据通过协议栈传输到网卡,
再通过DMA copy到内核buf.
再通过DMA copy到内核buf.
拷贝数据阶段
内核将buf数据拷贝到用户进程buf。
IO模型
同步IO
(bloking IO)BIO
(bloking IO)BIO
一个线程只能同时处理一个socket通道。
等待数据阶段和拷贝数据阶段都会阻塞线程
等待数据阶段和拷贝数据阶段都会阻塞线程
缺点
每个新请求需要新开线程,会阻塞,耗资源且效率不高。
非阻塞IO
(non-blocking IO)NIO
(non-blocking IO)NIO
图示
一个线程能同时处理多个socket通道。
进程不会阻塞,等待数据阶段若返回EWOULDBLOCK,
会定时再发起recvfrom请求,期间可以做其他事情。
进程不会阻塞,等待数据阶段若返回EWOULDBLOCK,
会定时再发起recvfrom请求,期间可以做其他事情。
缺点
当有大量客户端链接时,每次判断fd状态都需要从用户态切换到内核态,
即10k个连接需要循环10k次系统调用,不能满足大量请求的场景。
即10k个连接需要循环10k次系统调用,不能满足大量请求的场景。
参考资料
C10K问题
聊聊C10K问题及解决方案
C10K问题原文翻译
多路复用IO
(multiplexing IO)
(multiplexing IO)
本质
多路监听+阻塞/非阻塞 IO
多路复用IO不仅配合非阻塞IO使用,
很多时候也配合单进程单线程使用协程的方式,
避免的线程进程的上下文切换带来的性能折损。
很多时候也配合单进程单线程使用协程的方式,
避免的线程进程的上下文切换带来的性能折损。
类型
select O(n)
图示
在NIO基础上,改进10k次系统调用为1次系统调用。
缺点
每次调用都要传10k个fd
实际上是由用户循环10k次改为了内核循环10k次,
问题复杂度依旧是O(n)
问题复杂度依旧是O(n)
在32位机器上,源码中限制了最多接受的fd为1024个,
这是性能瓶颈。
这是性能瓶颈。
poll O(n)
在select基础上,改进了最多1024个fd的限制。
缺点
除了“1024”缺陷外,其他select模式的缺点都还在。
epoll O(1)
图示
在poll的基础上:
1.在内核开辟缓存区,将监听的fd注册到缓存区,记录fd监听的事件类型。
2.当fd监听事件触发,将fd加入就绪列表。
3.内核轮询就绪队列,回调通知用户进程。
1.在内核开辟缓存区,将监听的fd注册到缓存区,记录fd监听的事件类型。
2.当fd监听事件触发,将fd加入就绪列表。
3.内核轮询就绪队列,回调通知用户进程。
工作模式
LT(level trigger)
当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。
下次调用 epoll_wait 时,会再次响应应用程序并通知此事件。
下次调用 epoll_wait 时,会再次响应应用程序并通知此事件。
同时支持block和no-block socket
epoll默认模式
ET(edge trigger)
当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。
如果不处理,下次调用 epoll_wait 时,不会再次响应应用程序并通知此事件。
如果不处理,下次调用 epoll_wait 时,不会再次响应应用程序并通知此事件。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。
epoll工作在ET模式的时候,必须使用非阻塞套接口,
以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
只支持no-blocksocket
优点
10k个fd不用再循环10k次,文件描述符数量不再受限。
使用零拷贝技术,节省了用户态和内核态间数据拷贝的资源消耗。
通过每个fd定义的回调函数来实现的,只有就绪的fd才会执行回调函数。
I/O的效率不会随着监视fd的数量的增长而下降。
I/O的效率不会随着监视fd的数量的增长而下降。
缺点
每次读和写都是用户线程从内核缓存读写。
应用
Nginx、Redis、Tornado、netty等
对比
三种模式处理效率对比
当并发fd较小时,Select、Poll、Epoll的响应效率想差无几,
甚至Select和Poll更胜一筹。但是当并发连接(fd)较多时,
Epoll的优势便真正展现出来。
甚至Select和Poll更胜一筹。但是当并发连接(fd)较多时,
Epoll的优势便真正展现出来。
总结
综合属性总结
参考资料
高性能IO模型分析-浅析Select、Poll、Epoll机制
异步IO
(asynchronous IO)AIO
(asynchronous IO)AIO
在epoll基础上,改进由用户调内核读写为内核调用户读写。
缺点
目前还没有成熟商用的异步IO。
信号驱动IO
(signal-driven IO)
(signal-driven IO)
内核在fd就绪时发送信号通知用户进程。
和事件驱动类似。
和事件驱动类似。
异步阻塞IO
不存在
参考资料
从操作系统层面理解网络IO模型
简明网络I/O与并发
彻底理解IO多路复用实现机制
多路复用wiki
零拷贝
参考资料
网络分层和协议
网络分层模型和TCP/IP协议族
上篇
下篇
计算机网络原理
架构
DDD(领域驱动设计)
序言
互联网架构演进
演进路线
补充
核心思想
业务逻辑不应该依赖于技术框架
基础篇
通用概念
领域
子域
核心域
通用域
支撑域
限界上下文
战术设计
实体(Entity)
值对象(ValueObject)
关联
聚合(Aggregate)
聚合根(AggregateRoot)
进阶篇
领域事件
分层架构
领域服务
重要原则
每层只能位于其下方的层发生耦合
分层情况
接口层
应用层
领域层
基础层
微服务架构模型
整洁架构
洋葱架构
用户界面
应用服务
领域服务
领域模型
六边形架构
子主题
实战篇
加餐篇
总览
聚合中的对象
聚合根
实体
值对象
领域服务
注意事项
领域事件
领域事件基类 DomainEvent
领域事件实体
领域事件的执行逻辑
领域事件数据持久化
仓储模式
DO 与 PO 对象的转换
仓储模式
仓储接口
仓储实现
仓储执行逻辑
工厂模式
服务的组合与编排
注意事项
微服务聚合拆分时的代码演进
微服务拆分前
微服务拆分后
服务接口的提供
facade 接口
DTO 数据组装
总结
聚合与聚合的解耦
微服务内各层的解耦
图示
图示
分布式系统
分布式算法
拜占庭将军问题
关键词
对比
共识(Consensus)
一致性(Consistency)
解决共识问题,达成共识行动一致则成功,不一致则失败。指令本身的真假先不考虑。
目的是在已知有成员不可靠的前提下,所有将军仍能达成一致共识。
本质问题
一致性
节点恶意行为影响共识的成本很低。
正确性
信息不加密,容易被破解,冒充和伪造。
例子
两忠一叛
解决方案
1.口信消息拜占庭将军问题之解
兰伯特论文
如果叛将人数为m,那么将军总数不能少于3m+1,方能解决共识问题。
可理解为增加节点数
缺点
叛将数不确定,无法不停增加节点数。
2.签名消息拜占庭将军问题之解
签名加密
内容无法篡改
将军不能伪造
算法
拜占庭容错算法
(byzantine Fault Tolerance, BFT)
(byzantine Fault Tolerance, BFT)
存在故障行为,并且还可能有恶意行为
PBFT算法
PoW(Proof of Work)工作量证明算法
非拜占庭容错算法
故障容错算法
(Crash Fault Tolerance, CFT)
(Crash Fault Tolerance, CFT)
只有故障行为,没有恶意行为,即只有消息丢失或者重复,而不存在错误消息。
Paxos算法
Raft算法
ZAB协议
拜占庭将军问题与比特币
比特币解决拜占庭将军问题
非对称加密
消息传送的私密性
能够确认身份
签名不可伪造、篡改
扩展
拜占庭将军问题也是密码学领域的核心问题
CAP理论
分布式系统基础理论
图示
子主题
布鲁尔定理(Brewer’s theorem)
CAP三个基础指标
一致性(Consistency)
在分布式系统中的所有数据备份,在同一时刻是否同样的值。
可用性(Availability)
在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。
分区容错性(Partition tolerance)
在分区数据同步失败的情况下,仍然能够提供服务。
CAP不可能三角
即不可能存在CAP型分布式系统,因为P是分布式系统的必要条件,所以分布式系统的可能型只会是AP或者CP。
模型实例
示意图
子主题
CA模型
不同理解
1
观点一:理解成一个的操作范围,即CA是在没有网络分区的情况下的操作
2
观点二:牺牲一部分功能,比如写操作,以保证强一致性,但是似乎A受到了一定程度的影响
3
观点三:单机系统
典型案例
kafka
可配置满足CP或者AP
nacos
可配置满足CP或者AP
单站点数据库
LDAP协议
xFS文件系统
CP模型
强调数据一致性
为了防止数据不一致,集群会拒绝新数据的写入。
实现方式
悲观锁
少数分区不可用
典型案例
ZooKeeper
Etcd
HBase
分布式数据库
大部分的协议
分布式锁
AP模型
强调系统的可用性
分区故障时,相同的读操作,可能会结果不一样。
实现方式
到期或者租赁
解决冲突
乐观锁
典型案例
Cassandra
DynamoDB
Eureka
Redis
rocketmq
Web缓存
DNS
NoSQL
酸碱理论
ACID理论(AC)
CAP理论的酸(CP)
强调一致性
ACID是衡量事务的四个特性
ACID说明
原子性(Atomicity,或称不可分割性)
一个事务的所有操作,要么全部完成,要么全部不完成。
一致性(Consistency)
强一致性
是最强的一致性模型,要求任何读取操作都能读取到最新的值,换句话说,要求任何写入操作立即同步给所有进程。
弱一致性
这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不久承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态
特例
最终一致性
最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。
隔离性(Isolation)
事务隔离级别
数据库能够防止由于多个并发事务交叉执行而导致数据的不一致。
持久性(Durability)
指事务结束后,对数据的修改是永久的,不会回滚到之前的状态。
可以理解为,操作实现了符合ACID特性,那么这个操作就实现了事务。
分布式事务协议
2PC(Two Phase Commit)二阶段提交协议
原理
提交请求阶段(投票阶段)
由发起节点统计其他节点的执行意愿(假设都OK),则进入后面执行阶段。
提交执行阶段(完成阶段)
发起节点收集各个节点的执行结果后,统一返回。
要点
二阶段提交要求每个阶段必须保证实现自己的承诺,即如果请求阶段阶段承诺成功,那么在执行的时候就不能失败,这样保证了整体的一致性。
应用
目前常用的是XA协议(也叫做X/Open Distributed Transaction Processing即DTP)
XA协议是X/Open国际联盟基于二阶段提交协议提出
MySQL数据库
缺点
在提交请求阶段需要预留资源,即锁定资源,期间其他人不能操作。
数据库是独立的系统,实际上的分布式系统,业务复杂性会远高于数据库系统。
3PC
TCC(Try Confirm Cancel)
原理
Try
预留
这个阶段会收集各个节点返回的请求结果,若都OK,则进入下一步
Confirm
确认
Cancel
撤销
BASE理论(AP)
CAP理论的碱(AP)
强调可用性
核心
基本可用(Basically Available)
定义
基本可用指的是分布式系统出现了不可预知故障的时候,允许损失部分可用性。
响应时间合理延长,功能上适当做服务降级。
响应时间合理延长,功能上适当做服务降级。
实现基本可用
软状态(Soft state)
软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。
最终一致性(Eventually consistent)
最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
算法
Paxos算法
zookeeper选主算法
Raft算法
基础概念
角色
领导者(Leader)
一般一个集群只有一个
追随者(Follower)
普通选民
候选人(Candidate)
可以竞选领导人
term(任期)
哪个节点做leader是大家投票选举出来的,每个leader工作一段时间,
然后选出新的leader继续负责。这根民主社会的选举很像,
每一届新的履职期称之为一届任期,在raft协议中,也是这样的,对应的术语叫term。
然后选出新的leader继续负责。这根民主社会的选举很像,
每一届新的履职期称之为一届任期,在raft协议中,也是这样的,对应的术语叫term。
选举过程
特点
领导说了算
选举时先到先得
应用
Redis Sentinel领头选举
参考资料
Raft算法动态演示
Raft论文译文
raft算法
一致性hash算法
Gossip协议
应用
Redis 集群节点信息传播
Quorum NWR算法
PBFT算法
ZAB协议
zookeeper消息传播算法
对比
四个维度对比
参考资料
算法可视化网站
聚合架构
架构设计
架构理论
CAP
拜占庭将军问题
架构模式
分层模式
管道过滤器模式
CS模式(客户端服务器模式)
MVC模式
事件驱动模式
微服务架构
黑板模式
代理人模式
点对点模式
主从模式
解释器模式
架构设计三原则
合适原则,演化原则,简单原则
合适优于先进,演化优于一步到位,简单优于复杂
架构基本问题
规模
规模带来的复杂度的主要原因是“量变引起质变”
安全
从技术角度分两类
1.功能上的安全,即代码实现层面
2.网络架构上的安全
防火墙
云服务商承受强大带宽的能力
低成本
各实现环节考虑成本控制问题
可扩展
两个基本条件
正确预测变化,完美封装变化。
预测变化复杂性
不可能每个设计点考虑可扩展性
不可能完全不考虑可扩展性
所有预测都存在出错的可能性
方案
方案一
“变化”封装在变化层,不变的封装在“稳定层”
问题
系统要拆分变化层和稳定层
需要设计变化层和稳定层之间的接口
方案二
提炼抽象层和实现层
抽象层稳定
实现层根据具体业务定制开发
实例有装饰者模式,策略模式等等
3H问题
高可用
高可用架构
独裁式
一主多备
主节点宕机,其中一个备份节点升级为主节点
协商式
一主多从
主节点宕机,其中一个从节点升级为主节点
从节点负责读
与主备架构的区别
mysql
民主式
集群分区
ZooKeeper
高可用分类
应用层
无状态设计。负载均衡,解决分布式Session问题
秒杀系统
服务层
本质是通过冗余来实现高可用。
手段
负载均衡
业务分级
超时重试
异步调用
缓存
缓存预热
冗余
无状态
故障转移
多地多活
过载保护
限流
算法
令牌桶
漏桶
降级
拒绝服务
关闭服务
熔断
幂等
压测
线上压测
线下压测
jmeter等
灰度发布
监控报警
数据层
冗余备份
如何应对副本同步延迟和中断导致的数据一致性问题
数据备份
冷备份
热备份
异步热备份
同步热备份
失效转移
高性能
前端优化
浏览器访问优化
使用浏览器缓存
减少请求次数
启用压缩
减少Cookie传输
前端业务处理代码优化
CDN加速
JS异步
反向代理
应用层优化
使用缓存
集群
异步
代码优化
合理的架构
多线程
资源复用
良好的数据结构
JVM调优
存储优化
缓存
高性能存储盘
分布式存储
NoSQL
光纤传输
减少数据库IO操作
读写分离
分库分表
高并发
高并发指标
响应时间
吞吐量
QPS
并发用户数
高并发手段
加机器
增加单机性能
并发编程
并发和并行
并发(concurrency)
一个以上任务在一段时间内被执行。可以是同时执行,也可以是分时间片执行。
并行(parallellism)
一个以上的任务同时被执行。
区别
并发是逻辑概念,并行强调物理运行状态。并发包含并行的情况。
三要素
可见性
原子性
有序性
死锁的四个原因
互斥条件
请求和保持条件
不剥夺条件
循环等待
高并发需求
连接数量
请求数量
并发模型
相关
操作系统IO模型
阻塞
非阻塞
同步
非同步
进程模型
单进程
多进程
多线程
分类
操作系统层面
PPC(Process Per Connection)
每次有新连接,新建进程处理
UNIX
缺点
fork代价高
父子进程通信复杂
支持的并发数量有限
prefork
提前创建进程,有新连接直接使用
缺点
同PPC,相对有改进
TPC(Thread Per Connection)
每次有新连接,创建新线程处理。
缺点
高并发仍有性能问题。
无需进程间通信,但是线程间的互斥和共享引入了复杂度,容易死锁。
多线程互相影响的问题,线程出问题,可能导致整个进程退出。
prethread
预先创建线程,有新的连接直接处理。
缺点
同TPC,相对有改进。
参考资料
系统高性能架构
编程框架层面
并行worker
单线程回调和事件轮询模型
缺点
会造成大量回调函数的嵌套,代码可读性不佳。
因为没有多线程,在多核的机器上,也没办法实现并行执行。
因为没有多线程,在多核的机器上,也没办法实现并行执行。
案例
Nginx
多进程(单线程)+多路IO复用模型
原理
Nginx启动后,会有一个master进程和多个相互独立的worker进程。
在每个 worker 进程里,Nginx 调用内核 epoll()函数来实现 I/O 的多路复用。
缺点
存在惊群现象。连接进来时,所有子进程都争着处理。
Nginx在accept上加互斥锁应对惊群现象。
Nginx在accept上加互斥锁应对惊群现象。
Node.js
单线程模型
原理
Node.js中所有的逻辑都是事件的回调函数,
所以 Node.js始终在事件循环中,
程序入口就是事件循环第一个事件的回调函数。
所以 Node.js始终在事件循环中,
程序入口就是事件循环第一个事件的回调函数。
缺点
会造成大量回调函数的嵌套,代码可读性不佳。
因为没有多线程,在多核的机器上,也没办法实现并行执行。
因为没有多线程,在多核的机器上,也没办法实现并行执行。
流水线并行
协程模型
MPG(Go并发)
Actor
所有的线程(或进程)通过消息传递的方式进行合作,这些线程(或进程)称为Actor。
主角是Actor,类似一种worker,Actor彼此之间直接发送消息,
不需要经过什么中介,消息是异步发送和处理的
主角是Actor,类似一种worker,Actor彼此之间直接发送消息,
不需要经过什么中介,消息是异步发送和处理的
特点
1.所有Actor状态是Actor本地的,外部无法访问。
2.Actor必须只有通过消息传递进行通信。
3.一个Actor可以响应消息,推出新Actor,改变其内部状态。
或将消息发送到一个或多个其他参与者。
或将消息发送到一个或多个其他参与者。
4.Actor可能会堵塞自己,但Actor不应该堵塞它运行的线程。
优点
共享内存更适合单机多核的并发编程,而且共享带来的问题很多,编程也困难。
随着多核时代和分布式系统的到来,共享模型已经不太适合并发编程。
随着多核时代和分布式系统的到来,共享模型已经不太适合并发编程。
应用
MpaReduce
Proactor
proactor主要是通过对异步IO的封装的一种模型,
它需要底层操作系统的支持,
目前只有windows的IOCP支持的比较好。
它需要底层操作系统的支持,
目前只有windows的IOCP支持的比较好。
Reactor
Reactor典型模式
单Reactor单线程
单Reactor多线程
多Reactor多进程
多Reactor多线程
参考资料
Reactor 反应堆设计模式
高性能IO模型分析-Reactor模式和Proactor模式
CSP
(Communicating Sequential Processes)(Go并发)
(Communicating Sequential Processes)(Go并发)
Go语言的CSP模型是由协程Goroutine与通道Channel实现
特点
Worker之间通信区别于Actor模式,是通过channel进行消息发布和侦听。
channel使worker之间实现松耦合。
即发送者不知道自己的消息被哪个消费者接收,
消费者也不知道自己接收的哪个发送者的消息。
即发送者不知道自己的消息被哪个消费者接收,
消费者也不知道自己接收的哪个发送者的消息。
优点
Channel不需要缓冲消息,Actor需要。
应用
Go语言
函数性并行
Lambda架构
JDK 1.7 中的 ForkAndJoinPool
参考资料
常见并发模型相关知识
架构实践
关键技术
秒杀系统
秒杀系统业务逻辑
秒杀的问题
大量用户同时刷新界面,会对服务器的带宽造成非常大的压力;
用户在秒杀前后可以多次重复点击按钮,造成很多不必要的请求;
用户可以通过脚本进行抢购,并且抢购成功率非常高;
服务端承受高并发请求,会出现响应过慢或失败等情况;
数据库承受高并发请求,会导致连接池耗尽和响应缓慢;
如果数据库更新设计的不合理,可能会出现超卖的情况;
秒杀系统的问题
限流降级
超卖
架构演进
傻瓜式秒杀系统
架构图
1
关键点
大量用户同时刷新界面,会对服务器的带宽造成非常大的压力;
用户在秒杀前后可以多次重复点击按钮,造成很多不必要的请求;
用户可以通过脚本进行抢购,并且抢购成功率非常高;
服务端承受高并发请求,会出现响应过慢或失败等情况;
数据库承受高并发请求,会导致连接池耗尽和响应缓慢;
如果数据库更新设计的不合理,可能会出现超卖的情况;
秒杀界面CDN
架构图
1
关键点
预先把网页的静态资源存放在CDN节点,用户在刷新界面时直接从CDN获取静态资源,从而降低刷新秒杀界面对服务器造成的压力。
秒杀按钮优化
架构图
1
关键点
用户在秒杀开始前点击按钮,造成很多无用请求;
用户在秒杀开始后多次点击按钮,造成很多重复请求;
秒杀链接优化
架构图
1
关键点
URL动态化
秒杀验证码
架构图
1
关键点
使用验证码不仅可以增加脚本秒杀的难度,还可以降低请求的QPS
过滤请求
架构图
1
关键点
通过Nginx过滤,我们可以把100W的请求过滤为1000个请求,大大减少了服务器端的压力。
Redis缓存
架构图
1
关键点
Redis是不支持事务的,所以可能出现扣减为负数的情况,这种情况下我们可以使用Lua脚本来保证一次扣减操作的原子性,从而保证扣减结果的正确性。
异步更新数据库(完整版)
架构图
关键点
削峰填谷最好的实践就是MQ了。
参考资料
秒杀系统
Seckill
1
2
秒杀Redis逻辑
一文搞懂秒杀系统
分布式锁
特征
互斥性
互斥是锁的基本特征,同一时刻锁只能被一个线程持有,执行临界区操作。
超时释放
通过超时释放,可以避免死锁,防止不必要的线程等待和资源浪费,类似于 MySQL 的 InnoDB 引擎中的 innodblockwait_timeout 参数配置。
可重入性
一个线程在持有锁的情况可以对其再次请求加锁,防止锁在线程执行完临界区操作之前释放。
高性能高可用
加锁和释放锁的过程性能开销要尽可能的低,同时也要保证高可用,防止分布式锁意外失效。
实现方式
Redis分布式锁
核心要素
加锁
解锁
锁超时
两大类方式
单机
使用 SETNX 指令
存在一个严重的问题
由于 SETNX 和 EXPIRE 这两个操作是非原子性的, 如果进程在执行 SETNX 和 EXPIRE 之间发生异常,SETNX 执行成功,但 EXPIRE 没有执行,导致这把锁变得“长生不老”,这种情况就可能出现前文提到的锁超时问题,其他进程无法正常获取锁。
使用 SET 扩展指令
仍然不能彻底解决分布式锁超时问题
使用 Redisson 的分布式锁
多机
基于 Redis 多机实现的分布式锁 Redlock
Memcached 分布式锁
利用 Memcached 的 add 命令。
Zookeeper 分布式锁
利用 Zookeeper 的顺序临时节点,来实现分布式锁和等待队列。
Chubby
Google 公司实现的粗粒度分布式锁服务,有点类似于 ZooKeeper。
Chubby 通过 sequencer 机制解决了请求延迟造成的锁失效的问题。
数据库分布式锁
性能缺点等比较多,不推荐。
参考资料
浅析 Redis 分布式锁解决方案
1
分布式锁的3种实现(数据库、缓存、Zookeeper)
1
Zookeeper 分布式锁实现原理
1
分布式Session存储
参考资料
分布式理论与分布式架构设计理论
安全
权限管理
权限管理框架
权限设计原理
后台权限系统的设计以及主流的五种权限模型详解
角色权限设计:以阿里产品为例,展开体验分析
OIDC & OAuth2.0 协议及其授权模式详解
参考资料
漏洞扫描
开源项目
QingScan
文档
常见安全问题
https
XSS
CSRF
哈希洪水攻击
DDos攻击
拒绝服务
审计安全
拖库、洗库、撞库
参考资料
网络安全分类
测试
运维
docker
k8s
K0S
K3S
rancher
参考资料
再见命令行!K8S傻瓜式安装,图形化管理真香!
开源
项目
权限
sa-token
地址
github
电商
mall
作者
macrozheng
地址
mall学习教程
newbee-ltd
心脏跳动旗下newbee-mall系列产品
github
Lilishop开源商城系统
Lilishop开源商城系统
gitee
github
Mall4j商城系统
广州市蓝海创新科技有限公司
gitee
电商架构与设计
一文读懂电商产品架构
从运营、产品和技术,多角度思考电商的营销体系建设
阿里-京东-电商-营销平台目标架构-功能架构-业务架构
方法论是锦上添花的东西,不是破局的利器,不必迷信。
BPM
(Business Process Management)
业务流程管理
JEECG
地址
JEECG
HRM
微人事
github
支付
spring-boot-pay
gitee
秒杀
spring-boot-seckill
gitee
风控
规则引擎
Drools
源码
github
参考资料
Drools规则引擎应用看这一篇就够了
参考资料
互联网风控系统架构分析
风控系统资料合集-蚂蚁,京东,美团,开源系统
推荐
参考资料
分享几个github上开源的推荐系统项目
推荐系统领域开源项目/工具介绍
入坑推荐系统,拿这个开源项目练手
财务
SaaS
多租户架构
BI
参考资料
BI系统常见的架构解析及分享
数据分析-BI基础概念介绍
规则引擎
drools
别再说你不懂规则引擎了
从0到1:构建强大且易用的规则引擎
规则引擎基本原理及应用架构简介
流程引擎
开源流程引擎哪个好,如何选型
流程引擎介绍
监控体系
ELK日志监控
一篇文章全面了解监控知识体系
参考资料
分享13个Spring Boot 优质开源项目!商城,ERP,管理系统
知识
Java 全栈知识体系
作者
pdai
地址
Java 全栈知识体系
JavaGuide
地址
JavaGuide(Java面试+学习指南)
开源协议
各种开源协议介绍
新技术
区块链
参考资料
区块链分层结构
1
分层结构
图示
1
layer4
layer3
layer2
layer0
web3.0
参考资料
《一本书读懂Web3.0:区块链、NFT、元宇宙和DAO》
参考链接
Web3.0到底是什么?一文简单看懂
Web3.0发展现状及合规路径
什么是 Web3.0?
Web3.0:开放、隐私和共建三大标签颠覆互联网
一图看懂web3.0
产业互联网
参考资料
中国互联网发展报告
产业互联网
概念
wiki
百度百科
云
开源云
OpenStack
云原生
参考资料
云原生的设计哲学
边缘计算
公开课
1.边缘计算深度调研
2.云走向边缘,云将无处不在
3.边缘计算的发展现状与趋势展望
4.边缘计算在推荐系统中的应用
5.阿里云边缘云原生应用实践
6.KubeEdge
7.用SuperEdge统管边缘设备和机器
8.如何使用k8s管理中国高速公路上的10万边缘节点
9.边缘协同,打通AI最后一公里
10.用EdgeAdm一件安装k8s集群和原生k8s集群
11.基于KubeEdge实现中国移动10086客服云边系统平台
12.Volcano架构设计与原理介绍
13.一文读懂SuperEdge的云边隧道
14.打破内网壁垒,从云端一次添加成百上千的边缘节点
16.2020十大边缘计算开源项目带您深入边缘方向
人工智能AI
(Artificial Intelligence)
(Artificial Intelligence)
大纲
人工智能、机器学习、深度学习的关系
机器学习
模型评估
交叉验证
ROC曲线
AUC值
模型调优
超参数优化
正则化
集成方法
监督学习
分类
回归
序列预测
无监督学习
聚类
降维
关联规则
半监督学习
强化学习
集成学习
特征工程
数据清洗
缺失值处理
异常值处理
重复值处理
数据类型转换
特征选择
过滤式特征选择
包裹式特征选择
嵌入式特征选择
特征提取
文本特征提取
图像特征提取
声音特征提取
时间序列特征提取
特征降维
主成分分析
线性判别分析
因子分析
独立成分分析
深度学习
深度学习基础
深度学习框架
TensorFlow
PyTorch
Keras
深度学习工具
JupyterNotebook
Anaconda
GoogleColab
大语言模型
参考资料
大语言模型调研汇总
产品
数字人
机器人自主移动
SLAM
物联网IoT
(Internet of Things)
(Internet of Things)
管理
人
事
物
参考资料
《知行 技术人的管理之路》
0 条评论
下一页