- Thread
- 线程的状态
- 线程池
- Callable 和 FutureTask
- 面试题: 限制访问频率,当前时间往前推5分钟的次数
- ConcurrentHashMap
- db相关
- Spring
- Spring boot
- Spring Cloud
- 分布式id
- 类加载
- CAP
##线程相关
Thread
- start() : 启动线程
- run() : 通过start()之后,cpu获得了线程执行时间,去调用run();
- sleep() : 线程等待指定时间,释放cpu资源,不释放锁
- yield() : 释放当前cpu资源,线程回到就绪状态, 不释放锁,让相同优先级线程执行
- join() : 保持当前线程在其他线程结束时才能执行
- interrupt() : 中断线程
- wait():从object()继承来,进入等待池,释放锁
- notify():从object()继承来,唤醒一个线程
线程的状态
-
新建:使用new关键字或者使用Thread类或者其子类创建线程,线程会保持新建状态,直到程序start()这个线程
-
就绪:当线程执行start()方法后,线程进入就绪状态并在等待队列中等待调度
-
运行:就绪的线程获取cpu资源之后,就会调用run()方法,线程进入运行状态。它可以变成就绪、阻塞、死亡状态
-
阻塞:
- 等待阻塞:运行时执行了wait()方法,使线程进入等待阻塞状态
- 同步阻塞:线程获取锁失败时进入同步阻塞
- 其他阻塞:通过执行sleep()或join()发出IO请求时
-
死亡:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态
线程池
线程池的创建方式:5种
线程池的状态
**设置线程池:**跟cpu核心数,阻塞系数,io时间有关
公式:启动线程数 = 【任务执行时间/(任务执行时间-IO等待时间)】*CPU内核数
最佳线程数量 = CPU执行核心数/(1-阻塞系数)
阻塞系数 = 阻塞时间/(阻塞时间+使用CPU的时间)
- 线程池参数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- corePoolSize: 核心线程数:线程会维护一个最小核心线程数,即使线程处于闲置状态,也不会被销毁,除非设置allowCoreThreadTimeOut。如果当前线程数小于核心线程数,则会创建新的线程来执行任务。
- maximumPoolSize:线程池最大线程数量:线程未达到最大线程数量,工作队列没有满,则会等到工作队列满,创建新的线程去执行队列任务,如果线程达到最大线程数,并且队列已满,会抛出异常。
- keepAliveTime:线程空闲时间:允许线程闲置的最大空闲时间,如果超过这个时间会被销毁。
- unit:keepAliveTime的时间单位。
- workQueue:工作队列。
- threadFactory:创建这个线程使用的工厂,可以用来设置线程名称,是否是守护线程等。
- handler:当工作队列满了并且最大线程达到上限,拒绝后续任务的策略。
Callable 和 FutureTask
实现callable接口创建线程的方式,可以配合future获取线程的返回值,以及是否执行完成等信息。
- FutureTask提供的get(): 该方法能够获取线程的返回值,如果给定时间无法获取结果,会抛出TimeoutException异常
- FutureTask提供的isDone(): 该方法能够判断该线程是否执行完成
面试题: 限制访问频率,当前时间往前推5分钟的次数
ConcurrentHashMap
- 初始化map通过cas操作
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
- put操作对节点上锁
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (ConcurrentHashMap.Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
ConcurrentHashMap.Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new ConcurrentHashMap.Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof ConcurrentHashMap.TreeBin) {
ConcurrentHashMap.Node<K,V> p;
binCount = 2;
if ((p = ((ConcurrentHashMap.TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
- 当map扩容时,调度其他所有线程帮助扩容数据迁移,每个线程领取大小为6的节点进行数据迁移
与HashTable的区别
ConcurrentHashMap采用的是分段锁,对不同节点的数据不会有影响,是并行化操作。HashTable是在每个操作方法上都加上synchronized是串行操作,效率低。
db相关
mysql
- ACID:
(1) 原子性:以单个事务为最小单元,要么全部成功,要么全部失败
(2) 一致性:事务总是从一个一致性状态转向另一个一致性状态
(3) 隔离性:一个事物在提交前,对其他事物是不可见的
a.读已提交:事物可以读取未提交的数据。
b.读已提交:一个事物在提交前所做任何操作对其他事物不可见。
c.可重复读:同一个事务读取的结果是一致的。
(select 通过读取事物id小于等于当前事务id,如果大于,去undolog查询快 照信息解决不可重复读,update上锁,会更新版本号,属于当前读。可能会产生幻读,当一个事物提交前查询和这个事物提交后查询会出现幻读)
d.可串行化:强制事务串行化。
a.脏读:读取到其他事务未提交的操作结果
b.不可重复读:多次读取结果不一致
c.幻读:两次查询,第二次查询的结果出现了包括第一次查询所没有的结果
(4) 持久性: 持久化
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
RU | y | y | y |
RC | n | y | y |
RR | n | n | y |
serial | n | n | n |
mysql数据类型
- 整数类型:tinyint, smallint,mdeiumint, int, bigint,整数类型有unsigned属性,不允许为负值
- 实数类型:
- 字符串类型:
- Char: 类型是定长的,根据定义的字符串长度来分配空间,在存储char值时,mysql会删除末尾的空格,char适合存储很短或者定长的数据
- Varchar: 用于存储可变长字符串,是最常见的字符串数据类型。它比定长类型更节省空间,因为它仅使用必要的空间(越短的字符串使用越少的空间)
索引
普通索引,主键索引,唯一索引,联合索引
聚簇索引与非聚簇索引
聚簇索引(二级索引):索引结构与数据文件在一块,例如主键索引就是聚簇索引,叶子节点的主键值下是整行数据。
非聚簇索引(一级索引):索引结构与数据文件分开,例如联合索引,叶子结点下是该索引值的行对应的主键id,
通过非聚簇索引查询会通过主键索引得到数据,这个过程称作为回表。
一个表只能有1个聚簇索引,因为聚簇索引的顺序就是物理数据的顺序,所以一个表只能有一个聚簇索引
索引失效
- 最左原则
- 使用or可能索引失效
- 使用like查询,%在前缀(类似最左原则)
- 字段类型转换可能会导致失效,例如:字符串不加引号会导致索引失效
- 对索引字段进行条件操作
- is null 和is not null在mysql在查询成本非常高时会不走索引
最左原则
Mysql会从按照从左到右进行匹配,直到遇到范围查询
Mysql的执行过程
查询过程:先去查缓存,若直接命中缓存,则返回数据(这条链路前提的缓存开启),若没命中缓存,则由server端进行SQL解析,预处理,再优化,最后根据优化器生成的执行计划,调用存储引擎的api去查询数据,最后返回
修改过程:先去查数据,查询到数据之后执行器执行操作,形成新的一行数据,再调用存储引擎的api将数据更新到内存当中,存储引擎(innoDB)去写redo log,undo log,此时状态标记成perpare,并告诉执行器可随时提交,执行器写bin log,写完后将状态改成commit提交事务。
NOW()和 CURRENT_DATE()有什么区别?
now(): 返回的是当前的日期,有年月日时分秒
Current_date(): 返回的是当前的日期,只有年月日
redis
redis为什么那么快
redis虽然是单线程工作,但是性能瓶颈在io,抛去io性能瓶颈,单线程省去了线程上下文切换、线程锁抢占等问题,cpu利用率比多线程高,redis数据结构简单,数据存储在内存当中,查询效率高。在redis 6.x版本中,采用多个io线程,单个work线程的模型,从io瓶颈问题上提升redis的效率
缓存淘汰
当redis内存超出物理内存限制时,内存的数据会和磁盘数据产生频繁的交换,会让redis性能急剧下降。对于访问频繁的redis来说,这样的存取效率基本上等于不可用,为了限制最大使用内存,redis提供了一些缓存淘汰方案:
-
Noeviction:默认的淘汰策略,不会继续处理写请求。
-
Volatile-lru: 尝试淘汰设置了过期时间的key,使用最少的key优先被淘汰。没有设置过期时间的key不会被淘汰,这样可以保证需要持久化的数据不会被淘汰
-
Volatile-til:尝试淘汰设置了过期时间的key,key的剩余寿命越小优先被淘汰
-
Volatile-random: 随机淘汰设置了过期时间的key
-
Allkeys-lru: 使用最少的key会被优先淘汰
-
allkeys-random: 随机淘汰key
##JVM相关
内存模型
- 方法区:
- 常量池:
- 堆:
- 虚拟机栈:
- 本地方法栈:
- 计数器
- String 是final类型,不可变,不可继承,被赋予新值的过程是创建同样的一个对象,将新对象赋值,旧对象GC回收
GC
GC ROOT:栈引用,常量池,静态变量,JNI引用
Spring
IOC 和 DI
IOC是控制反转:应用本身不负责对象的创建与维护,创建与维护的工作交给spring容器去进行管理
DI依赖注入:程序在运行期间,由外部容器,动态的将依赖对象注入到容器当中,可以提升组件重用的频率
Spring bean的生命周期
spring的生命周期有4个阶段,其中每个阶段会穿插扩展点
-
实例化 createBeanInstance()
- InstantiationAwareBeanPostProcessor:作用于实例化的前后
InstantiationAwareBeanPostProcessor extends BeanPostProcessor
-
属性赋值 populateBean()
-
初始化 Initialization
- BeanPostProcessor:作用于初始化的前后
- 在初始化之前调用Aware接口
-
销毁 Destruction
Spring 循环依赖的问题
一个对象的创建过程分为两步:
1. 实例化对象
2. 实例化对象属性
- Spring 实例化bean的时候是分两步进行,先实例化目标bean,再去注入属性
- Spring 通过递归的方式获取目标bean以及目标属性
例如:A拥有B成员变量属性,B拥有A成员变量属性,在实例化A的时候,Spring会先从Spring容器中获取A的实例,如果没有,实例化A,将A放入Spring容器当中(此时A还没有实例化属性)。然后Spring会去实例化A的属性,当从Spring容器中获取B实例,没有时就会实例化B,并放入容器当中,再实例化B的属性,B的属性为A,会从容器中取出A设置到B的属性上。成功实例化B之后,再将B设置到A上。整个过程就完成了,解决了循环依赖问题。
AOP
AOP的原理
aop使用动态代理实现,在spring bean初始化的的阶段通过BeanPostProcessor方法使用动态代理在内存中临时生成一个aop对象增强存储在spring容器当中,该aop对象拥有对象的所有方法。
aop的动态代理使用JDK动态代理和CGLib动态代理来实现的。
JDK动态代理通过反射实现代理,需要实现一个接口InvocationHandler
CGLib通过字节码来实现代理,CGLib是通过继承来实现代理,如果某个类被声明称final,将不会被代理。AOP代理如果没有实现接口,将默认会使用CGLib进行代理
Spring boot
优点
- 内嵌tomcat,可独立部署jar包
- 简化配置,可自动装配
缺点
- 集成度过高,难以理解源码
简化配置
spring加载:spring 会在初始化的时候加载META-INF下的spring.factory文件获取资源,然后通过properties加载资源
Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作。以前我们需要自己配置的东西,自动配置类都帮我们完成了。
Spring Cloud
Eureka
-
服务注册:服务提供者将自身注册到服务中心
-
服务续约:eureka将每30s请求一次心跳,来进行续约,若90s无续约将会删除服务信息
-
服务剔除:当个别节点90s内无心跳,将会将该服务注册列表删除
-
服务下线:程序在关闭时会向注册中心发送请求,eureka收到请求后会将注册信息删除
-
自我保护:当出现大批量心跳失败时,15分钟内心跳成功率在85%以内,将会进入自我保护机制
1)eureka不再从注册列表移除长时间没收到心跳的服务信息
2)eureka仍然会接受新服务的注册和查询的请求,但是不回被同步到其他节点上(保证当前节点依然可用)
3)当网络稳定时,当前实例新的注册信息会被同步到其他节点中
-
Eureka分区:eureka提供了Region和Zone两个概念进行分区,用一个区的请求会优先调用本区的请求
1)Region: 可以理解成地理区域
- Zone: 可以理解成机房区域
多级缓存
拉取注册信息时,判断是否开启了多级缓存,如果开启了从只读缓存取数据,如果没开启,从读写缓存取。默认每30s从读写缓存同步信息到只读缓存,每60s清空超过90s未续约的节点,服务每30s从注册中心拉取一次注册信息。
- 只读缓存(ConcurrentMap):如果只读缓存为空再去查询读写缓存
- 读写缓存(Guava):定时30s将信息同步到只读缓存,如果还是没有数据从registry取
- Registry(ConcurrentMap):异步同步多节点注册信息,当节点信息失效,将会清空二级缓存信息并同步更新二级缓存
/**
* Get the payload in both compressed and uncompressed form.
*/
@VisibleForTesting
Value getValue(final Key key, boolean useReadOnlyCache) {
Value payload = null;
try {
if (useReadOnlyCache) {
final Value currentPayload = readOnlyCacheMap.get(key);
if (currentPayload != null) {
payload = currentPayload;
} else {
payload = readWriteCacheMap.get(key);
readOnlyCacheMap.put(key, payload);
}
} else {
payload = readWriteCacheMap.get(key);
}
} catch (Throwable t) {
logger.error("Cannot get value for key : {}", key, t);
}
return payload;
}
注册列表的原理:双层map
//外层的key是服务名称,里层key是实例名称,Lease<InstanceInfo>服务信息
ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>
分布式id
雪花算法
雪花算法:由首位无效符 + 时间戳(毫秒)+ 机器号 + 序列号
优点:
- 高性能高可用:在内存中生成,不依赖数据库
- 容量大:每秒能生成几百万个id
- id自增:由于引入时间戳,id自增,作为mysql索引能增加查询效率
缺点:依赖于时间一致性,如果时间回调,将会产生重复id
类加载
双亲委派加载
BootStrapClassLoad(启动类加载器): 加载核心类库
ExtensionClassLoad(标准扩展器加载器): 加载扩展类
ApplicationClassLoad(应用程序类加载器): 加载当前应用classPatch下所有目录
CustomClassLoader(用户自定义加载器):
好处:
- 可避免重复加载,如果父类已经加载,子类不需要重新加载
- 更安全,很好的解决了加载器加载基础类的统一问题,核心类不由用户去自定义加载
CAP
一致性,可用性,容错性
Q.E.D.