##线程相关

Thread

  1. start() : 启动线程
  2. run() : 通过start()之后,cpu获得了线程执行时间,去调用run();
  3. sleep() : 线程等待指定时间,释放cpu资源,不释放锁
  4. yield() : 释放当前cpu资源,线程回到就绪状态, 不释放锁,让相同优先级线程执行
  5. join() : 保持当前线程在其他线程结束时才能执行
  6. interrupt() : 中断线程
  7. wait():从object()继承来,进入等待池,释放锁
  8. notify():从object()继承来,唤醒一个线程

线程的状态

  • 新建:使用new关键字或者使用Thread类或者其子类创建线程,线程会保持新建状态,直到程序start()这个线程

  • 就绪:当线程执行start()方法后,线程进入就绪状态并在等待队列中等待调度

  • 运行:就绪的线程获取cpu资源之后,就会调用run()方法,线程进入运行状态。它可以变成就绪、阻塞、死亡状态

  • 阻塞:

    • 等待阻塞:运行时执行了wait()方法,使线程进入等待阻塞状态
    • 同步阻塞:线程获取锁失败时进入同步阻塞
    • 其他阻塞:通过执行sleep()或join()发出IO请求时
  • 死亡:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态

线程池

线程池的创建方式:5种
线程池的状态

**设置线程池:**跟cpu核心数,阻塞系数,io时间有关

公式:启动线程数 = 【任务执行时间/(任务执行时间-IO等待时间)】*CPU内核数

最佳线程数量 = CPU执行核心数/(1-阻塞系数)

阻塞系数 = 阻塞时间/(阻塞时间+使用CPU的时间)

  1. 线程池参数:
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

  1. 初始化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;
    }
  1. 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;
                    }
                }
            }
        }
  1. 当map扩容时,调度其他所有线程帮助扩容数据迁移,每个线程领取大小为6的节点进行数据迁移

与HashTable的区别

ConcurrentHashMap采用的是分段锁,对不同节点的数据不会有影响,是并行化操作。HashTable是在每个操作方法上都加上synchronized是串行操作,效率低。

db相关

mysql

  1. ACID:
	(1)  原子性:以单个事务为最小单元,要么全部成功,要么全部失败
	(2)  一致性:事务总是从一个一致性状态转向另一个一致性状态
	(3)  隔离性:一个事物在提交前,对其他事物是不可见的
		a.读已提交:事物可以读取未提交的数据。
		b.读已提交:一个事物在提交前所做任何操作对其他事物不可见。
		c.可重复读:同一个事务读取的结果是一致的。
			(select 通过读取事物id小于等于当前事务id,如果大于,去undolog查询快	照信息解决不可重复读,update上锁,会更新版本号,属于当前读。可能会产生幻读,当一个事物提交前查询和这个事物提交后查询会出现幻读)
  	d.可串行化:强制事务串行化。
  	
  	a.脏读:读取到其他事务未提交的操作结果
 		b.不可重复读:多次读取结果不一致
 		c.幻读:两次查询,第二次查询的结果出现了包括第一次查询所没有的结果
 	(4) 持久性: 持久化
隔离级别脏读不可重复读幻读
RUyyy
RCnyy
RRnny
serialnnn

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相关

内存模型

  • 方法区:
    • 常量池:
  • 堆:
  • 虚拟机栈:
  • 本地方法栈:
  • 计数器
  1. 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

优点

  1. 内嵌tomcat,可独立部署jar包
  2. 简化配置,可自动装配

缺点

  1. 集成度过高,难以理解源码

简化配置

spring加载:spring 会在初始化的时候加载META-INF下的spring.factory文件获取资源,然后通过properties加载资源

Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作。以前我们需要自己配置的东西,自动配置类都帮我们完成了。

Spring Cloud

Eureka

  1. 服务注册:服务提供者将自身注册到服务中心

  2. 服务续约:eureka将每30s请求一次心跳,来进行续约,若90s无续约将会删除服务信息

  3. 服务剔除:当个别节点90s内无心跳,将会将该服务注册列表删除

  4. 服务下线:程序在关闭时会向注册中心发送请求,eureka收到请求后会将注册信息删除

  5. 自我保护:当出现大批量心跳失败时,15分钟内心跳成功率在85%以内,将会进入自我保护机制

    1)eureka不再从注册列表移除长时间没收到心跳的服务信息

    2)eureka仍然会接受新服务的注册和查询的请求,但是不回被同步到其他节点上(保证当前节点依然可用)

    3)当网络稳定时,当前实例新的注册信息会被同步到其他节点中

  6. Eureka分区:eureka提供了Region和Zone两个概念进行分区,用一个区的请求会优先调用本区的请求

    1)Region: 可以理解成地理区域

    1. Zone: 可以理解成机房区域

多级缓存

拉取注册信息时,判断是否开启了多级缓存,如果开启了从只读缓存取数据,如果没开启,从读写缓存取。默认每30s从读写缓存同步信息到只读缓存,每60s清空超过90s未续约的节点,服务每30s从注册中心拉取一次注册信息。

  1. 只读缓存(ConcurrentMap):如果只读缓存为空再去查询读写缓存
  2. 读写缓存(Guava):定时30s将信息同步到只读缓存,如果还是没有数据从registry取
  3. 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

雪花算法

雪花算法:由首位无效符 + 时间戳(毫秒)+ 机器号 + 序列号

优点:

  1. 高性能高可用:在内存中生成,不依赖数据库
  2. 容量大:每秒能生成几百万个id
  3. id自增:由于引入时间戳,id自增,作为mysql索引能增加查询效率

缺点:依赖于时间一致性,如果时间回调,将会产生重复id

类加载

双亲委派加载

BootStrapClassLoad(启动类加载器): 加载核心类库

ExtensionClassLoad(标准扩展器加载器): 加载扩展类

ApplicationClassLoad(应用程序类加载器): 加载当前应用classPatch下所有目录

CustomClassLoader(用户自定义加载器):

好处:

  1. 可避免重复加载,如果父类已经加载,子类不需要重新加载
  2. 更安全,很好的解决了加载器加载基础类的统一问题,核心类不由用户去自定义加载

CAP

一致性,可用性,容错性

Q.E.D.