HashMap的put过程
1.根据要添加的键的哈希码计算在数组中的位置
2.检查该位置是否为空(即没有键值对存在)
3.如果为空,则直接在该位置创建一个新的Entry对象来存储键值对
4.如果该位置已经存在其他键值对,检查该位置的第一个键值对的哈希码和键是否与要添加的键值对相同
5.如果第一个键值对的哈希码和键不相同,则需要遍历链表或红黑树来查找是否有相同的键
6.检查链表长度是否达到阈值 默认为8 如果链表长度超过阈值,且HashMap的数组长度大于等于64,则会将链表转换为红黑树,以提高查询效率
7.检查负载因子是否超过阈值 默认为0.75
8.扩容操作,创建一个新的两倍大小的数组,将旧数组的键值对重新计算哈希码并分配到新数组中的位置,更新HashMap的数组引用和阈值参数
红黑树的数据结构
自平衡的二叉查找树,每个节点要么是黑色,要么是红色。根节点是黑色,每个叶子节点是黑色,如果一个节点是红色,则其子节点必须是黑色,从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点
ConcurrentHashMap
CAS+synchronized来实现线程安全的,如果根据存储的元素计算结果为空,则利用CAS设置该节点,如果根据存储的元素计算结果不为空,则使用synchronized,然后遍历桶中的数据,并替换或新增节点到桶中,最后判断是否需要转为红黑树,保证并发访问时的线程安全了
索引失效场景
(1)使用like %xx 或者 like %xx% 造成索引失效
(2)对索引使用函数 length(name)
(3) 对索引进行表达式求值 where id+1=10
(4)对索引使用隐式类型转换
(5)联合索引非最左匹配原则
(6) where子句中的or语句 只有or前后的条件列都为索引列,则进行命中索引,否则索引失效
类加载过程
加载:通过类的全限定名(包名+类名),获取到该类的.class文件的二进制字节流,将二进制字节流所代表的静态存储结构,转化为方法区运行时的数据结构,在内存中生成一个代表该类的Java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证:确保class文件的字节流包含的信息,符合当前虚拟机的要求,保证这个被加载的class类的正确性,不会危害到虚拟机的安全
准备:为类中的静态字段分配内存,并设置默认的初始值,比如int类型初始值是0,被final修饰的static字段不会设置,因为final在编译时就分配了
解析:解析阶段是虚拟机将常量池的符号引用直接替换为直接引用的过程,符号引用是以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用的时候可以无歧义地定位到目标即可
初始化:初始化是整个类加载过程的最后一个阶段,初始化阶段简单来说就是执行类的构造器方法
使用:使用类或创建对象
卸载:该类所有的实例都已经被回收,Java堆中不存在该类的任何实例,加载该类的classLoader已经被回收。类对应的java.lang.class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法
线程池
通过ThreadPoolExecutor来构建线程池,corePoolSize指定工作的核心线程数量
maxPoolSize:最大线程数,线程池允许创建的最大线程数
keepAliveTime:超出corePoolSize后创建线程存活时间
unit:keepAliveTime的时间单位
workQueue:任务队列,是一个阻塞队列,当线程数达到核心线程数后,会将任务存储在阻塞队列中
threadFactory:线程池内部创建线程所用的工厂
handler:拒绝策略,当队列已满并且最大线程数量时,会调用该方法处理任务
拒绝策略:
丢弃任务,抛出运行时异常
由提交任务的线程来执行任务
丢弃任务,但是不抛出异常
从队列中剔除最先进入队列的任务,然后再次提交任务
Executors工具类提供四种线程池
FixedThreadPool:固定线程数量的线程池,该线程池中的线程数量始终不变,当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务提交时,线程池中若有空闲线程,则立即执行,若没有,则新的任务会暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务,使用的是有界阻塞队列LinkedBlockingQueue,其任务队列的最大长度为Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM
SingleThreadExecutor:只有一个线程的线程数,若多于一个任务被提交到线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务,使用的是有界阻塞队列LinkedBlockingQueue,其任务队列的最大长度为Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM
CachedThreadPool:根据实际情况调整线程数量的线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务,所有线程在当前任务执行完毕后,将返回线程池进行复用,允许创建的线程数量为Integer.Max_VALUE 如果任务数量过多,可能会创建大量的线程
ScheduledThreadPool:给定的延迟后运行任务或定期执行任务的线程池,使用无界的延迟阻塞队列
线程的状态
NEW:初始状态,线程被创建出来但没有被调用start()
RUNNABLE:运行状态,线程被调用了start()等待运行的状态
BLOCKED:阻塞状态,需要等待锁释放
WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)
TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像waiting那样一直等待
TERMINATED:终止状态,表示该线程已经运行完毕
核心线程数和最大线程数怎么设置
CPU密集型:指的是任务主要使用进行大量的计算,没有什么导致线程阻塞,一般线程数设置为CPU核心数+1
IO密集型:当执行任务需要大量的IO,比如磁盘IO,网络IO,可能会存在大量的阻塞,所以在IO密集型任务中使用多线程可以大大加速任务的处理,一般线程数设置为2*CPU核心数
无锁: 没有开启偏向锁的状态,
偏向锁:当一个线程拿到偏向锁时,下次想要竞争锁只需要拿线程ID跟MarkWord当中存储的线程ID进行比较,如果线程ID相同则直接获取锁,不需要CAS操作
轻量级锁:有锁竞争,需要CAS操作
重量级锁:自旋失败,升级为重量级锁
缓存雪崩
即缓存同一时间大面积的失效,一大波请求都打到数据库上 通过设置随机时间,热点数据不设置过期时间
缓存击穿
一个热点key过期或被删除后,导致线上原本能命中该热点key的请求,瞬间大量打到数据库,导致数据库击垮