Java 核心知识点-原理篇-完善版
2022-04-06 23:06:46 0 举报
AI智能生成
Java 核心知识点
作者其他创作
大纲/内容
一、JVM
1、JVM 运行机制
JVM
Java Virtual Machine
运行于操作系统纸上,不与硬件设备直接交互
运行 Java 字节码的虚拟机
包含
字节码指令集
程序寄存器
虚拟机栈
虚拟机堆
方法区
垃圾回收期
构成
结构图
类加载器子系统
Class Loader SubSystem
将编译好的.class文件加载到 JVM 中
运行时数据区
Runtime Data Area
用于存储在 JVM 运行过程中产生的数据
程序计数器
方法区
本地方法区
虚拟机栈
虚拟机堆
执行引擎
Execution Engine
类型
即时编译器
将 Java 字节码编译成具体的机器码
垃圾回收器
回收在运行过程中不在使用的对象
本地接口库
Native Interface Library
调用操作系统的本地方法库来完成具体的指令操作
运行过程
1、Java 源文件被编译器编译成字节码文件
2、JVM 将字节码文件编译成操作系统的机器码
3、机器码调用相应操作系统的本地方法库执行相应方法
2、多线程
多核操作系统中,JVM 允许一个进程内同时并发执行多个线程
JVM 后台运行的线程类型
虚拟机线程
JVM Thread
在 JVM 到达安全点(SafePoint)时出现
周期性任务线程
通过定时调度器线程来实现周期性操作的执行
GC 线程
支持 JVM 中不同的垃圾回收活动
编译器线程
在运行时将字节码动态编译成本地平台机器码
JVM 跨平台的具体实现
信号分发线程
接收发送到 JVM 的信号并调用 JVM 方法
3、JVM 内存区域
划分
线程私有区域
生命周期同线程
线程共享区域
同虚拟机的生命周期
直接内存
(堆外内存)
(堆外内存)
非 JVM 运行时数据区的一部分
Java 进程可以通过堆外内存技术避免在 Java 堆和 Native 堆中来回复制带来的资源占用和性能损耗
高并发应用场景下广泛使用
并发编程时被频繁使用
应用场景:NIO 模块
基于 Channel 与 Buffer 的 I/O操作方式
1、程序计数器
线程私有,无内存溢出问题
很小的内存空间,存储当前运行的线程所执行的字节码的行号指示器
每个线程都有一个独立的程序计数器
native 方法时,程序计数器为空(Undefined)
2、虚拟机栈
线程私有,描述 Java 方法的执行过程的内存模型
在当前栈帧(Stack Frame)中存储局部变量表、操作数栈、动态链接、方法出口等信息
栈帧
记录方法的执行过程
方法的执行和返回对应栈帧在虚拟机栈中的入栈和出栈
每个运行中的线程当前只有一个栈帧处于活动状态
3、本地方法区
线程私有
为 natie 方法服务
4、堆/运行时数据区
运行时数据区,线程共享
JVM 运行过程中创建的对象和产生的数据都存储在堆中
被线程所共享的内存区域 & 垃圾收集器进行垃圾回收的最主要的内存区域
从 GC 的角度分为
新生代
老年代
永久代
5、方法区/永久代
线程共享
包含
常量
静态变量
类信息
类版本
字段
方法
接口描述
即时编译机器码
运行时常量池
JVM 把 GC 分代收集扩展至方法区
使用 Java 堆来实现方法区
永久代内存回收主要针对常量池的回收和类的卸载
可回收对象很少
4、JVM 运行时内存/JVM 堆
基于GC 的角度划分
新生代
默认 1/3堆空间
Eden 区
8/10新生代空间
Servivor From 区
1/10新生代空间
Servivor To 区
1/10 新生代空间
老年代
默认 2/3堆空间
永久代
非常少的堆空间
1、新生代
JVM 新创建的对象(非大对象外)会存放在新生代
大对象
和具体的 JVM 版本、堆大小和垃圾回收策略有关
一般为 2K-128KB
-XX:PretenureSizeThreshold设置大小
JVM 频繁创建对象
频繁出发 MinorGC
新生代的 GC
采用复制算法
GC 过程
1、复制 Eden 区和 ServivorFrom 区中存活的对象至 ServivorTo 区
复制到老年代场景
1、对象年龄达到老年代标准
默认为 15
XX:MaxTenuringthreshol设置
2、ServivorTo 区内存空间不够
3、对象属于大对象
2KB-128KB 间
XX:PretenureSizeThreshold设置
2、清除 Eden 区和 ServivorFrom 区中对象
3、将 ServivorTo 和 ServivorFrom 区交换,新的 ServivorTo 区成为下一次 GC 时的 ServivorFrom 区
2、老年代
存放长生命周期对象及大对象
MajorGC
老年代的 GC
1、进行 MajorGC 前,JVM 会进行一次 MinorGC来释放空间
2、释放空间后依然出现
1、老年代空间不足
2、无法找到足够大的连续空间分配给新创建的大对象时
采用标记清除算法
1、扫描所有对象并标记存活的对象
2、回收未被标记的对象
3、释放内存空间
注意事项
1、MajorGC 耗时较长
2、MajorGC 标记清除算法容易产出内存碎片
3、老年代没有内存空间可分配时,会出现 OOM
3、永久代
内存的永久保存区域
Class信息
Meta 信息
注意事项
1、不同于新生代和老年代,GC不会在程序运行期间对永久代进行清理
永久代的内存会随着加载的Class文件的增加而增加
加载的 Class 文件过多是会出现 OOM
2、Java8中永久代已被元数据区取代
元数据区直接使用操作系统内存
元数据区不受 JVM 内存的限制
3、Java8 中
JVM 将类的源数据放入本地内存(Native Memory)中
将常量池和类的静态变量放入 Java 堆中
5、垃圾回收与算法
1、如何确定垃圾
垃圾确定
引用计数法
循环引用的问题
可达性分析
通过根搜索算法(GC Roots Tracing)实现
以一系列 GC Roots 的点作为起点向下搜索,当一个对象到任何 GC Roots 都没有引用链时,说明其已死亡
不可达对象需要经过至少两次标记才能判定是否可被回收
跟搜索算法
栈中的引用
方法区中的静态引用
JNI 中的引用
2、常用垃圾回收算法
标记清除算法(Mark-Sweep)
基础的垃圾回收算法
标记
标记所有需要回收的对象
清除
清除可回收的对象并释放所占用的内存空间
注意事项
效率低
内存碎片多
清理对象后并未整理可用的内存空间,如果回收的小对象居多的话,会引起内存碎片化问题,继而引起大对象无法获得连续可用空间问题
复制(Copying)
为了解决标记清除算法内存碎片化问题而设计的
过程
1、将内存一分为二,区域1及2
2、新生成的对象都存放在区域 1 中
3、区域 1 中对象存储满后会对区域 1 进行一次标记
4、将标记存活的对象全部复制到区域 2 中
5、清除整个区域 1 的内存
注意事项
效率高且容易实现
大量的内存浪费
大量长时间存活的对象会在区域 1 及区域 2 来回复制影响系统运行效率
标记整理(Mark-Compact)
结合了标记清除算法和复制算法的优点
过程
1、标记阶段同标记清除算法的标记阶段
2、标记完成后将存活对象移至内存另一端
3、清除该端的对象并释放内存
分代收集(Generational Collecting)
背景
上述三种算法都无法对所有类型的对象进行垃圾回收
针对不同的对象类型,JVM 采用的不同的垃圾回收算法
基于对象的不同类型将内存划分
新生代
存放新生成的对象
对象数量多生命周期亿
采用复制算法
老年代
存放大对象和生命周期长的对象
可回收对象数量较少
采用标记清除算法
6、4 中引用类型
1、Java 中一切皆对象
对象的操作需要通过该对象的引用来实现
强引用
把一个对象赋给一个引用变量时,该引用变量就是强引用
有强引用的对象一定为可达性状态
不会被垃圾回收
造成 MemoryLeak 内存泄露的主要原因
软引用
通过 SoftReference 类实现,
仅有软引用时,在系统内存空间不足时会被回收
弱引用
通过 WeakReference 类实现
如果一个对象只有弱引用,GC 过程中一定会被回收
虚引用
通过 PhantomReferencr 类实现
虚引用和引用队列联合使用
跟踪对象的垃圾回收状态
7、分代收集算法和分区收集算法
1、分代收集算法
1、新生代与复制算法
新生代存储短生命周期对象
GC 环节会标记大量已死亡的对象及少量存活对象
复制少量存活对象到另一端并清除原区域
2、老年代与标记整理算法
老年代主要存放长生命周期的对象及大对象,可回收对象一般较少
采用标记整理算法
2、分区收集算法
将堆空间分成连续大小不同的小区域,对每个小区域单独进行内存使用及垃圾回收
根据每个小区域内存的大小灵活使用和释放内存
8、垃圾收集器
新生代
1、Serial 垃圾收集器
单线程复制算法
在进行垃圾回收时,必须暂停其他所有工作线程,直到垃圾收集结束
Java 虚拟机在 Client 模式中新生代的默认垃圾收集器
2、ParNew 垃圾收集器
多线程复制算法
Serial 的多线程实现
Java 虚拟机在 Server 模式中新生代默认垃圾收集器
默认开启与 CPU 同等数量的线程进行垃圾回收
-XX:ParallelGCThreads
调节工作线程数
3、Parallel Scavenge 垃圾收集器
多线程复制算法
提升新生代垃圾收集效率
自适应调节策略来提高系统吞吐量
控制最大垃圾收集停顿时间
-XX:MaxGCPauseMillis
控制吞吐量大小
-XX:GCTimeRatio
控制自适应调节策略是否开启
UseAdaptiveSizePolicy
老年代
4、Serial Old 垃圾收集器
单线程标记整理算法
老年代实现
Java 虚拟机在 Client 模式下老年代的默认垃圾收集器
5、Parallel Old 垃圾收集器
多线程标记整理算法
特点
设计上优先考虑系统吞吐量
新生代 Parallel Scavenge
老年代 Parallel Old
其次考虑停顿时间等因素
6、CMS 垃圾收集器
多线程标记清除算法
老年代的垃圾收集器
目标
达到最短的垃圾回收停顿时间
步骤
1、初始标记
只标记和 GC Roots 直接关联的对象
速度快
需要暂停其他所有工作线程
2、并发标记
和用户线程一起工作
执行 GCRoots 跟踪标记过程
不需要暂停其他工作线程
3、重新标记
原因
并发标记环节,由于用户线程继续运行,导致垃圾回收中部分状态发送变化,为了确保这部分对象的状态的正确性
重新标记并暂停工作线程
4、并发清除
和用户线程一起工作
执行清除 GCRoots 不可达对象的任务
不需要暂停工作线程
7、G1 垃圾收集器
多线程标记整理算法
目标
有限的时间内获得最高的垃圾收集效率
方案
1、堆内存独立分区
堆内存划分为大小固定的几个独立区域
2、区域优先级列表
独立使用该区域内存资源
跟踪区域的垃圾收集进度
后台维护优先级列表
相比于 CMS的改进之处
1、基于标记整理算法,不产生内存碎片
2、可以精确控制停顿时间
不牺牲吞吐量的情况下来实现短停顿垃圾回收
9、Java 网络编程模型
1、阻塞 I/O模型
常见的 IO 模型,读写数据客户端会发送阻塞
流程
用户线程发出 IO 请求
内核检查数据是否就绪
用户线程一直阻塞等待结果
数据已就绪,内核复制数据到用户线程中
返回 IO 执行结果至用户线程
用户线程解除阻塞状态,开始处理数据
2、非阻塞 I/O模型
用户线程发起 一个IO 操作后,无需阻塞就可以得到内核返回的结果
3、多路复用I/O模型
多线程并发编程用的较多的模型
Java NIO
过程
一个selector线程不断轮询多个 Socket 的状态
只有在 Socket 有读写事件时,才通知用户线程执行 IO 操作
场景
连接数众多且消息体不大的情况
只做数据的接收和转发
将具体的业务操作转发给后面的业务线程处理
比非阻塞 IO 效率高原因
在系统内核进行Socket状态检查
4、信号驱动I/O模型
1、用户线程发起 IO 请求操作
2、系统为 Socket 注册一个信号函数
3、用户线程继续执行其他业务逻辑
4、在内核数据就绪时,系统发送信号到用户线程
5、用户线程接收信号,在信号函数中调用 IO 读且操作完成 IO 请求
5、异步I/O模型
过程
1、用户线程发起 asynchronous read操作到内核
2、内核立即返回一个状态
表明该请求是否成功发起
此时用户线程不会发生阻塞
3、内核等待数据准备完成并将数据复制到用户线程中
4、复制完毕后内核发送信号个用户线程,通知 asynchronous read 已完成
注意事项
两个阶段
请求发起
数据读取
6、Java I/O
java.io中
File
OutputStream
InputStream
Writer
Reader
Serializable
7、Java NIO
三个核心内容
Selector:选择器
可以监听多个 Channel 的事件
Channel:通道
双向
可读
可写
NIO 实现
FileChannel
File IO
DatagramChannel
UDP
SocketChannel
TCP IO
ServerSocketChannel
Socket CLient
Socket Server
Buffer:缓冲区
容器
连续的字节数据存储 IO 数据
区别
IO
面向流
数据只能在一个流中连续读写
数据没有缓冲
字节流无法前后移动
阻塞模式
NIO
面向缓冲区
将数据从一个 Channel 读取到一个 Buffer中,再从Buffer 写入到 Channel 中
可以方便的在缓冲区中进行数据前后移动等操作
数据的拆包、粘包等
非阻塞模式
10、JVM 类加载机制
1、JVM 类加载阶段
5 个阶段
加载
JVM 读取 Class 文件并根据 Class 文件描述来创建 java.lang.Class对象的过程
将 Class 文件读取到运行时区域的方法区内,在堆中来创建 java.lang.Class对象,并封装类在方法区的数据结构的过程
验证
确保 Class 文件符合当前虚拟机的要求
准备
在方法区中为类变量分配内存空间并设置类中变量的初始值
解析
JVM 将常量池中的符号引用替换为直接引用
初始化
不会初始化的场景
常量编译时,不会触发常量所在类的初始化
常量编译时会将常量值存入使用该常量类的常量池中
子类引用父类的静态字段时,不会触发子类的初始化,但是会触发父类的初始化
定义对象数组,不会触发该类的初始化
使用类名获取 Class 对象时不会触发该类的初始化
使用 Class.forName加载指定的类时,可以通过 initialize 参数设置是否需要初始化该类
使用 ClassLoader 默认的 loadClass 加载类不会触发类的初始化
2、类加载器
三种类加载器
启动类加载器
负责加载 JAVA_HOME/lib目录中的类库
-Xbootclasspath参数指定路径中被虚拟机认可的类库
扩展类加载器
负责加载 JAVA_HOME/lib/ext目录中的类库
通过 java.ext.dirs系统变量加载指定路径中的类库
应用程序类加载器
负责加载用户路径 classpath 上的类库
自定义类加载器
继承java.lang.ClassLoader
3、双亲委派机制
通过双亲委派机制来加载类
加载流程
1、将自定义类加载器挂载至应用程序类加载器
2、应用程序列加载器将类加载请求委托至扩展类加载器
3、扩展类加载器将类加载请求委托至启动类加载器
4、启动类加载器在加载路径下查找并加载 Class,如果没有找到 Class 文件,则交由扩展类加载器
5、扩展类加载器在加载路径下查找并加载Class文件,如果未找到目标 Class,则交由应用程序类加载器
6、应用程序类加载器在加载路径下查找并加载 Class 文件,如果未找到目标 Class 文件,则交由自定义类加载器加载
7、自定义加载器查找并加载用户指定目录下的Class文件,如果没有找到则跑出 ClassNotFound 异常
核心
保障类的唯一性和安全性
4、OSGI
Open Service Gateway Initiative
java 动态化模块化系统的一系列规范
为实现 java 程序的模块化变成提供基础条件
可以实现模块级的热插拔功能
二、Java 基础
1、集合
集合类定义在 Java.util中
1、List
特性
有序的 Collection
可重复
ArrayList
基于数组实现
增删慢
查找快
线程不安全
不需要定义时指定数组的长度,
长度不满足存储需求时
会创建一个新的更大的数据
将已有数据复制到新数组中
Vector
基于数组实现
增删慢
查找快
线程安全
同一时刻只允许一个线程对 Vector 机械能写操作
读写效率整体比 ArrayList 低
LinkList
基于双向链表实现
增删快
查找慢
线程不安全
提供了额外方法
操作链表头部和尾部
可以作为堆栈、队列或双向队列使用
2、Queue
ArrayBlockingQueue
基于数组数据结构实现的有界阻塞队列
LinkedBlockingQueue
基于链表数据结构实现的有界阻塞队列
PriorityBlockingQueue
支持优先级排序的无界阻塞队列
DelayQueue
支持延迟操作的无界阻塞队列
SynchronousQueue
用于线程同步的阻塞队列
LinkedTransferQueue
基于链表数据结构的无界阻塞队列
LinkedBlockingDeque
基于链表数据结构的双线阻塞队列
3、Set
特性
不可重复
需同时复写hashCode和 equals
HashSet
基于 HashTable 实现,
存放散列值
无序
TreeSet
基于二叉树实现
基于二叉树的原理对新添加对象按照指定顺序进行排序
基础数据类型采用默认排序
自定义数据类型需实现 Comparable 接口
LinkHashSet
基于HashTable实现的数据存储
双向链表记录顺序
4、Map
HashMap
基于数组+链表存储数据,
线程不安全
线程安全的有
Collections 中的 synchronizedMap
ConcurrentHashMap
key 和 value 允许 null
常用参数
capacity
当前数组的容量
默认为 16
loadFactor
负载因子
默认为 0.75
threshold
扩容的阈值
=capacity * loadFactor
java8 中进行了优化
数据结构修改为数组+链表/红黑树
链表超过 8 个后,hashmap 将数据结构转换为红黑树
提高查询料率
ConcurrentHashMap
分段锁实现,线程安全
过程
由多个 Segment 组成(segment 的数量即锁的并发度)
每个 segment 均继承自 ReentrantLock 并单独加锁
参数
concurrencyLevel
并行级别
默认为 16
HashTable
线程安全
同一时刻只能有一个线程写 HashTable
性能不如 ConcurrentHashMap
TreeMap
基于二叉树数据结构存储数据
实现了 SortedMap 接口
保障元素的书序存取
默认键值升序排序
场景
实现排序的映射列表
LinkedHashMap
基于 HashTable 数据结构,使用链表保存插入顺序
2、异常分类及处理
1、异常概念
方法不按照正常方式完成,可以通过异常来退出该方法
2、异常分类
Throwable 所有错误或异常的父类
Error
AWTError
ThreadError
原因
系统内部错误或资源耗尽
Exception
RuntimeException
运行时异常
Java 虚拟机正常运行期间跑出的异常
可以被捕获并处理
举例
NullPointException
ClassCastException
CheckedException
检查异常
编译阶段 Java 编译器会检查 CheckedException 异常并强制程序捕获和处理此类异常
需要在可能出现异常的地方通过 try catch 来来捕获并处理异常
举例
IOException
SQLException
ClassNotFoundException
3、异常处理方式
抛出异常或使用 try catch 捕获并处理异常
3、反射机制
1、动态语言概念
运行时可以改变其结构的语言
结构
属性增加或删除
方法增加或删除
动态语言
python
js
ruby
非动态语言
c
c++
半动态语言
java
2、反射机制概念
在程序运行过程中,对任意一个类都能获取其属性和方法,并且对任意一个对象都能调用其任意一个方法
反射机制
动态获取类和对象的信息及动态调用对象的方法的功能
3、反射应用
对象类型
编译时类型
在声明对象时所采用的类型
运行时类型
在为对象赋值时所采用的类型
4、Java 中反射 API
在运行过程中动态生成类、接口或对象等信息
API
Class
获取类的信息
属性
方法
Field
类的成员变量
获取和设置类中的属性值
Method
类的方法
获取方法的描述信息或执行某个方法
Constructor
类的构造方法
5、反射步骤
1、获取要操作的类的 Class 对象
三种
1、调用某个对象的 getClass()方法
eg
Person p = new Person();
Class clazz = p.getClass();
Class clazz = p.getClass();
2、调用某个类的 class 属性来获取对应的 Class 对象
eg
Class clazz = Person.class;
3、调用 Class 类中的 forName 静态方法
eg
Class clazz = Class.forName("fullClassPath")
最安全
性能最好
2、调用 Class 对象所对应的类中定义的方法
3、使用反射 API 来获取并调用类中的属性和方法等信息
6、创建对象的两种方式
两种创建对象方式
1、使用 Class 对象的 newInstance 方法
Class 对象要的类要有默认的空构造器
2、使用 Class 对象获取指定的 Constructor 对象,调用 Constructor 对象的 newInstance 对象创建实例
7、Method 的 invoke 方法
动态调用
过程
1、获取对象的 Method
2、调用 Method 的 invoke
4、注解
1、注解的概念
Java 提供的设置程序中元素的关联信息和源数据的方法
属于接口
程序通过反射来获取指定程序中元素的注解对象
通过注解对象来获取注解中的元数据信息
2、标准元注解
目的
注解其他注解
用于描述元数据的信息
定义注解处理器
四个标准元注解类型
@Target
说明了注解所修饰的对象范围
可以用于
packages
types
类型成员
方法参数
本地变量
具体取值类型
@Retention
定义该注解被保留的级别,被描述的注解在什么级别有效
三种类型级别
SOURCE
源文件中有效
CLASS
在 Class 文件中有效
RUNTIME
在运行时有效
@Documented
表明该注解应该被 javadoc 工具记录
@Inherited
标记注解,表明被标注的类型是被继承的
3、注解处理器
通过反射信息来获取注解数据
5、内部类
定义
定义在类内部的类
四种不同定义方式
1、静态内部类
定义在类内部的静态类
静态内部类可以访问外部类的静态变量和方法
静态内部类可以定义静态变量、方法、构造函数等
静态内部类通过“外部类.静态内部类”来调用
场景
和外部类关系密切且不依赖外部类实例的类
2、成员内部类
定义在类内部的非静态类
成员内部类不能定义静态方法和变量(final 修饰的除外)
3、局部内部类
定义在方法中的类
只需要在某个方法中使用某个特定的类时
4、匿名内部类
通过继承一个父类或实现一个接口的方式来直接定义并使用的类
匿名内部类没有 class关键字
直接使用 new 生成对象的引用
6、泛型
简介
本质参数化类型
提供了编译时类型的安全检测机制
允许在编译时检测非法的类型
好处
在编译期就能检查类型是否安全
所有强制类型的转换都是自动且隐式进行
提高代码的安全性和重用性
1、泛型标记和泛型限定
泛型标记
E-Element
集合中使用,表示在集合中使用的元素
T-Type
表示 java 类,包括基本类和自定义的类
K-Key
表示 Key,Map 中的 key
V-Value
表示值
N-Number
表示数值类型
?
表示不确定的 Java 类型
泛型限定
限定类的继承关系
1、对泛型上限的限定
<? extends T>
该通配符所代表的类型是 T 类的子类或接口 T 的子接口
2、对泛型下限的限定
<? super T>
该通配符所代表的的类型是 T类型的父类或父接口
2、泛型方法
将方法的参数类型定义为泛型,以便在调用的时候能够接受不同类型的参数
在方法的内部根据传递给泛型方法的不同参数类型来执行不同的处理方法
eg
public static <T> void generalMethod(T ... inputArray) {}
3、泛型类
在定义类时在类上定义了泛型,以便类在使用时能够根据传入的不同参数类型实例化不同的对象
eg
public class GeneralClass<T> {}
4、泛型接口
类似泛型类的定义
eg
public interface IGeneral<T> {}
5、类型擦除
编码阶段采用泛型所加上的类型参数,在编译时被去掉的过程
泛型用于编译阶段
擦除过程
1、查找用来替换类型参数的具体类
2、将代码中的类型参数替换为具体的类
7、序列化
作用
保存对象及状态的信息
持久化对象
1、序列化 API 使用
注意事项
1、类要实现序列化功能,只需实现java.io.Serializable接口即可
2、序列化和反序列化的 ID 必须保持一致
private static final long serialVersionUID = xxxxxL;
3、序列化不保存静态常量
4、需要序列化父类时,父类同样需要实现 Serializable 接口
5、使用 Transient 关键字可以阻止该变量被序列化;反序列化时,transient 变量的值会设定为对应类型的初始值
6、transient修饰的属性 和 static 所修饰的静态属性不会被序列化
2、序列化和反序列化
常用框架
arvo
protobuf
thrift
fastjson
JDK
ObjectOutputStream
ObjectInputStream
三、Java 并发编程
1、Java 线程创建方式
1、继承 Thread 类
实现 run 方法
线程类的具体实现逻辑
调用 start 启动线程
native 方法
2、实现 Runnable 接口
创建 Thread 类
传入 Runnable 接口的子类
3、通过 ExecutorService 和 Callable<Class>实现有返回值的线程
4、基于线程池
创建线程池,使用该线程池来提交线程任务
管理线程组及其运行状态,以便 Java 虚拟机能更好的利用 CPU 资源
2、线程池工作原理
1、线程复用
2、线程池核心组件和核心类
核心组件
线程池管理器
创建并管理线程池
工作线程
线程池中执行具体任务的线程
任务接口
定义工作线程的调度和执行策略
只有实现该接口,线程中的任务才能被线程池调度
任务队列
存放待处理的任务
新任务不断加入到队列中
执行完成的任务将从队列中被移除
核心类
Executor 框架
Executor
Executors
ExecutorService
ThreadPoolExecutor
构建线程的核心方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
corePoolSize
线程池中核心线程的数量
maximumPoolSize
线程池中最大线程的数量
keepAliveTime
当前线程数量超过 corePoolSize 时,空闲线程的存活时间
unit
keepAliveTime 的时间单位
workQueue
任务队列
被提交单尚未被执行的任务存放的队列
threadFactory
线程工程,用于创建线程
可以使用默认线程工厂或自定义线程工厂
handler
由于任务过多或其他原因等导致线程池无法处理时的任务拒绝策略
Callable
Future
FutureTask
3、线程池的拒绝策略
若线程池中核心线程数被用完且阻塞队列已排满时,线程池将没有足够的线程资源执行新的任务
内置拒绝策略
AbortPolicy
直接抛出异常,阻止线程正常运行
CallerRunsPolicy
如果被丢弃的线程任务未关闭,则执行该线程任务
不会真的丢弃任务
DiscardOldestPolicy
移除线程队列中最早的一个线程任务
尝试提交当前任务
DiscardPolicy
丢弃当前线程任务且不做任务处理
保障系统安全稳定
如果系统允许在资源不足的情况下丢弃部分任务,
自定义拒绝策略
实现RejectedExecutionHandler接口
3、5种常用线程池
1、newCachedThreadPool
可缓存的线程池
创建新线程时,如果有可重用的线程,就重用,否则重新创建新的线程并添加到线程池中
适用场景
执行时间很短的任务
重用线程来提高系统性能
2、newFixedThreadPool
固定大小的线程池
创建一个固定线程数量的线程池,并将线程资源存放在度列中循环使用
3、newScheduledThreadPool
可做任务调度的线程池
创建一个可定时调度的线程池,可设置在给定的延迟时间后执行或定期执行某个线程任务。
4、newSingleThreadPool
单个线程的线程池
确保永远有且只有一个可用的线程
该线程停止或异常后,线程池会启动一个新的线程来代替该线程继续执行任务
5、newWorkStealingPool
足够大小的线程池
JDK1.8 新增
创建持有足够线程的线程池来达到快速运算的目的
足够
JDK 根据当前线程的运行需求向操作系统申请足够的线程,来保障线程的快速运行
内部使用多个队列来减少各个线程调度产生的竞争
4、线程生命周期
1、新建状态:New
通过 new 关键字创建一个线程
新创建的线程处于新建状态
为线程分配可内存及初始化成员变量的值
2、就绪状态:Runnable
新创建线程调用 start 方法后转为就绪状态
JVM 已完成程序计数器的创建
等待噶线程的调度和执行
3、运行状态:Running
就绪状态的线程在竞争到 CPU 的使用权并开始执行 run 方法的线程
主要任务就是执行 run 方法中的逻辑代码
4、阻塞状态:Blocked
处于执行状态的线程在主动或被动的放弃 CPU 使用权并暂停执行时
阻塞状态类型
等待阻塞
运行状态的线程调用wait()方法时,JVM 会把该线程放入等待队列中
同步阻塞
运行状态的线程尝试获取正在被其他线程占用的对象同步锁时,JVM 会把该线程放入到锁池(Lock Pool)中
其他阻塞
运行状态的线程在执行 Thread.sleep(long ms)、Thread.join()或发出 I/O请求时
5、线程死亡:Dead
三种死亡方式
线程正常结束
run 方法或 call 方法执行完成
线程异常退出
线程抛出 Error 或未捕获的 Exception
手动结束
调用线程对象的 stop()方法
瞬间释放该线程占用的同步对象锁
不推荐使用
5、线程基本用法
1、线程等待:wait方法
进入到 WAITING 状态,只有等待其他线程的通知或被中断后才返回
会释放对象的锁
一般用于同步方法或同步代码中
2、线程睡眠:sleep方法
导致线程休眠
不会释放当前所占有的锁,会使线程进入 TIMED-WAITING状态
3、线程让步:yield方法
让当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片
4、线程终端:interrupt方法
向线程发出一个终止通知信号
影响该线程内部的一个中断标志位
不会中断一个正在运行的线程
调用 sleep 方法而使得线程处于 TIMED-WAITING状态,此时调用该方法会抛出 InterruptedException,使得线程提取结束 TIMED-WAITING状态
在抛出 InterruptedException前会清除中断标志文
抛出异常后调用 isInterrupted 会返回 false
中断状态是线程固有的一个标志位
安全的终止线程
5、线程加入:join方法
等待其他线程终止
主线程生成并启动了子线程,需要等到子线程返回结果并收集和处理在推出
6、线程唤醒:notify方法
Object 的方法
唤醒在此对象监视器上等待的一个线程
唤醒的线程是任意的
7、后台守护线程:setDaemon方法
定义一个守护线程,服务线程
后台线程
为用户线程提供公共服务
JVM 级别
8、sleep 与 wait 区别
sleep
属于 Thread 类
暂停执行指定的时间,让出 CPU 给其他线程,但是监控状态依然保持,在指定的时间过后自动恢复运行状态
线程不会释放对象锁
wait
属于 Object 类
线程会放弃对象锁,进入等待此对象的等待锁池
只有针对该对象调用 notify 方法后,该线程才能进入到对象锁池准备获取对象锁,并进入到运行状态
9、start 与 run 区别
start
启动线程,真正实现了多线程运行
调用 Thread 的 start 方法启动一个线程时,该线程属于就绪状态,非运行
run
线程体,包含了要执行的线程的逻辑代码
调用 run 方法后,线程进入到运行状态
run 方法执行完毕后,该线程终止,CPU 开始调度其他线程
10、终止线程的 4 种方式
1、正常运行结束
2、使用退出标志退出线程
设置 volatile 的 boolean exit类型标志
volatile
使 exit 线程同步安全
同一时刻只有一个线程修改exit 的值
3、使用 Interrupt 方法终止线程
线程处于阻塞状态
线程处于非阻塞状态
4、使用 stop 方法终止线程
不安全
子线程会抛出 ThreadDeatherror 错误,并释放子线程持有的所有锁
6、Java 中的锁
简介
保障多并发线程情况下数据的一致性
1、乐观锁
采用乐观的思想处理数据
读取数据
不加锁
更新数据
先读取版本号然后加锁
过程
1、比较当前版本号和上一次的版本号
版本号一致
更新
版本号不一致
重复进行读、比较、写操作
通过 CAS
Compare And Swap
比较和交换
原子更新操作
2、悲观锁
采用悲观思想处理数据
读取数据
加锁
通过 AQS
Abstract Queued Synchronized
抽象的队列同步器
定义了一套多线程访问共享资源的同步框架
Synchronized
ReentrantLock
Semaphore
CountDownLatch
.....
该框架下的锁会先尝试以 CAS 乐观锁去获取锁,如果获取不到,则转为悲观锁
3、自旋锁
目的
避免用户线程在内核状态的切换导致所时间消耗
思想
如果持有锁的线程能在很短的时间内来释放锁资源,那么那些等待竞争锁的线程就不需要在内核态和用户态之间的切换进入阻塞、挂起状态,只需要等一等(自旋),在等待持有锁的线程释放锁后即可立即获得锁
自旋会占用 CPU,长时间自旋会消耗 CPU 资源
设置自旋等待最大时间
优缺点
优点
减少 CPU 上下文的切换
适用场景
占用锁的时间非常短
锁竞争不激烈的代码块
缺点
持有锁的线程占用锁时间过长或锁的竞争过于激烈时,
线程在自旋过程中会长时间获取不到锁资源导致 CPU 浪费
自旋锁的时间阈值
JDK1.5
固定 DE 时间
JDK1.6
适应性自旋锁
不是固定值
由上一次在同一个锁的自旋时间及锁的拥有者的状态来决定
一个线程上下文切换的时间
4、synchronized
作用域
Java 对象
每个对象都有个 monitor 对象
加锁竞争 monitor 对象
方法
通过一个标记位来判断
代码块
通过在代码块的前后分别加上 monitorenter 和 monitorexit 指令来实现
独占式悲观锁
可重入锁
同一时刻只能有一个线程对该对象进行访问
重量级操作
需要调用操作系统的相关接口,性能较低
加锁对象不建议使用 String Integer Long 等基础数据类型
如果程序抛出异常,默认会释放锁
原理
6 个区域
ContentionList
锁竞争队列
所有请求锁的线程都放在锁竞争队列中
EntryList
竞争候选列表
在 ContentionList 中有资格成为候选者来进竞争锁资源的线程都移动到 EntryList 中
WaitSet
等待集合
调用 wait 方法后被阻塞的线程都在 WaitSet 中
OnDeck
竞争候选者
同一时刻最多只有一个线程在竞争锁资源
该线程的状态为 OnDeck
Owner
竞争到锁资源的线程
!Owner
在 Owner 线程释放锁后,会从 Owner 状态变更为!Owner状态
5、ReentrantLock
简介
继承了 Lock 接口并实现了接口中定义的方法
可重入的独占锁
独占锁
同一时刻只能被一个线程获取
获取锁的其他线程只能在同步队列中等待
可重入锁
该锁能够支持一个线程对同一个资源执行多次加锁操作
允许连续多次获得同一把锁
允许多次释放同一把锁
通过自定义队列同步器 AQS(Abstract Queued Synchronized)来实现锁的获取与释放
支持公平锁和非公平锁的实现
公平
竞争锁的机制是公平的
先到先得原则
非公平
不同的线程获取锁的机制是不公平的
JVM 遵循随机、就近原则分配锁的机制
执行效率高于公平锁
使用流程
1、定义一个 ReentrantLock
2、在需要加锁的地方通过 lock 方法加锁
3、等资源使用完成后通过 unlock 方法释放锁
如何避免死锁
响应中断
synchronized
如果有线程来获取锁
获取锁
执行代码
没有获取到锁
保持等待
ReentrantLock
提供了响应中断的功能
等待锁的过程中,线程可以根据需要来取消对锁的请求
可轮询锁
可以通过 boolean tryLock()来获取锁
有可用锁
获取锁并返回 true
无可用锁
返回 false
定时锁
通过 boolean tryLock(long time, TimeUnit unit) throws InterruptedException来获取锁
给定时间内是否获取到锁
获取到了锁
当前线程未中断
没有获取到锁
禁用当前线程
一直处于休眠状态-三种方式
当前线程获取到了可用锁并返回 true
清除当前线程已中断状态
当前线程在进入此方法时设置了该线程的中断状态
当前线程在获取锁时被中断,则跑出 InterruptedException
当前线程获取锁的时间超过了指定的等待时间
返回 false
如果设定时间<=0,该方法完全不等待
lock 区别
tryLock
若有可用锁,则获取该锁并返回 true,否则返回 false,不会有延迟或等待
tryLock(long timeout, TimeUnit unit)可以增加时间限制,超过指定时间还没有获得锁就返回 false
lock
如有可用锁,直接获取该锁并返回 true
否则一直等待直到获取可用锁
lockInterruptibly
锁中断时 lockInterruptibly会抛出异常
lock 不会
6、synchronized与 ReentrantLock 比较
相同点
控制多线程对共享对象的访问
都是可重入锁
都保证了可见性和互斥性
不同点
synchronized
隐式获取和释放锁
JVM 级别的锁
底层实现为同步阻塞,采用的是悲观并发策略
Java 中的关键字,由内置的语言来实现
通过 synchronized 无法得知有没有成功获取锁
抛出异常会释放锁
ReentrantLock
显示获取和释放锁
在 finally 控制块中进行解锁操作
避免程序出现异常而无法正常释放锁
可响应中断,可轮回
为处理锁提供了更多的灵活性
API 级别的锁
可以定义公平锁和非公平锁
公平锁
当一个线程来获取锁时,会先判断队列中是否有线程在等待,如果队列中有等待的,则该线程进入到等待状态
非公平锁
不管队列中有没线程等待,直接尝试获取锁
通过 Condition 可以绑定多个条件
底层实现为同步非阻塞,采用的是乐观并发策略
Lock 是一个接口
通过 Lock 可以得知有没有成功获取锁
可以通过分别定义读写锁来提高多个线程读操作的效率
7、Semaphore
简介
基于计数的信号量
1、定义信号量对象的时候设定一个阈值
2、基于该阈值,多个线程来竞争获取许可信息
3、线程竞争到许可信号后开始执行具体业务逻辑
4、业务逻辑执行完毕后释放该许可信号
5、许可信号的竞争队列超过阈值后,新加入的申请许可信号的线程将被阻塞,直到其他许可信息被释放
对锁的申请和释放类似 ReentrantLock
acquire
release
需要手动执行
需要在 finally 块完成
已实现功能有
可轮询的锁请求
定时锁
公平锁
非公平锁机制
8、AtomicInteger
提供原子操作的 Integer 类
性能是 synchronized 和 ReentrantLock 的好几倍
9、可重入锁
递归锁
synchronized
ReentrantLock
同一线程中,外层函数获取到该锁后,内层的递归函数仍然可以继续获取该锁
10、公平锁与非公平锁
公平锁
在分配锁之前检查是否有线程在排队等候获取锁,优先将所分配给排队时间最长的线程
在多核情况下需要维护一个锁线程等待队列,基于该队列进行锁的分配
效率比非公平锁慢很多
Synchronized
非公平锁
在分配锁之前不考虑线程排队等候情况,直接获取锁,获取不到在排到队尾等待
ReentrantLock
默认的 lock 方法为非公平锁
11、读写锁:ReadWriteLock
类型
读锁
多个读锁不互斥
读锁与写锁互斥
读的地方使用读锁
适用场景
共享数据支持很多线程并发读,但不能支持很多线程并发写时
写锁
写的地方使用写锁
无写锁的情况下,读是无阻塞的
适用场景
共享数据在同一时刻只能有一个线程在写,且写的过程中不能读取该共享数据时
做法
1、分别定义读锁和写锁
2、读取共享数据时使用读锁
3、写共享数据时使用写锁
4、使用完成后释放锁
ReentrantReadWriteLock
12、共享锁和独占锁
独占锁
互斥锁
每次仅允许有一个线程持有锁
ReentrantLock 为独占锁的实现
悲观的加锁策略
限制了读操作的并发性
共享锁
允许多个线程同时获取该锁,并发访问共享资源
ReentrantReadWriteLock 中的读锁为共享锁的实现
加锁和解锁最终都调用的 Sync提供的方法
Sync 对象通过继承 AQS 来实现
AQS 内部的 Node 定义的两个常量
SHARED
共享锁
EXCLUSIVE
独占锁
乐观的加锁策略
并发读不会影响数据的一致性
13、重量级锁和轻量级锁
重量级锁
基于操作系统的互斥量(Mutex Lock)实现的锁
需要进程在用户态和内核态间切换
开销较大
synchronized
内部基于监视器锁Monitor实现
监视器锁基于底层操作系统的 Mutex Lock 实现
轻量级锁
核心设计
在没有多线程竞争的前提下,减少重量级锁的使用来提高系统性能
适用场景
线程交替执行同步代码块
互斥操作
同一时刻有多个线程访问同一个锁,会导致轻量级锁膨胀微重量级锁
14、偏向锁
同一个锁被同一个线程多次获取的情况
尽量减少轻量级锁的执行路径
轻量级锁的获取和释放需要多次的 CAS 原子操作
偏向锁只需要在切换 ThreadID 后执行一次 CAS 原子操作
提高锁的运行效率
出现多个线程竞争锁的情况时,
JVM 会自动撤销偏向锁
适用场景
在某个线程交替执行同步块时进一步提高性能
15、分段锁
一种思想
将数据分段并在每个段上都单独加锁,细粒度化锁
提升并发效率
ConcurrentHashMap
内部分段加锁
16、同步锁和死锁
多个线程同时被阻塞时,他们之间相互等待对方释放锁资源的情况
解决方案
为锁增加超时时间,在线程持有锁超时后自动释放锁
17、如何进行锁优化
减少锁持有的时间
适用场景
只在有线程安全要求的程序上加锁来尽量减少同步代码块对锁的持有时间
减少锁粒度
将单个耗时较多的锁操作拆分为多个耗时较少的锁操作来增加所的并行度,减少同一个锁的竞争
减少锁的竞争后,偏向锁、轻量级锁的使用率才会提高
经典实现
ConcurrentHashMap
锁分离
根据不同的应用场景,将锁的功能进行分离,来应对不同的变化
读写锁分离
读锁
写锁
读读不互斥
读写互斥
写写互斥
扩展
只要操作不影响,就可以拆分
LinkedBlockingQueue
头部取出数据
尾部加入数据
锁粗化
将关联性强的锁操作集中起来处理,提高系统整体的效率
锁消除
消除不必要的锁来提高系统的性能
7、线程上下文切换
1、上下文切换
背景
CPU 利用时间片轮询来为每个任务都服务一定的时间,然后将当前任务的状态进行保存,继续服务下一个任务,
定义
任务的状态保存及加载
名词简介
进程
一个运行中的程序的实例
一个进程内部会有多个线程同时运行
多个线程会与创建它的进程共享同一地址空间和其他资源
上下文
线程切换时 CPU寄存器和程序计数器所保存的当前线程的信息
寄存器
CPU 内部容量较小但速度很快的内存区域
对常用值的快速访问来加快程序运行的速度
程序计数器
一个专用的寄存器
表明程序指令中 CPU 正在执行的位置
存储的值为
正在执行的指令的位置
下一个将被执行的指令的位置
2、引起线程上下文切换的原因
当前正在执行的任务完成,系统的 CPU 正常调度至下一任务
当前正在执行的任务遇到I/O等阻塞操作,调度器挂起此任务,继续调度下一个任务
多个任务并发抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续调度下一个任务
用户的代码挂起任务,比如 sleep 等让出了 CPU
硬件中断
8、Java 阻塞队列
队列
一种只允许在表的前端进行删除操作,在表的后端进行插入操作的线性表
阻塞队列
两种情况
消费者阻塞
队列为空时,消费者端的线程被自动阻塞,直到有数据放入,消费者线程被自动唤醒并消费数据
生产者阻塞
队列为满时且没有可用空间时,生产端的线程被自动阻塞,直到队列中有空的位置腾出,生产者线程被自动唤醒并生产数据
1、阻塞队列的主要操作
插入
add
将指定的元素插入队列中,
成功返回 true
没有可用空间
抛出 IllegalStateException
元素为 null
抛出 NullPointerException
offer
将指定的元素插入到队列中
成功
返回 true
没有可用空间
返回 false
offer(e,time,unit)
将指定的元素插入到队列中,设定指定的等待时间,
成功返回 true
超过指定的等待时间
返回 false
put
将指定的元素插入到队列中,
如果队列满了
阻塞等待可用的队列空间释放
直到有可用的队列空间释放且插入成功为止
移除
poll()
取走队列中队首的对象
取不到数据
返回 null
pool(time,unit)
取走队列中队首的对象
在指定的时间内队列中有数据可取时
返回队列中的数据
在指定的时间内队列中没有数据可取时
等待一定时间,
超时且没有数据可取时
返回 null
take()
取走队列中队首的对象
队列为空
进入阻塞状态等待
队列不空
及时取出新加入的数据
drainTo(collection)
一次性从队列中批量获取所有可用的数据对象
可以指定获取数据的个数
提升获取数据的效率
避免多次频繁操作引起队列锁定
2、Java 中阻塞队列实现
ArrayBlockingQueue
基于数组结构实现的有界阻塞队列
按照先进先出原则对元素进行排序,
默认情况不保证元素操作的公平性
队列操作的公平性
生产者线程或消费者线程发生阻塞后再次被唤醒时,按照阻塞的先后顺序来操作队列
先阻塞的生产者线程优先向队列中插入元素
先阻塞的消费者线程优先从队列中获取元素
保证公平性会降低吞吐量
eg
公平队列
new ArrayBlockingQueu(1000, true)
非公平队列
new ArrayBlockingQueue(1000, false)
LinkedBlockingQueue
基于链表结构实现的有界阻塞队列
按照先进先出原则对元素进行排序
对生产者端和消费者端分别采用了两个独立的锁来控制数据同步
写锁
队列头部的锁
读锁
队列尾部的锁
eg
new LinkedBlockingQueue(100)
PriorityBlockingQueue
支持优先级排序的无界阻塞队列
默认情况下自然顺序升序排列
可以自定义 compareTo 方法指定排序规则
构造方法中指定 Comparator
两个元素优先级相同,不保证该元素的存储和访问顺序
DelayQueue
基于优先级队列实现的无界阻塞队列
支持延时获取元素
底层使用 PriorityQueue 实现
该队列中的元素必须实现 Delayed 接口
定义了创建元素时该元素的延迟时间
内部通过为每个元素的操作加锁来保障数据一致性
只有在延迟时间到后才从队列中来提取元素
适用场景
缓存系统的设计
保存缓存元素的有效期,使用一个线程循环查询 DelayQueue,一旦获取到元素,表示缓存的有效期到了
定时任务调度
保存即将执行的任务和执行时间,一旦获取到元素,就标识该任务开始执行
TimerQueue
内部使用 DelayQueu 实现
SynchronousQueue
用于控制互斥操作的阻塞队列
不存储元素的阻塞队列
每个 put 操作必须等待上一个 take 操作完成才可以
适用场景
将生产者线程的数据直接传递到消费者线程
吞吐量高于 LinkedBlockingQueue 和 ArrayBlockingQueue
LinkedTransferQueue
基于链表结构的无界阻塞队列
增加了
transfer
tryTransfer
tryTransfer(e, timeout, unit)
LinkedBlockingQueue
基于链表结构实现的双向阻塞队列
可以在队列双端分别执行插入和移除元素操作
多线程操作队列,减少一半所资源竞争,提高队列的操作效率
增加了
First 结尾
在队列头部执行操作
Last 结尾
在队列尾部执行操作
初始化时可以设置队列大小,防止内存溢出
9、Java 并发关键字
1、CountDownLatch
同步工具类
允许一个或多个线程一直等待其他线程的操作执行完后在执行相关操作
不可重用
基于线程计数器来实现并发访问控制
适用场景
主线程等待其他子线程任务都执行完毕后再执行接下来的业务逻辑单元
适用过程
1、主线程中定义CountDownLatch,将线程计数器的初始值设定为子线程的个数
2、多个子线程并发执行
每个子线程执行完毕后调用 countDown 函数
3、所有子线程执行完毕后,主线程被唤醒并继续执行
2、CyclicBarrier
循环屏障
同步工具
所有等待线程都被释放之后,CyclicBarrier 可以被重用
CyclicBarrier 的运行状态
Barrier 状态
调用 wait 方法
可重用
适用场景
让一组线程互相等待至大家都到达某个状态后再全部同时执行接下来的业务逻辑单元
3、Semaphore
信号量
控制同时访问某些资源的线程个数
调用过程
调用 acqure()获取许可
调用 release()释放该许可
适用场景
多个线程需要共享有限资源的情况
4、volatile 关键字的作用
确保将变量的更新操作通知到其他线程
两种特性
保证该变量对所有线程可见
一个线程修改了变量的值后,新的值会立即被其他线程获取到
禁止指令重排
volatile 变量不会被缓存到寄存器中或其他处理器不可见的地方
读取 volatile 类型的变量时总会返回最新额写入的值
指令重排序
CPU 执行指令可并发执行多条,为了充分利用这一点,编译器对源码进行编译完后,可对指令进行重新排序
实现原理
CPU 级别增加读屏障和写屏障
loadfence原语指令
storefence 原语指令
mfence 原语指令
eg
new 对象的三个步骤
1、申请内存空间
2、成员变量初始化
3、变量地址指向该内存地址
实现依赖硬件的 MESI 协议
缓存一致性协议
比 synchronized 更轻量级的同步机制
不会执行加锁机制
适用场景
一个变量被多个线程共享,多个线程均可针对该变量进行赋值或读取的操作
原理
多线程读写普通变量
每个线程将数据从内存赋值到 CPU 缓存中,
某个线程更新内存中的变量后,不会更新 CPU Cache 中的值
多线程读写 volatile 变量
JVM 保证每次读取变量均直接从内存中读取
跳过了 CPU Cache
解决了多线程数据同步问题
如何确保并发环境线程安全
1、对变量的写操作不依赖当前值,或单纯的变量赋值
2、该变量没有被包含在具有其他变量的不变式中
不同的 valitile 变量之间不能相互依赖
只有在状态真正独立于程序内的而其他内容时才能使用
10、多线程如何共享数据
多线程通信
实现方式
共享内存
三个关注点
可见性
有序性
原子性
被锁解决了
共享数据方式
1、抽象数据为类
2、内部类 Runnable 对象
11、ConcurrentHashMap 并发
1、减小锁粒度
通过缩小锁定对象的范围来减少锁冲突的可能性,最终提高系统的并发能力
削弱多线程锁竞争的有效方法
2、ConcurrentHashMap 实现
采用分段锁的思想支持并发操作,线程安全
数据段
Segment
若干个小的 HashMap
默认为 16 个数据段
锁的并发度
12、Java 线程调度
1、抢占式调度
每个线程都以抢占的方式来获取 CPU 资源并快速执行,执行完毕后立刻释放 CPU 资源,具体哪些线程能抢到 CPU 资源由操作系统控制
适用于多线程并发执行的情况
一个线程的阻塞不会导致整个进程性能下降
2、协同式调度
一个线程在执行完毕后主动通知操作系统将 CPU 资源切换到另一个线程来执行
线程对 CPU 的持有时间由线程自身控制
适用于多个线程交替执行某些任务的情况
缺点
其中某个线程由于外部原因导致运行阻塞,会影响整个系统阻塞甚至崩溃
3、Java 线程调度的实现:抢占式
Java 给每个线程按照优先级高低分配不同的 CPU 时间片
高优先级优先执行
4、线程让出 CPU 的情况
1、当前运行的线程主动放弃 CPU
当前运行的线程调用 yield()放弃 CPU 的使用权
2、当前运行的线程进入阻塞状态
调用文件读取 I/O操作
锁等待
Socket 等待
3、当前线程运行结束
执行完 run()中的任务
13、进程调度算法
1、优先调度算法
先来先服务调度算法
每次调度时,从队列中选择一个或多个最早进入该队列的作业,为其分配资源、创建进程和放入就绪队列
实现简单相对公平
短作业优先调度算法
每次调度时,从队列中选择一个或多个预估运行时间最短的作业,为其分配资源、创建进程和放入就绪队列。
优先运行短时间作业,提高 CPU 整体的利用率和系统运行效率
某些大任务可能会出现长时间得不到调度的情况
2、高优先权优先调度算法
非抢占式优先权算法
每次调度时,从队列中选择一个或多个优先权最高的作业为其分配资源、创建进程和放入就绪队列。
运行过程中一直持有该 CPU,直到进程执行完毕或发生异常而放弃 CPU
抢占式优先权调度算法
首先把 CPU 资源分配给优先权最高的任务并执行,在 运行过程中,如果出现比当前运行任务优先权更高的任务,调度算法会暂停当前运行任务并回收 CPU 资源,来为该任务分配新的优先权最高的任务
有紧急作业,第一时间被执行
高响应比优先调度算法
使用了动态优先权的概念
优先权越高
执行时间越短
等待时间越长
保障快速、并发的执行短作业
保障了优先权低但长时间等待的任务也有被调度的可能性
保障效率的同时尽可能的提升调度的公平性
3、时间片的轮转调度算法
时间片轮转算法
按照先来先服务从就绪队列中取出一个任务
为该任务分配一定的 CPU 时间片去运行
该任务使用完 CPU 时间片后,由时间计数器发出时钟中断请求
调度器停止当前进程的运行,并将该进程放入到就绪队列的队尾
调度器从就绪队列的队首取出一个任务来分配 CPU 时间片并执行
多级反馈队列调度算法
在时间片轮询算法的基础上设置多个就绪队列,并为每个就绪队列都设置不同的优先权
14、CAS
1、CAS 概念:比较并交换
CAS(V,E,N)
V
要更新的变量
E
预期的值
N
新值
仅当 V=E 时,才将 V 设置为 N
如果 V 和 E 不同,说明有其他线程做了更新,此时当前线程什么也不做,返回 V 的真实值
2、CAS 特性:乐观锁
3、CAS 自旋等待
java.util.concurrent.atomic中的原子类的内部实现
某个线程进入方法中执行指令时,不会被其他线程打断
其他线程就像自旋锁一样,一直等到该方法执行完毕才有 JVM 从等待的队列中选择另外一个线程进入
https://www.processon.com/outline/view/5ebfa3e40791290fe068378e
15、ABA 问题
CAS 算法的实现的重要前提
需要取出内存中某时刻的数据,在下一时刻进行比较、替换
中间的时间差内数据可能会发生变化,导致 ABA 的问题
部分乐观锁通过版本号来解决 ABA
16、AQS
1、AQS 原理
AQS
Abstract Queued Synchronizer
抽象的队列同步器
通过维护一个共享资源状态和一个先进先出的线程等待队列来实现一个多线程访问共享资源的同步框架
原理
AQS 为每个共享资源都设置一个共享资源锁
线程在需要访问共享资源时,首先需要获取共享资源锁
获取到了共享资源锁
在当前线程中使用该共享资源
没有获取到共享资源锁
等待下一次资源调度
同步类的实现都依赖 AQS
ReentrantLock
Semaphore
CountDownLatch
2、state:状态
volatile int 类型的变量
当前同步状态
3、共享资源方式
独占式
只有一个线程能执行
ReentrantLock
共享式
多个线程可同时执行
Semaphore
CountDownLatch
四、数据结构
1、栈及 Java 实现
2、队列及 Java 实现
3、链表
1、链表特点
2、单向链表操作及实现
3、双向链表操作及实现
4、循环链表
4、散列表
1、常用构造散列函数
2、Hash 应用
5、二叉排序树
1、插入操作
2、删除操作
3、查找操作
4、Java 实现二叉排序树
6、红黑树
1、特性
2、左旋
3、右旋
4、添加
5、删除
7、图
1、无向图和有向图
2、存储结构:邻接矩阵
3、存储结构:邻接表
4、图的遍历
8、位图
1、位图的数据结构
2、位图的 Java 实现
五、Java 常用算法
1、二分查找算法
2、冒泡排序算法
3、插入排序算法
4、快速排序算法
5、希尔排序算法
6、归并排序算法
7、桶排序算法
8、基数排序算法
9、其他算法
1、剪枝算法
2、回溯算法
3、最短路径算法
六、网络与负载均衡
1、网络
简介
信息传输、接收、共享的虚拟平台,将各个点、面、体的信息联系到一起,从而实现资源的共享
通过了解原理,针对性的调优系统架构
1、OSI 七层网络模型
从下到上
物理层
目的
主要定义物理设备标准
作用
传输比特流
做法
发送端将 1、0 转化为电流强弱来传输,在到达目的地后再将电流强弱转化为 1、0
模数转换和数模转换
数据名称
比特
数据链路层
作用
对数据包中的 MAC 地址进行解析和封装
数据名称
帧
工作的设备
网卡
网桥
交换机
网络层
作用
对数据包中的 IP 地址进行封装和解析
数据名称
数据包
工作的设备
路由器
交换机
防火墙
......
传输层
目的
定义传输数据的协议和端口号
作用
数据的分段、传输和重组
工作的协议
TCP
传输控制协议
特点
传输效率低
可靠性强
适用场景
可靠性强、数据量大的数据
UDP
用户数据报协议
特点
与 tcp 相反
适用场景
传输可靠性要求不高、数据量小的数据
会话层
作用
在传输层的基础上建立连接和管理会话
包括
登录验证
端点续传
数据粘包及分包
......
设备之间需要互相识别的
IP
MAC
主机名
表示层
作用
对接收的数据进行解释、加密、解密、压缩、解压缩等
把计算机能够识别的内容转换为人能够识别的内容
应用层
基于网络构建具体应用
FTP
Telnet
HTTP
DNS
SNMP
2、TCP/IP四层网络模型
TCP/IP
不是指 TCP/IP这两个协议的合称
指的是因特网整个的 TCP/IP协议簇
四个层次
(从下到上)
(从下到上)
应用层
负责具体应用层协议的定义
telnet
ftp
smtp
dns
nntp
http
传输层
使源端和目的端机器上的对等实体可以基于会话相互通信
两个端到端的协议
TCP
面向连接的协议
提供可靠的报文传输和对上层应用的连接服务
提供可靠性保证、流量控制、多路复用、优先权和安全性控制等功能
UDP
面向无连接的不可靠传输协议
不需要 TCP 的排序和流量控制
网络层
用于数据的传输、路由及地址的解析
保障可以把数据发送给任何网络上的目标
数据经网络传输,发送的顺序和到达的顺序可能会发生变化
网络层协议有
IP
ARP
地址解析协议
网络接口层
定义了主机间网络连通的协议
Echernet
FDDI
ATM
......
3、TCP 三次握手/四次挥手
建立连接-三次握手
TCP 数据在传输之前会建立连接需进行 3 次沟通
过程
1、客户端发送 SYN(seq=x)报文给服务器端,进入 SYN_SEND状态
2、服务器端收到 SYN 报文,回应一个SYN(seq=y)和 ACK(ack=x+1)报文,进入 SYN_RECV状态
3、客户端收到服务器端的SYN报文,回应一个 ACK(ack=y+1)报文,进入 Established 状态
断开连接-四次挥手
TCP 数据在传输完成后断开连接的时候要进行 4 次沟通
TCP 的半关闭导致
TCP 连接时全双工的
关闭时对每个方向都要单独进行关闭
瓣关闭
一方完成数据发送任务后,需要发送一个 FIN 来向另一方通告要终止该方向的链接
发起方
客户端发起
客户端主动断开连接
服务器端发起
服务器端主动断开连接
过程
1、客户端应用进程调用断开连接的请求,向服务器端发送一个终止标志位 FIN=1,seq=u 的消息,进入 FIN-WAIT-1状态
2、服务器端收到 FIN 消息后,返回一个 ACK=1,ack=u+1,seq=v的消息给客户端,服务器端进入 CLOSE-WAIT状态,客户端收到消息后进入到FIN-WAIT-2状态
3、服务器端将关闭链路前需要发送给客户端的消息发送给客户端,在等待数据发送完成后,发送一个终止标志位FIN=1,ACK=1,seq=w,ack=u+1的消息给客户端,此时服务器端进入LAST-ACK状态,等待客户端最终端口链路
4、客户端接收到最终的 FIN 消息后,发送一个 ACK=1,seq=u+1,ack=w+1消息给服务器端,此时客户端进入到TIM-WAIT状态,等待 2MSL 设置的时间后,客户端进入到CLOSE状态
4、HTTP 的原理
简介
无状态协议
客户端和服务器之间不需要建立持久的连接
传输流程
地址解析
地址解析通过域名系统 DNS 解析服务器域名从而获得主机的 IP 地址
元素
协议名
主机名
端口
对象路径
封装 HTTP 数据包
将地址解析的信息等结合本机自己的信息封装 HTTP 请求数据包
封装 TCP 包
将 HTTP 请求数据包进一步封装成 TCP 数据包
建立 TCP 连接
基于 TCP 的三次握手机制建立 TCP 链接
客户端发送请求
建立连接后,客户端发送一个请求给服务器
服务器响应
服务器端收到请求后,结合业务逻辑进行数据处理,然后向客户端返回响应信息
响应信息
状态行
协议版本号
成功/失败的代码
消息体
......
服务器端关闭 TCP 连接
服务器向浏览器发送请求数据后关闭 TCP 链接
消息头增加Conneection: keep-alive
TCP 连接在请求响应数据发送后仍保持连接状态
减少请求响应时间
节约网络带宽和系统资源
常见状态码
20x
成功
30x
网络重定向
40x
客户端请求错误
50x
服务器错误
5、CDN 的原理
简介
Content Delivery Network
内容分发网络
基于部署在各地的机房服务器,通过中心平台的负载均衡、内容分发、调度的能力,使得用户就近获取所需内容,降低网络延迟,提升用户访问的响应速度和体验度
关键技术
内容发布
内容路由
内容交换
性能管理
主要特点
本地缓存加速
镜像服务
远程加速
带宽优化
集群抗攻击
2、负载均衡
简介
建立在现有网络结构上, 提供廉价、有效、透明的方法来扩展网络设备和服务器带宽
增加吞吐量,加强网络数据处理能力,提高网络的灵活性和可用性
1、四层及七层负载均衡的对比
四层负载均衡
基于 IP 和端口来实现网络层的负载均衡
具体实现
对外提供一个虚拟 IP 和端口接收所有用户的请求,根据负载均衡配置和负载均衡策略将请求发送给真实的服务器
通过修改报文中的目标地址和端口来实现报文的分发和负载均衡
只能针对 IP 地址和端口上的数据做统一的分发
通常使用 LVS 等技术实现基于 Socket 的四层负载均衡
常用软硬件
F5
硬件负载均衡器
功能完备
价格昂贵
LVS
基于 IP+端口实现四层负载均衡软件
常和 Keepalive 配合使用
Nginx
同时实现四层负载和七层负载均衡
带缓存功能
可以基于正则表达式灵活转发
七层负载均衡
基于 URL 等资源来实现应用层基于内容的负载均衡
具体实现
通过虚拟的 URL 或主机名接收所有用户的请求,将请求发送给真实的服务器
根据消息的内容做更加详细的有针对性的负载均衡
通常使用 Nginx 等技术实现基于内容分发的七层负载均衡
可以使整个网络更智能化
常用软件
HAProxy
支持七层代理、会话保持、标记、路径转移等
Nginx
同时实现四层负载和七层负载均衡,在 Http 和 Mail 协议功能比较好
性能和 HAProxy 差不多
Apache
使用简单
性能较差
2、负载均衡算法
轮询均衡(Round Robin)
将客户端请求轮流分配到 1 到 N 台服务器上,每台服务器均被均等的分配一定数量的客户端请求
适用于集群中所有服务器都有相同的软硬件配置和服务能力的情况下
权重轮询均衡(Weighted Round Robin)
根据每台服务器的不同配置及服务能力,为每台服务器都设置不同的权重值,然后按照设置的权重值轮询的将请求分配到不同服务器上
适用于服务器配置不均等的集群中
随机均衡(Random)
将来自网络的请求随机分配给内部的多台服务器
不考虑服务器的配置和负载情况
权重随机均衡(Weighted Random)
类似权重轮询算法
在分配是不在轮询发送,而是随机选择某个权重的服务器发送
响应速度均衡(Response Time)
根据服务器设备响应速度的不同将客户端请求发送到响应速度最快的服务器上,
响应速度的获取
通过负载均衡设备定时为每台服务器都发出一个探测请求实现
最少连接数均衡(Least Connection)
在负载均衡器内部记录当前每台服务器正在处理的连接数量,在有新的请求时,将该请求分配给连接数最少的服务器
适用于网络连接和带宽有限,CPU 处理任务简单的请求服务
ftp
处理能力均衡
将服务请求分配给内部负荷最轻的服务器,
负荷
根据服务器的 CPU 型号、CPU 数量、内存代销及当期连接数等换算的
适用于七层负载均衡的场景
DNS 响应均衡(Flash DNS)
在分布在不同中心机房的负载均衡设备都收到同一个客户端的域名解析请求时,所有负载均衡设备均解析此域名并将解析后的服务器 IP 地址返回给客户端,客户端向收到第一个域名解析后的 IP 地址发起请求服务,而忽略其他负载均衡设备的响应。
适用于全局负载均衡的场景
散列算法均衡
通过一致性散列算法和虚拟节点技术将相同参数的请求总是发送到同一台服务器,该服务器将长期、稳定的为某些客户端提供服务
某个服务器移除或异常后,该服务器的请求基于虚拟节点技术平摊到其他服务器
IP 地址散列
在负载均衡器内部维护了不同链接上客户端和服务器的 IP 对应关系表,将来自同一客户端的请求同一转发给相同的服务器
以会话为单位,保证同一客户端的请求能一直在同一台服务器上处理
适用于客户端和服务器端需要保持长连接的场景
基于 TCP 长连接的应用
URL 散列
通过管理客户端请求 URL 信息的散列表,将相同URL的 请求转发给同一台服务器
适用于在七层负载中根据用户请求类型的不同将其转发给不同类型的应用服务器
3、LVS 原理及应用
简介
Linux Virtual Server
虚拟机的服务器集群系统
采用 IP负载均衡技术将请求均衡的转移到不同的服务器上执行
通过调度器自动屏蔽服务器
组成
前端的负载均衡器LB(Load Balancer)
后端的真实服务器RS(Real Server)
核心组件
负载均衡调度器
整个集群对外提供服务的入口
通过对外提供一个虚拟 IP 来接收客户端请求
客户端请求发送到该虚拟IP后,负载均衡调度器会负责将请求按照负载均衡策略发送到一组具体的服务器上
服务器池
一组真正处理客户端请求的真实服务器
共享存储
为服务器池提供一个共享的存储区
使得服务器池拥有相同的内容,提供相同的服务
LVM-名词
CIP
Client IP Address
客户端IP
用户记录发送给集群的源 IP 地址
VIP
Virtual IP Address
虚拟 IP
用于 Director 对外提供服务的 IP 地址
DIP
Director IP Address
Director IP
用于连接内外网络的 IP 地址
负载均衡器上的 IP 地址
RIP
Real Server 的 IP Address
真实 IP
集群中真实服务器的物理 IP 地址
LIP
Local IP Address
LVS 内部 IP
LVS 集群的内部通信 IP
4、Nginx 反向代理与负载均衡
七、数据库及分布式事务
1、数据库基本概念及原则
1、存储引擎
简介
数据库的底层软件组织
数据库管理系统 DBMS 使用存储引擎来创建、查询、更新和删除数据
存储引擎提供了不同的存储机制、索引技巧、锁定水平等
常见存储引擎
MyIASM
MySQL默认的存储引擎
不支持数据库事务、行级锁和外键
写操作需要锁定整个表
insert
update
特点
执行读取操作的速度快
占用的内存和存储资源较少
更新数据慢
不支持事务处理
设计之初的假设
数据被组织称固定长度的记录
数据按顺序存储
查找数据
MyIASM直接查找文件的 OFFSET,定位比 InnoDB 要快
InnoDB 寻址要先映射到块,再映射到行
InnoDB
为 MySQL 提供了事务支持、回滚、崩溃修复能力、多版本并发空置、事务安全的操作
底层存储结构为 B+树
每个节点对应 InnoDB 中的一个 Page
Page 是大小固定的一般为 16KB
非叶子节点只有键值
叶子节点包含完整的数据
结构图
适用场景
经常有数据更新的表,适合处理多重并发更新请求
支持事务
支持灾难恢复(通报不哦 bin-log 日志等)
支持外键约束,只有 InnoDB 支持外键
支持自动增加列属性auto_increment
TokuDB
底层存储结构为 Fractal Tree
类似 B+树
结构图
在线添加索引,不影响读写操作
非常高的写入性能
适用场景
写入速度快,访问频率不高的数据或历史数据归档
Memory
使用内存空间
对应一个磁盘文件用于赤计划
访问速度非常快
使用 Hash 索引来
Archive
Federated
2、创建索引的原则
通过创建索引能够提高数据库查询数据效率
原则
选择唯一性索引
基于 Hash 算法
快速唯一的定位某条数据
为经常需要排序、分组和联合操作的字段建立索引
为常作为查询条件的字段建立索引
限制索引的数量
索引越多,更新越慢
尽量使用数量少的索引
索引值很长,占用的磁盘会变大,影响查询速度
尽量使用前缀来索引
使用字段的部分前缀来索引
删除不再使用或很少使用的索引
尽量选择区分度高的列作为索引
区分度
字段值不重复的比例
索引列不能参与计算
带函数的查询不建议参与索引
尽量扩展现有索引
联合索引的查询效率高于多个独立索引
3、数据库三范式
范式
具有最小冗余的表结构
第一范式
每列都是不可再分的最小数据单元
目标
确保每列的原子性
eg
第二范式
在第一范式的基础上,规定表中的非主键列不存在对主键的部分依赖
要求每个表只描述一件事情
eg
第三范式
满足第一范式和第二范式
并且表中的列不存在对非主键列的传递依赖
eg
4、数据库事务
数据库事务执行一系列基本操作,这些基本操作组成一个逻辑工作单元一起向数据库提交,要不都执行,要不都不执行
事务是一个不可分割的工作逻辑单元
ACID 四属性
原子性 Atomicity
事务是一个完整操作,参与事务的逻辑单元要么都执行,要么都不执行
一致性 Consistency
事务执行完毕时,数据必须处于一致状态
无论是正常执行完毕还是异常退出
隔离性 Isolation
对数据进行修改的所有并发事务都是彼此隔离的
不应以任何方式依赖或影响其他事务
永久性Durability
事务操作完成后,对数据的修改将被持久化到永久性存储中
5、存储过程
6、触发器
2、数据库并发操作和锁
1、数据库并发策略
三种方法
乐观锁
在读数据时,认为别人不会去写所读的数据
悲观锁
在修改某条数据时,不允许别人去读取该数据,直到自己的整个事务都提交并释放锁,其他用户才能访问该数
类型
排它锁
写锁
共享锁
读锁
时间戳
操作数据时不加锁,通过时间戳来控制并发时出现的问题
在数据库表中额外增加一个时间戳,每次读取数据时,将时间戳也读取出来,更新数据时把时间戳加 1,提交之前跟数据库中的自读单进行比较
比数据库中的值大
允许保存
否则
不允许保存
2、数据库锁
行级锁
对某行数据机械能加锁
排它锁
防止事务修改此行
操作类型
INSERT\UPDATE\DELETE\SELECT ... FOR UPDATE
SELECT ... FOR UDPATE 允许用户一次针对多条记录执行更新
使用 COMMIT或 ROLLBACK 语句释放锁
表级锁
对当前操作的整张表加锁
实现简单、资源消耗较少
大部分存储引擎都支持
类型
表共享读锁
共享锁
表独占写锁
排它锁
页级锁
锁定粒度介于行级锁和表级锁之间
一次锁定相邻的一组记录
行级锁
冲突少
加锁速度慢
表级锁
加锁速度快
冲突多
基于 Redis 的分布式锁
流程
获取锁
调用 setnx,查看返回值
0
该锁正被别人使用
1
成功获取锁
释放锁
判断锁是否存在,存在的话,执行 Redis 的 delete 操作释放锁
3、数据库分表
垂直切分
按照功能模块、关系密切程度切分并部署到不同的库中
水平切分
一个表中数据量过大时,按照某种规则进行划分,将其存储到多个结构相同的表不同的库上
3、数据库分布式事务
1、CAP
CAP 原则,又称 CAP 定理
一个分布式系统中,一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)三者不可兼得
一致性
在分布式系统的所有数据备份中,在同一时刻是否有同样的值
所有节点都访问同一份最新的数据副本
可用性
在集群中一部分节点发生故障后,集群整体能否响应客户端的读写请求
对数据更新具备高可用性
分区容错性
系统如果不能在时限内达成数据的一致性,就意味着发生了分区,必须就当前操作在 C 和 A 之间做出选择
对通信的时限要求
2、两阶段提交协议
在计算机网络及数据库领域内,为了使分布式数据库的所有节点在进行事务提交时都保持一致性而设计的一种算法
算法思路
1、参与者将操作成败通知协调者
2、协调者根据所有参与者的反馈决定各个参与者是提交操作还是终止操作
步骤
1、Prepare 准备阶段
事务协调者给每个参与者都发送 prepare 消息
每个参与者
要么直接返回失败
比如权限验证失败
姚蜜直接本地执行事务,但不提交
写本地的 redo 和 undo 日志
2、Commit 提交阶段
如果协调者接收到了各个参与者的失败消息或者超时,则直接给每个参与者都发送回滚消息,否则就发送提交消息。参与者根据协调者的指令执行提交或回滚,释放在所有事物处理过程中使用的锁资源
缺点
1、同步阻塞问题
执行过程中所有参与者的任务都是同步阻塞的
2、单点故障
所有请求都需要经过协调者,协调者发生故障时,所有参与者都会被阻塞
3、数据不一致
在提交的第二阶段,在协调者向参与者发送 Commit 请求后发生了局部网络异常
发送 Commit 请求过程中协调者发生了故障,导致只有一部分参与者接收到 Commit 请求
4、协调者若机后事务状态丢失
协调者在发出 Commit 消息之后若机,唯一接收到消息的参与者也若机,即使协调者通过选举协议产生了新的协调者,这条事务的状态是不确定的,没人知晓是否是否已提交
流程图
3、三阶段提交协议
二阶段提交协议的改进版本
改进环节
1、引入超时机制
在协调者和参与者中引入了超时机制,如果协调者长时间没有收到参与者反馈,则认为参与者执行失败
2、在第一阶段和第二阶段都加入了预准备阶段,来保证在最后的任务提交之前各参与节点状态都是一致的
步骤
1、CanCommit阶段
协调者向参与者发送 commit 请求,参与者如果可以提交就返回 Yes,否则返回 No
2、PreCommit 阶段
协调者根据参与者的反映来决定是否继续执行
两种情况
1、协调者从参与者中获得的反馈都是 Yes
预执行事务
2、有任意参与者向协调者发送了 NO,或等待超时之后协调者没有收到任意参与者的响应
中断事务
3、DoCommit 阶段
真正事务的提交
协调者发送提交请求
参与者提交事务
参与者响应反馈
协调者确定完成事务
流程图
4、分布式事务
传统事务
ACID
柔性事务
CAP 理论
BASE 理论
CAP 理论的延伸
三个原则
基本可用(Basically Available)
柔性状态(Soft State)
最终一致性(Eventual Consistency)
类型
两阶段型
分布式事务的两阶段提交
技术实现
XA
JTA/JTS
补偿性
TCC(Try、Confirm、Cancel)
基于补偿的事务处理模型
牺牲了一定的隔离性和一致性,提高了事务的可用性
流程图
异步确保型
将一系列同步的事务操作修改为基于消息队列异步执行的操作,来避免分布式事务中同步阻塞带来的数据操作性能下降
流程
流程图
最大努力通知型
通过消息中间件实现
与异步确保型区别
消息经由 MQ 服务器发送到消费者后,允许在达到最大重试次数后正常结束事务,
无法保证数据的最终一致性
牺牲了数据的一致性
金融等对事务高要求的业务中不建议使用
日志记录类对数据一致性要求不高的业务中执行效率高
流程图
八、分布式缓存原理及应用
1、分布式缓存介绍
缓存
定义
将需要频繁访问的数据放在内存中来加快用户访问速度的一种技术
类型
进程级缓存
将数据缓存在服务内部,通过 Map、List 等结构实现存储
分布式缓存
将缓存数据单独存放在分布式系统中,以便于缓存的统一管理和存取
常用分布式缓存系统
Ehcache
Redis
Memcached
2、Ehcache 原理及应用
基于 Java 实现的一套简单、高效、线程安全的缓存管理类库
提供了内存、磁盘文件及分布式存储等多种灵活的 Cache 管理方案
特点
快速
内部采用多线程机制实现,存取性能高
轻量
安装包大小只有 1.6MB,可以快速、方便的集成到系统中
可伸缩
Ehcache 缓存在内存和硬盘的存储可伸缩到几十 GB,
可以轻松应对大数据场景
操作灵活
丰富的 API 接口
支持多种淘汰算法
最近最少被使用
最少被使用
先进先出
支持持久化
原理
基于 Java 实现的高效缓存框架
内部采用多线程实现
使用 LinkedHashMap 存储元素
支持将数据持久化到磁盘上
架构
架构图
角色
Cache Replication
存储缓存副本
Core
Ehcache 的核心部分
CacheManager
管理缓存
Store
存储缓存
CacheAPI
操作缓存
In-Process APIS
封装操作缓存数据的 API
Hibernate API
Servlet Caching Filter API
JMX API
。。。。。
Network APIS
Web API 接口
RESTful API
SOAP API
。。。。。
存储方式
堆存储
将缓存数据存储在 Java 堆内存中
特点
存取速度快
容量有限
堆外存储
基于 NIO 的 DirectByteBuffers 实现,将缓存数据存储在对外内存上
特点
比磁盘存取速度快,且不受 GC 影响,可以保证响应时间的稳定性
在内存分配开销比堆内存大
必须以字节数组方式存储
存
序列化
取
反序列化
数据存取速度比堆内存慢一个数量级
磁盘存储
将数据存储在磁盘上,保障服务重启后内存数据能重新从磁盘加载
特点
读取效率最低
应用
SpringBoot 使用
引入 jar 包
设置 ehcache.xml文件
使用 Ehcache 缓存
3、Redis 原理及应用
4、分布式缓存设计核心问题
1、缓存预热
用户请求数据前,先将数据加载到缓存系统中,用户查询事先被预热的缓存数据,来提高系统查询效率
预热方式
系统启动加载
定时加载
2、缓存更新
数据发生变化后需及时将变化后的数据更新到缓存中
缓存更新策略
定时更新
定时将底层数据库内的数据更新到缓存中
特点
比较简单
适用场景
需要缓存的数据量不是很大的场景
过期更新
定时将缓存中过期的数据更新为最新数据并更新缓存的过期时间
写请求更新
用户有写请求时先写数据库,同时更新缓存
适用于对缓存数据和数据库有实时强一致性要求的情况
读请求更新
用户有读请求时,先判断该请求数据的缓存是否存在或过期,如果不存在或过期,则进行底层数据库查询并将查询结果更新到缓存中,同时将结果返回给用户
3、缓存淘汰策略
FIFO
先进先出
判断被存储的时间,离目前最远的数据优先被淘汰
LRU
最近最少使用
判断缓存最近被使用的时间,距离当前时间最远的数据优先被淘汰
LFU
最不经常使用
在一段时间内,被使用次数最少的缓存优先被淘汰
4、缓存雪崩
同一时刻大量缓存失效,导致大量原本要应该访问缓存的请求都去查询数据库,从而对数据库的 CPU 和内存造成巨大压力,导致数据库宕机,从而形成一系列连锁反应,使整个系统崩溃
处理方案
请求加锁
适用于并发量不是很多的应用
失效更新
为每一个缓存数据都增加过期标记来记录缓存数据是否有效
设置不同的失效时间
为不同的数据设置不同的缓存时效时间,防止同一时刻有大量的数据时效
5、缓存穿透
由于缓存系统故障或者用户频繁查询系统中并不存在的数据,这时,请求会穿过缓存不断的发送到数据库,导致数据库过载,进而引发一连串并发问题
处理方案
布隆过滤器
将所有可能存在的数据都映射到一个足够大的 Bitmap 中,
用户请求首先经过布隆过滤器的拦截
对于不存在的数据一定会被拦截
cache null
核心原理
在缓存中记录一个短暂的数据在系统中是否存在的状态
不存在
直接返回 null,不在查询数据库
6、缓存降级
由于访问量剧增导致服务出现问题,优先保障核心业务的运行,减少或关闭非核心业务对资源的使用
降级策略
写降级
写请求增大时,只进行 Cache 的更新,异步更新数据到数据库中,确保最终一致性
将写请求降级为写 Cache
读降级
在数据库服务负载过高数据数据库系统故障时,只对 Cache 机械能读取并返回结果,在数据库服务正常后再去查询数据库
将读请求降级为读 Cache
九、设计模式
1、设计模式简介
经过高度抽象化的在编程中可以被反复使用的代码设计经验的总结
优势
提高代码质量提升
可读性
可重用性
可靠性
提升系统稳定性、可靠性
有利于外部系统对接
7 个原则
单一职责原则
一个类只有一个职责
开闭原则
软件中的对象(类、模块、函数等)对扩展开放,对修改关闭
一个实体运行在不改变源代码的前提下改变其行为
里氏代换原则
对开闭原则的补充
规定了在任意父类可以出些的地方,子类都一定可以出现
关键
抽象化
依赖倒转原则
程序要依赖于抽象,而不依赖于具体的实现
对抽象编程,不要求对实现编程
降低用户与实现模块间的耦合度
接口隔离原则
将不同的功能定义在不同的接口中来实现接口的隔离
减少接口之间依赖的冗余性和复杂性
合成/聚合原则
在一个新的对象中引入已有的对象来达到类的功能复用和扩展的目的
尽量使用合成或聚合而不要使用继承来扩展累的功能
迪米特原则
一个对象要尽可能少的与其他对象发生相互作用,
降低模块间的耦合度
提高模块的内聚性
三种设计模式类型
创建型模式
提供多种优雅创建对象的方法
所包含的设计模式
工厂模式
抽象工厂模式
单例模式
建造者模式
原型模式
结构型模式
通过类和接口之间的继承和引用来实现创建复杂对象的功能
所包含的设计模式
适配器模式
桥接模式
过滤器模式
组合模式
装饰器模式
外观模式
享元模式
代理模式
行为型模式
通过类之间不同的通信方式实现不同的行为方式
责任链模式
命令模式
解释器模式
迭代器模式
中介者模式
备忘录模式
观察者模式
状态模式
策略模式
模板模式
访问者模式
2、工厂模式
3、抽象工厂模式
4、单例模式
5、建造者模式
6、原型模式
7、适配器模式
8、装饰器模式
9、代理模式
10、外观模式
11、桥接模式
12、组合模式
13、享元模式
14、策略模式
15、模板方法模式
16、观察者模式
17、迭代器模式
18、责任链模式
19、命令模式
20、备忘录模式
21、状态模式
22、访问者模式
23、中介者模式
24、解释器模式
收藏
收藏
0 条评论
下一页