1、线程池7个配置属性
1.1、corePoolSize核心线程数
当有线程任务提交时,若线程池内没有空闲的核心线程,且核心线程数量未达到峰值,则新建一个核心线程,若存在空闲的核心线程,则直接由该线程去执行任务。当核心线程任务完成,该线程不会被销毁,而是进入空闲状态,等待下一个任务。
线程复用,根据源码分析,当核心线程没有任务时,进入循环状态,不断去执行空任务,直到任务提交到该线程,则去执行提交的任务。
1.2、maximumPoolSize最大线程数
最大线程数和核心线程数决定了非核心线程数的大小(即maximumPoolSize-corePoolSize=非核心)。只有在核心线程已满且无空闲核心线程,且等待队列也满,则新建一个非核心线程去处理新提交的任务。非核心线程不会复用,执行完任务后,直到达到存活时间,则该线程被销毁
1.3、keepAliveTime空闲线程存活时间
当线程数大于corePoolSize但小于maximumPoolSize时,只要有空闲线程,线程池则根据keepAliveTime去销毁多出corePoolSize个数的空闲线程。
1.4、unit时间单位
keepAliveTime过期时间的时间单位,默认是毫秒级
1.5、workQueue阻塞队列
当corePoolSize满了后,新提交的任务会先进入阻塞队列,等待空闲的核心线程执行任务
1.6、threadFactory线程工厂
生产线程来执行任务,可自定义县城工厂模式
1.6.1、newCachedThreadPool可缓存的线程池
1、该线程池的核心线程数量是0,线程的数量最高可以达到Integer 类型最大值;
2、创建ThreadPoolExecutor实例时传过去的参数是一个SynchronousQueue实例,说明在创建任务时,若存在空闲线程就复用它,没有的话再新建线程。
3、线程处于闲置状态超过60s的话,就会被销毁。
1.6.2、newFixedThreadPool定长线程池
1、线程池的最大线程数等于核心线程数,并且线程池的线程不会因为闲置超时被销毁。
2、使用的列队是LinkedBlockingQueue,表示如果当前线程数小于核心线程数,那么即使有空闲线程也不会复用线程去执行任务,而是创建新的线程去执行任务。如果当前执行任务数量大于核心线程数,此时再提交任务就在队列中等待,直到有可用线程。
1.6.3、newSingleThreadExecutor单线程线程池
该线程池基本就是只有一个线程数的newFixedThreadPool,它只有一个线程在工作,所有任务按照指定顺序执行。
1.6.4、newScheduledThreadPool支持定时的定长线程池
与newCachedThreadPool相似,但支持延迟执行及定时执行任务
1.7、handler拒绝策略
当workQueue已满且maximumPoolSize已达最大值时,执行设置的拒绝策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。(默认) ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务 ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
2、redis持久化机制
2.1、RDB(Redis DataBase快照,默认)
产生文件为dump.rdb,以一定时间将内存数据以快照的方式保存到硬盘。
优点:
1、方便持久化
2、容灾性好
3、性能最大化:fork子进程来完成写操作,主进程继续处理命令,IO最大化,主进程不会有任何IO操作
4、数据集大时,启动效率更高
缺点:
1、数据安全性低:RDB是隔一段时间进行持久化。如果在这期间发生故障,会丢失数据
2.2、AOF(Append Only File持久化)
将redis每次操作记录写到日志文件中,当redis重启时,会优先执行日志文件中的操作记录恢复数据。
优点:
1、数据安全
缺点:
1、AOF文件大,恢复速度慢
2、启动效率低
2.2.1、三种策略
always:每条写指令都同步到硬盘
everysec:每秒执行一次同步
no:由操作系统决定同步时间
2.2.2、AOF重写
由于AOF会随着redis的运行不断增大,为解决AOF文件过大问题,redis会对AOF中的指令进行重写以减小文件大小。
3、HashMap
3.1、数据结构
数组+链表+红黑树
3.2、put过程
1、判断数组是否为空,为空则初始化
2、不为空,计算hash值,计算数组下标Index
3、查看index下是否有数据,没有数据则构建一个node节点放在index中
4、存在数据,则发生hash冲突,判断key值是否相等,相等则替换
5、不相等,判断节点是否为树结构,是的话构建树节点插入红黑树,不是的话创建普通节点,用尾插法插入链表
6、判断链表长度,大于8则转为红黑树
7、判断节点数是否大于阈值,大于则扩容为原数组两倍
3.3、hash函数(扰动函数)
先拿到key的hashcode,32位int值,然后将高16位和低16位进行异或操作
3.4、jdk1.8的优化
1、数组+链表优化为数组+链表+红黑树
2、头插法改为尾插法(头插法扩容时会反转链表,多线程下可能成环)
3、扩容时1.7需要重新计算hash,1.8采用逻辑判断,位置不变或索引+旧容量大小
4、插入时,1.7先判断扩容再插入,1.8先插入后判断扩容
3.5、ConcurrentHashMap分段锁
jdk1.8之前是采用数组+Segment的实现,Segment是类似hashMap的结构,同时也是一个ReentrantLock。
jdk1.8采用synchronized+CAS,synchronized锁住的是数组下的链表的第一个节点。
4、synchronized锁原理
1.6之前单纯是重量级锁,1.6之后加入了偏向锁、轻量级锁和重量级锁,采用锁升级原理
4.1、对象头
在hotspot虚拟机中,对象分为三块区域:对象头、实例数据和对齐填充。synchronized是根据对象头实现的,对象头记录了对象的锁状态。
4.2、monitor
可理解为同步工具,java中所有的对象都是monitor
4.3、锁类型
synchronized通过多种锁实现加锁逻辑
4.3.1、乐观锁
读数据的时候不会上锁,写数据的时候先读出当前版本号,然后进行加锁操作,如果发现版本号不一致,则失败并重复操作。
如CAS,也叫自旋锁
4.3.2、悲观锁
每次读写操作都会加锁,其他线程要读写这个对象,则需要等待获取锁。
如synchronized
AQS框架的锁是先尝试CAS乐观锁,如果获取不到则转为悲观锁,如retreenLock
4.3.3、偏向锁
大多数情况下,锁竞争并不激烈,获取到锁的线程总是同一个,于是引入偏向锁。
当一个线程获取锁时,会在对象头和栈帧中的锁记录里,存储偏向锁的线程ID,之后线程在加解锁都不需要进行CAS。
4.3.4、轻量级锁
当线程2尝试获取锁,锁会检测当前持有锁的线程1是否存活,如果不存活则线程2加锁(轻量级锁)成功,如果线程1存活,则暂停线程1并升级锁为轻量级锁。
轻量级锁会在线程1的栈帧中建立一个锁记录空间,存储锁对象当前的Mark Work,此时JVM会采用CAS尝试将Mark Work更新为指向栈帧中锁记录的空间指针,并把锁的标志位置为00(轻量级锁标志),此时线程2会采用CAS多次尝试获取锁(即修改Mark Work)。
4.3.5、重量级锁
线程2多次CAS获取锁失败后,锁升级为重量级锁。
重量级锁通过对象内部的监视器(monitor)实现,所有获取锁失败的线程都会进行阻塞,阻塞的线程不消耗cpu。
5、RetreenLock原理
CAS+AQS,公平锁
AQS是一个FIFO(先入先出)的同步队列
支持独占锁和共享锁
维护一个state,记录锁状态和资源数量
线程首先采用CAS尝试获取锁(设置state为1),如果失败则加入同步队列,并进入等待状态,等待锁释放。如果同步队列中获取锁的不是第一个节点,则会唤醒第一个节点的线程获取锁,以此实现公平锁。
6、redis相关
String、List、Set、Zset、Hash
6.1、为什么redis快
1、完全基于内存
2、数据结构简单
3、采用单线程,避免上下文切换带来的损耗,无需考虑多线程引起的加解锁
4、使用多路IO复用模型,非阻塞IO
6.2、持久化策略(见第2大点)
6.3、缓存雪崩
6.3.1、原因
大量缓存集中在一个时间段失效,给系统带来很大压力
6.3.2、解决
设置随机过期时间
6.4、缓存穿透
6.4.1、原因
针对一个缓存和数据库都不存在的值进行不断地访问
6.4.2、解决
1、设置空值缓存
2、布隆过滤器
6.5、缓存击穿
6.5.1、原因
数据库存在但缓存过期,此时大量请求到来
6.5.2、解决
互斥锁,加锁成功才从数据库获取,失败则直接从缓存获取
6.6、过期策略
默认同时采用了惰性删除和定期删除
6.6.1、定时过期
每个设置过期时间的key都要创建一个定时器,到过期时间会立即删除。会占用大量的cpu资源去处理过期数据。
6.6.2、惰性删除
只有当访问一个key时,才会去判断key是否过期,过期则删除。到达过期时间但没有进行访问的key会占用大量内存。
6.6.3、定期删除
每隔一段时间,会从设置了过期时间的key中随机抽取一部分进行过期处理
6.7、淘汰策略
内存不足时会执行淘汰策略
1、noeviction:直接报错
2、allkeys-lru:在键空间中,移除最近最少使用的key(最常用)
3、allkeys-random:在键空间中,随机移除
4、volatile-lru:在设置了过期时间的键空间中,移除最近最少使用的key
5、volatile-random:在设置了过期时间的键空间中,随机移除key
6、volatile-ttl:在设置了过期时间的键空间中,有更早过期时间的key优先移除
7、三握四挥
7.1、三握
客户端(C)和服务端(S)开始都处于CLOSED状态
S首先创建TCB(传输控制块),进入LISTEN状态,等待客户端连接
第一次握手:
C的TCP进程创建TCB,然后向S发出连接请求报文段。段首同步位SYN=1,初始序列号seq=x,此时C进入SYN-SENT状态。
第二次握手:
S收到连接请求报文段,如果同意建立连接,则向C发送确认报文段。报文段中SYN=1,确认位ACK=1、确认号ack=x+1,初始序列号seq=y,S进入SYN-RCVID状态。
第三次握手:
C收到S的确认报文段后,再向S发送确认报文段。报文段ACK=1、确认号ack=y+1。C进入ESTAB-LISHED状态。当S收到C的确认报文段后,也进入该状态,连接建立。
7.1.1、为什么不是两次握手
两次握手会发生如下情况:
当C发出第一次握手,但由于网络延迟等因素,导致本已超时的连接请求发送到了S,S接收到请求后,并无法感知该连接时无效的超时连接,于是向C发送了连接确认并建立连接,C收到S的连接确认后无法处理该超时连接,于是该无效连接将占用资源。
7.2、四挥
ESTAB-LISHED:已建立连接
FIN-WAIT-1:终止等待1
CLOSE-WAIT:关闭等待
FIN-WAIT-2:终止等待2
LAST-ACK:最后确认
TIME-WAIT:时间等待
CLOSED:关闭
第一次挥手:
C发送连接释放报文段,首部终止控制位FIN=1,序号seq=u,C进入FIN-WAIT-1状态,等待S确认。
第二次挥手:
S收到C的连接释放报文段后,立刻发出确认报文段,确认号ack=u+1、序号seq=v,S进入CLOSE-WAIT状态
(此时,C到S这个方向的连接就断开了,TCP处于半关闭状态,但S到C的连接还没有断开,B可以继续向C发送数据。)
第三次挥手:
C收到S的确认报文段后,进入FIN-WAIT-2状态,继续等待S发送连接释放报文段。当S没有其他数据要发送时,会发送连接释放报文段到C,报文段终止控制位FIN=1,序号seq=w,确认号ack=u+1,S进入LAST-ACK状态,等待C的确认报文段。
第四次挥手:
C收到S的连接释放报文段并发送确认报文段,ACK=1,确认信号ack=w+1,序号seq=u+1,C进入TIME-WAIT状态,S收到确认报文段后进入CLOSED状态,C等待一段时间后进入CLOSED状态。
7.2.1、为什么第四次挥手,C要等待一段时间后才关闭
保证最后的确认报文段能发送到S。
保证该连接滞留的报文段从网络中消失。
8、虚拟机内存结构
8.1、线程私有
本地方法栈:登记本地方法,执行引擎加载时加载本地方法(jna方法存放于此)
程序计数器:记录指向下一条指令的地址
java栈(虚拟机栈):一个线程对应一个栈帧,每执行一个方法都会建立一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等
8.2、线程共享
方法区:静态变量,常量,方法信息,运行时常量
堆:对象实例,GC主要位置
8.2.1、堆的结构详解
堆被划分为三大模块:永久代、老年代和新生代
新生代:新生代又分为Eden、survival from 、survival to。新创建的对象都会首先在Eden区,survival from 和survival to并没有明显的界限,两块区域在经历一次young GC后,总会有一块是空的,一块是有数据的。在经历young GC后,Eden区存活的对象,会复制到survival from/to中(取决于哪块区域是空的),并标记其存活标志位为1,survival from/to中(取决于哪块区域是有数据的)存活的对象,会复制到另一块区域,并标记其存货标记位为2,直到下一次young GC,存活标志位为2的对象如果依然存活,则转移到老年代。简单来说,新生代中如果经历三次young GC依然存活,那么就会被转移到老年代。
老年代:存放由新生代晋升过来的对象。该区域满的时候也会进行GC,频率正常来讲会远低于young GC。
永久代:举例来说,spring创建的bean都被存放于永久代。这里涉及bean的创建方式,一种是声明bean之后直接创建该实例,第二种是当调用时再创建对应实例。相比较来看,第二种会对永久代的空间利用更好。永久代是触发full GC的主要区域,一般来说,尽可能不要触发full GC或者降低full GC的频率,因为full GC会暂停程序所有线程,在GC完成后重新启动线程,这样CPU线程调度带来的消耗将是灾难性的。
9、数据库隔离级别
9.1、并发下出现的问题
脏读:事务读取到另一个事务未提交的数据。
不可重复读:同一个事务的多次查询操作,会读取到不同的数据(针对update和delete)。
幻读:同一个事务的多次查询操作,查询结果行数不一致(针对insert操作)
9.2、解决
读未提交:以上三种情况都会出现(修改操作加行锁)
读已提交:解决脏读,写事务未提交时,读事务不会重复读取到未提交的修改;依然会出现不可重复读和幻读(读操作加行级共享锁,读完立即释放;写操作加行级排他锁,事务结束后释放)
可重复读:通过加行锁解决不可重复读,会出现幻读(读操作加行级共享锁,事务结束后才释放;写操作加行级排他锁,事务结束后释放)
串行化:事务串行执行
10、计算机网络
10.1、网络七层模型
应用层(HTTP、FTTP等):客户端应用程序
表示层:数据的加密解密
会话层:负责在网络中的两节点之间建立、维持和终止通信。
(在TCP/IP五层模型中,以上三层称为应用层)
传输层(TCP、UDP等):定义传输数据的协议端口号,以及流控和差错校验
网络层(IP):将网络地址翻译成对应的物理地址,并决定如何将数据从发送方路由到接收方
链路层(数据帧):物理地址寻址、数据的成帧、流量控制、数据的检错、重发
物理层:例如接上网卡,就实现了一整套的网络模型
10.2、浏览器输入网址后发生什么
①DNS解析:主机向本地域名服务器发送消息,采用递归形式不断向后面的服务器查询。已访问过的网站,本地域名服务器会有保存,下次访问会比较快;
②封装HTTP请求:封装请求行,请求头及请求正文;
③TCP连接:已得到ip地址在应用层,应用层通过socket与传输层建立连接(采用TCP/IP),打包到网络层的ip数据包,在链路层转为数据帧,再到物理层利用比特流形式进行数据传输;
④服务器永久重定向响应:将对不同名称统一网络地址的网址重定向为统一的网址,避免因输入的名称不同而导致产生多次请求;
⑤浏览器跟踪重定向地址:重复第三步,客户端再次发送HTTP请求;
⑥服务器处理请求:
⑦服务器返回HTTP响应:
⑧浏览器显示HTML
⑨浏览器发送获取嵌入HTML中的资源