0. 基础
创建对象
1.用new语句创建对象,这是最常用的创建对象的方式。
2.运用反射手段,调用Java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
3.调用对象的clone()方法。
4.运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法.
类加载过程
-
加载
将class字节码文件加载到内存中,并将这些数据转换成方法区中的,运行时数据
(静态变量、静态代码块、常量池等),
在堆中生成一个Class类对象,代表这个类(反射原理),作为方法区类,数据的访问入口。
-
链接
验证
确保加载的类信息符合JVM规范,没有安全方面的问题。
准备
正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。注意此时的设置初始值为默认值,具体赋值在初始化阶段完成。
解析
虚拟机常量池内的符号引用,替换为直接引用(地址引用)的过程。 -
初始化
当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先初始化它的父类。
String,StringBuffer
1.为什么String不可变?
设想可变会存在哪些问题。
其一:如果可变,多个对象存储同一个值时,在内存中就会有多个相同的内容,造成空间浪 费。
其二:如果不同对象存储同一个值且存储一份,虽然不浪费空间,但是一个对象修改,其他对 象就会跟着修改。
不可变得原因:
多个对象存储同一个值时,只存储一份;如果某一对象需要修改,就重新创建,开辟一个新的 空间就好了。
StringBuffer
String对数据的操作会产生大量的临时对象,
所以当需要频繁的字符串修改,或者拼接时,就会很占用空间。
因此StringBuffer就可以很好地解决这个问题,因为其是对自身的操作不产生临时对象,且线程安全。
StringBuilder
相比StringBuffer有相同的属性,只是线程不安全。
IO阻塞
BIO、NIO、AIO
IO的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。
同步与异步
- 同步: 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。
- 异步: 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。
阻塞和非阻塞
- 阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。
- 非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。
BIO
同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。
NIO
NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。
AIO
AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型
1.集合
hashmap 初始容量 16,负载因子都是0.75,扩容为2倍,非同步,线程不安全(循环链表),适合单线程环境;
hashtable 初始容量11 , 负载因子都是0.75,扩容为2倍+1,同步,线程安全(可以多线程)
ArrayList线程不安全 初始长度为0 扩容1.5倍 添加第一个元素长度赋值为10 JDK1.2
Vector 线程安全 初始数组长度为10 扩容2倍 JDK1.0
1. LinkedList
- 增删快,查改慢
- 无下标,无初始长度
- LinkedList底层是双向链表,每一个Node(节点)包含前一个元素(前驱)的头指针域的地址,以及后一个元素(后继)尾指针域的地址
- 可以利用不连续的空间
- 不适合使用普通for循环遍历,比较适合增强for和迭代器遍历
- 除了实现了List接口的方法以外,还实现了Deque接口的方法(因此linkedlist具有了一定的队列性质)
- Deque表示双端队列,支持两端元素增加,和移除的线性集合
- 添加了操作头尾节点的方法
2. hashMap
在JDK8中,有以下性质
hashMap在多个线程同时执行时可能出现数据错乱:
关键的一步操作是transfer(newTable),这个操作会把当前Entry[] table数组的全部元素转移到新的table中,这个transfer的过程在并发环境下会发生错误, 导致数组链表中的链表形成循环链表 在后面的get操作时e = e.next操作无限循环,Infinite Loop出现。
loadFactor称为负载因子,默认值0.75
threshold是所能容纳的临界值 = 数组长度 * 负载因子
默认容量为16
hashMap数组部分称为hash桶,当hash桶使用率达到(3/4也就是0.75)扩容时会扩容为原来的两倍,当链表长度大于8时(泊松分布,考虑到时间开销和空间开销设计为8),数组长度大于64时,使用红黑树存储,当长度降为6时,转为链表
Node实现了Map接口中的Entry,本质是一个键值对
hasMap的Key是通过hashCode(哈希码)来来进行存储的
如果hashCoad的值相同,那就以链表的形式存储。
链表结点的hashCoad值都是一样的
哈希桶部分,计算Key值通过hash算法来计算出它的hashCode,对数组长度进行取模运算,计算的值放在数组中对应下标的位置,如果取模的值重复了,就有三种解决方案
线性探测法:线性往后寻找空位进行排列
平方探测法:(原始位置+寻找次数的平方)
双哈希探测法:
- 下标 = 关键字 mod 数组长度
hash2(Key) = R-(key mod R)第二次hash的结果在1-7之间不会等于0;(R表示比数组尺寸小的质数)
如果在满64后依然有重复,那就会使用链表存储。
2.1 ConCurrentHashMap
在JDK1.7中ConcurrentHashMap采用了数组+Segment+分段锁的方式实现。
- **Segment(分段锁):**ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表,同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
- **内部结构:**ConcurrentHashMap使用分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。如下图是ConcurrentHashMap的内部结构图:
JDK1.8之后的ConcurrentHashMap
JDK8中ConcurrentHashMap参考了JDK8 HashMap的实现,采用了数组+链表+红黑树的实现方式来
设计,内部大量采用CAS操作。并发控制synchronized 和 CAS 来操作。(JDK1.6 以后 对
synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在
JDK1.8 中还能看到 Segment 的数据结构,但
是已经简化了属性,只是为了兼容旧版本;
JDK1.8的Nod节点中value和next都用volatile修饰,保证并发的可见性。
ConcurrentHashMap不论1.7还是1.8,他的执行效率都比HashTable要高的多,主要原因还是因为
Hash Table使用了一种全表加锁的方式
put方法: CAS尝试写入,失败则自旋,
synchronized锁写入数据,如果大于treeify_threshold则要转换为红黑树
hashMap和ConcurrentHashMap区别
与HashMap相比,ConcurrentHashMap 增加了两个属性用于定位段,
分别是 segmentMask 和 segmentShift。
不同于HashMap的是,ConcurrentHashMap底层结构是一个Segment数组,而不是Object数组
3.hashSet
- set,无下标,不允许重复
- hashSet无序,无下标,ArrayList有下标
- linkedHashSet有序,无下标,
- toArray方法,遍历结合数组中的所有元素
JDK8HashMap数据结构
数组 + 单向链表 + 红黑树 当单向链表的长度大于8 提高查询效率
HashMap和Hashtable的区别
HashMap线程不安全 数组长度为16 扩容2倍
Hashtable线程安全 数组长度为11 扩容2倍 + 1
遍历HashMap的几种方式
获取所有的键 keySet() 获取所有的值values()
获取所有的项entrySet() 获取键,再继续获取迭代器 keySet().iterator();
获取值,再继续获取迭代器vlues().iterator();
获取所有的项,再继续获取迭代器 entrySet().iterator();
HashSet去重复的原理是什么
HashSet去重复原理是当两个元素hashCode相同,equals比较为true,则认为是重复对象
ArrayList和Vector的区别
ArrayList线程不安全 初始长度为0 扩容1.5倍 添加第一个元素长度赋值为10 JDK1.2 Vector 线程安全 初始数组长度为10 扩容2倍 JDK1.0
Collection和Collections的区别
Collection是集合的父接口
Collections工具类
- 如果需要对一个泛型为自定义类型的集合进行排序,需要做什么操作
集合中的元素类,必须实现Comparable接口,重写compareTo方法,指定比较规则
- TreeSet和TreeMap的关系,实现排序的原理是什么?
TreeSet底层是维护的一个TreeMap实例
实现排序的原理是实现Comparable接口,重写compareTo方法
4. 红黑树
- 二叉树,的根节点为黑,子节点为红(节点权重按照二叉树的中序排序的树)
- 变色:节点的颜色由黑变红,或者由红变黑
- 左旋:所有根节点逆时针运动(向左就是逆时针)上移一层,根节点的左子树变成,另外一个节点的右子树
- 右旋:所有根节点顺时针运动上移一层,右子节点变为另外一个节点的左子树
5. 集合汇总
- Queue对应的数据结构是队列,deque是其子类
- Collections接口,是List和Set的父类
- List对应链表
- ArrayList是数组链表,有下标
- LinkedList是双向链表,无下标
- List是ArrayList和vector以及LinkedList的父类
- Map接口是HashMap和HashTtable以及TreeMap的父类
- LinkedHashSet和HashMap的数据结构都是:数组 + 链表/红黑树。
- HashMap的数据结构是 数组+ 链表 + 红黑树。(红黑树:左<中<右 相当于中序遍历的结果)
- Properties类用于存储键值对形式的字符串,不可以存储非字符串
- 只有数组链表有下标,其他都没下标
- HashSet和HashMap无序且非线程安全,底层依然是HashMap
- TreeMap和TreeSet的元素必须实现Comparable接口,重写compareTo
- 线程不安全问题,多线程访问时,双向链表会环形链表;单向链表会数组下标越界
2.JVM
底层科普
- 堆和栈对应的物理结构:RAM,也叫主存,是与CPU直接交换数据的内部存储器。
RAM (random access memory)随机存储器,一小部分是集成于CPU,还有外置的,如我们的内存条.
ROM( read only memory ) 为只读存取器(光盘,磁盘,U盘),置于主板,于BIOS保存程序用的.
计算机中,所有的计算都是在CPU寄存器中完成,而指令完成所需要的数据读取和写入,都需要从RAM主存(内存条也叫外存)获取。受硬件工艺的影响,现在的CPU处理速度已经远远超过主存的访问速度,差额基本是成千上万的差距。
使用CPU缓存(高速缓存,也属于RAM)来处理数据的步骤大致为:
把需要的数据从主存复制一份到CPU缓存中;
CPU从缓存中读取数据并计算;
计算完成的数据刷新到主存中。
JVM运行时数据区,栈,对应的信息在CPU内部的RAM高速缓存中,堆,对应的信息在外存,RAM(内存条)中
1.四个主要部分
- 类加载器(ClassLoader)、将字节码文件加载到内存(运行时数据区)
- 运行时数据区(Runtime Data Area)、字节码文件
- 执行引擎(Execution Engine)、将字节码翻译成底层系统指令再交由CPU去执行
- 本地库接口(Native Interface)、调用其他语言接口,本地枯竭接口,进而执行java程序
2.运行时数据区的五个部分
1.1.1 程序计数器
内存空间小,线程私有。
字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,
分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成
1.1.2 Java 虚拟机栈
线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会
创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一
个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。
1.1.3 本地方法栈
区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,
而本地方法栈则为虚拟机使用到的 Native方法服务(Native方法是其他语言书写的(C))。
也会有 StackOverflowError 和 OutOfMemoryError 异常。
Native方法:
1.1.4 Java 堆
对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。
线程共享,主要是存放对象实例和数组。
内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。
可以位于物理上不连续的空间,但是逻辑上要连续。
新生代:
伊甸区:幸存者区1:幸存者区2,为8:1:1
老年代:
年龄大于15的
TLAB(tlab):
TLAB 就是 Thread Local Allocation Buffers 即线程本地分配缓存,是一个线程专有的内存区域,
这块区域在堆内存中,目的是为了加速对象分配而产生的,对象的创建是堆上面分配空间的,
同一时间可能会有多个线程在创建对象,因此每次创建对象都需要同步进行,这样在竞争激烈的情况下分配的效率会进一步下降,
考虑到创建对象分配内存是经常性的操作,所以 JVM 就使用 TLAB 这种机制给每个线程预先分配一小段专属空间,来避免多线程冲突,提高分配效率,TLAB 占用的是EDEN 区,因为在默认情况下我们创建的对象会在 EDEN 区
1.1.5 方法区
属于共享内存区域(常量池),存储已被虚拟机加载的类信息、常亮、常量池、静态变量、即时编译器编译后的代码等数据。
JDK1.7是方法区,JDK1.8是元空间
1.1.6 运行时常量池
其实还有一个 运行时常量池 ,这个常量池和方法区,的常量池唯一的不同是,
运行时常量池中,含有一个 运行时常量池表 用于编译器存放各种字面量。
也就是说,不是只有class文件中的常量池,才能放入到方法区的运行时常量池。
3.JVM调优
在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节
要调节的参数
什么是指令重排?
指令重排是指JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序。
指令重排的目的是为了在不改变程序执行结果的前提下,优化程序的运行效率。
需要注意的是,这里所说的不改变执行结果,指的是不改变单线程下的程序执行结果。
内存屏障(Memory Barrier)是一种CPU指令,维基百科给出了如下定义:
内存屏障 也称为内存栅栏,是一种屏障指令,
它使CPU或编译器对屏障指令之前和之后发出的内存操作执行一个 排序约束。
这通常意味着在屏障之前发布的操作先执行,屏障之后发布的操作后执行。
JVM优化,逃逸分析
什么是逃逸?
对象方法,或者线程被除本类和本线程外的,其他方法或者线程引用
- 不逃逸
- 方法逃逸
- 线程逃逸
基于逃逸分析有三种解决方案:栈上分配、标量替换、同步消除
-
栈上分配()
对引用对象的回收,GC消耗资源较大,
就把 不会发生线程逃逸 的对象,分配栈上内存。对象所占用的内存就会,随着栈帧的出栈而销毁。
不会发生逃逸和,不会方法逃逸的对象,在应用程序中占比很大,所以这是一个很好的优化方案
-
标量替换
标量:八种基本数据类型,以及引用数据类型String,都属于标量。
聚合量:如果数据类型可以被分解,就是聚合量。
Java中的对象就是一个非常典型的聚合量,
如果 把对象拆分成原始的标量进行访问 ,这个过程就是标量替换。
如果逃逸分析,分析出当前的对象 不会出现比方法逃逸更大的逃逸 ,那就可以将对象拆分,
把对象的成员变量创建在栈上(栈上的数据,分配到CPU告诉寄存器上的几率很大)
进而,分配和读写就可以进一步优化。
标量替换也是栈上分配的一个特例。
-
同步消除
主要是针对,不会发生,线程逃逸的对象。
要保持线程的同步,其实是一个比较耗时的过程。
如果经过逃逸分析,发现对象不会发生线程逃逸,
那么这个对象的成员变量,就不会有竞争,也就可以放心的,解除掉同步措施。
4.多线程
线程的状态
1. 新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有 机会转到运行状态。阻塞的情况分三种:
- (01) 等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。
- (02) 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
- (03) 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程的创建方式
1.thread, 继承thread类创建线程,重写该类的run方法,该方法的方法体就是线程要完成的任务,启动线程,调用线程的start()方法
2.runnable, 实现runnable接口创建线程,重写run()方法,启动线程,即调用线程的start()方法
3.使用callable创建线程
4.future创建线程
实现线程安全
使用同步代码块
使用同步方法
使用reentrantlock(重入锁)某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
线程池
四大线程池,7大参数,4种拒绝策略,五大状态
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个***可缓存线程池***,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个***定长线程池,支持定时,及周期性任务执行***。
newSingleThreadExecutor 创建一个**单线程化的线程池,**它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
线程池的构造函数有7个参数,分别是
corePoolSize 线程池核心线程大小
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了
allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
maximumPoolSize 线程池最大线程数量
一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如
果没有则会缓存到,第五个参数-工作队列中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取
出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有
一个最大线程数量的限制,这个数量即由maximunPoolSize指定
keepAliveTime 空闲线程存活时间
unit 空闲线程存活时间单位
workQueue 工作队列
四种工作队列
threadFactory 线程工厂
handler 拒绝策略
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略
拒绝策略
前三种解决策略,直接拒绝,丢弃,抛出异常,或者什么都不做。
④DiscardOldestPolicy(丢弃最早策略)
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
线程池五大状态
Running(运行)、ShutDown(关闭)、Stop(暂停)、Tidying(整理)、Terminated(终止)。
线程池处在Running状态时,能够接收新任务,以及对已添加的任务进行处理。
线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于Running状态,并且线程池中的任务数为0!
线程池处在ShutDown状态时,不接收新任务,但能处理已添加的任务。
调用线程池的shutdown()接口时,线程池由Running-> ShutDown。
线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
状态切换:调用线程池的shutdownNow()接口时,线程池由(Runningor ShutDown) -> STOP。当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
状态切换:当线程池在ShutDown状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 ShutDown-> Tidying。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> tidying 。线程池彻底终止,就变成TERMINATED状态。
状态切换:线程池处在tidying状态时,执行完terminated()之后,就会由 tidying -> terminated()。
volatile
原子性,有序性,可见性。
-
内存可见性
-
禁止指令重排
使用内存屏障,禁止指令重排
怎么用synchronized
1 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
2 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
3 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
synchronized底层实现
syncthronized是java jvm中的一个保留关键字,可以实现同步的作用,
- 对象头
- mark word (64bit) 标记锁的状态
- klass word (64bit) ,保存的事类的元信息
- 数组长度(64bit),当对象是数组的时候,使用到这个部分
- 对象体
- 对象的多个属性值
- 对齐字节
- 对象的大小必须是8字节的整数倍,当不够时,通过对其补全。
底层利用对象头的Mark Word(标记字符)标记锁的状态,
实现1.6以后synchronized锁的由无锁–偏向
锁–轻量级锁–重量级锁,状态的升级;利
用monitor(监视器)来获取锁,实现同步。
Lock和syncthronized
java.util.concurrent.locks.Lock :使用lock可获得更灵活的结构和同步属性解决方案;
lock的实现类,可以实现重入,公平的互斥锁,拥有比syncthronized更灵活高效的功能;
ThreadLocal
什么是ThreadLocal
ThreadLocal是除了加同步锁方式之外,一种规避多线程访问时,出现线程不安全的方法。
当我们在创建一个变量后,如果每个线程,对其进行访问的时候,访问的都是线程自己的变量,
(线程自己的变量是存在堆中的 tlib(线程的本地缓存)中)
这样就不会存在线程不安全问题。
threadLocal的工作原理
ThreadLocal是JDK包提供的,它提供线程本地变量,
如果创建一个ThreadLocal变量,
那么访问这个变量的每个线程都会有这个变量的一个副本,
在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。
死锁
线程死锁的原因
联想到,银行家算法 --> 解决的就是线程进行顺序的问题
(1)竞争系统资源 (2)进程的推进顺序不当
死锁常见于,线程在锁定对象还没释放时,又需要锁定另一个对象,
并且此时该对象可能被另一个线程锁定。这种时候很容易导致死锁。
因此在开发时需要慎重使用锁,尤其是需要注意尽量不要在锁里又加锁。
产生死锁的必要条件
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,而该资源又被其他线程锁定,从而导致每一个线程都得等其它线程释放其锁定的资源,造成了所有线程都无法正常结束。
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。下面用java代码来模拟一下死锁的产生。
java避免死锁的解决意见
当线程在同步某个对象里,再去锁定另外一个对象的话,就很容易发生死锁的情况。
最好是线程每次只锁定一个对象并且在锁定该对象的过程中不再去锁定其他的对象,
这样就不会导致死锁了。比如将以上的线程改成下面这种写法就可以避免死锁
加锁顺序(线程按照一定的顺序加锁)[这里使用的是银行行家算法的思想]
加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
死锁检测 :
java 中提供了可以检测死锁的工具类ThreadMXBean,我们可以用它来在项目运行时期使用代码去检测是否有死锁存在.
sleep和wait的区别
sleep()和wait()方法的最大区别是:
sleep()睡眠时,保持对象锁,仍然占有该锁,保持线程,不挂起;
而wait()睡眠时,释放对象锁。会线程挂起。
但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出
InterruptedException(但不建议使用该方法)。
5.GC
对象引用级别
Java 中提供了四种级别引用
- 强引用
强引用属于包内可见,在一个线程中无需引用可以直接对象,除非引用不存在了,否则对象不会被 GC
回收,我们平时声明变量就是强引用,普通情况下百分之99的对象都是强引用
- 软引用
属于public,当我们的 jvm 内存不足的时候,GC 会清理(不是回收)所有的软引用对象,当 GC
在清理的时候会通过一系列的算法来决定是否回收软引用对象,并可选的把软引用对象存放在一个引用队列,虚拟机会尽可能让软引用对象存活的时间更长点
- 弱引用
属于public,GC 在会收的时候一定会回收弱引用,但是关系复杂的弱引用一般要经过几次 GC 后才会被回收完,弱引用对象常常用于 Map
中,当 Map 这个强引用对象被释放后,内部的对象就是弱引用了,可以被 GC 快速回收
- 虚引用
属于
public,又称幽灵引用,本身不是为了使用这个对象而存在,主要目的是在一个对象被回收前收到通知,创建幽灵对象的时候必须要创建一个引用队列,并且在
get 这个对象的时候必须返回 null
GC Root
一个指针(引用),它保存了堆里面的对象(指向),而自己又不存储在堆中,那么它就可以是一个 ROOT,可以作为 GC Roots 的节点主要是全局性的引用(如常量或者静态属性引用的对象)与执行上下文(栈帧中的局部变量表)以及 JNI 本地方法栈中引用的对象
可以作为GCRoot的对象
>虚拟机栈(栈帧中的本地变量表)中的引用的对象;
>方法区中类静态属性引用的对象;
>方法区中常量引用的对象;
>本地方法栈中JNI(一般说的Native方法)的引用的对象
垃圾回收算法
-
标记算法
常见的垃圾标记算法有两种,分别是引用计数器算法和可达性分析算法(根搜索算法)
-
收集算法
标记算法
- 引用计数器算法
在对象头中分配一个空间来保存该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数+1,如果删除对该对象的引用,那么它的引用计数就-1,当该对象的引用计数为0时,那么该对象就会被回收。
- 可达性分析算法
根搜索算法的基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
*说人话:
当前对象没有任何引用,也没有被任何对象引用,则证明此对象是不可用的,就说明不可达,进而就可以进行回收。
收集算法
标记-清除算法
分为标记和清除两阶段:首先标记出所有需要回收的对象,然后统一回收所有被标记的对象。
标记-压缩算法(回收老年代)
让所有被标记的对象都往一端移动,然后清理掉端边界以外的内存。
内存碎片一直是非移动垃圾回收器(指在垃圾回收时不进行对象的移动)的一个问题,比如说在前面的标记-清除垃圾回收器就有这样的问题。而标记-压缩垃圾回收算法能够有效的缓解这一问题。
复制算法(半区复制算法),回收幸存者区
的目的也是为了更好的缓解内存碎片问题。对比于
标记-压缩算法
, 它不需要遍历堆内存那么多次,节约了时间,但是它也带来了一个主要的缺点,那就是相比于标记-清除和标记-压缩垃圾回收器,它的可用堆内存减少了一半。同时对于大对象,复制比标记的代价更大。所以半区复制算法更一般适合回收小的,存活期短的对象。
增量算法
的基本思想史如果一次性将所有垃圾都回收,需要造成系统长时间停顿,那么可以让垃圾回收线程和程序线程同时执行,垃圾回收只收集一小片区域的内存空间,然后切换到应用程序,依次反复,直到垃圾收集完成,
使用这种方式能减少系统停顿时间,
不过因为线程上下文的切换,导致垃圾回收总体成本较高,系统吞吐量下降
“分代收集”(Generational Collection)算法
这种算法是根据对象的存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清除”或“标记-整理”算法来进行回收。方法区永久代,回收方法同老年代。注:JDK8移除了永久代
Java堆
老年代 + 新生代 | 大小比例为1:2,如下
新生代 = Eden(伊甸区) + S0(幸存者区1) + S1(幸存者区2)永久代 JDK8已经移除,之前是 JVM 规范中方法区的实现
GC分类
Minor GC
年轻代的回收称之为Minor GC,年轻代的回收频率特别频繁,大多数对象都是在年轻代中创建并回收的
MajorGC/Full GC
年老代(老年代)的内存区域一般大于年轻代,所以年老代发生 GC 的频率会比年轻代少,对象从年老代消失的时候我们称为MajorGC或者Full GC,Full GC 会占用大量时间导致程序一段时间内无响应
GC回收过程
GC回收不局限于对堆的回收,但主要是对堆内存的回收。
内存不足时进行垃圾回收
当 Eden 区的空间满了, Java虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor 区。
大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年代
如果对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor(幸存这区)容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,经过了一轮又一轮,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年代。
Minor GC 是清理新生代
Full GC老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和老年代。
Major GC 发生在老年代的GC,清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10倍以上
GC 回收效率
因为 Java 堆是存储对象实例的区域,所以它也是 GC(垃圾回收)的重点关注区域,
所以 GC 可能在大内存使用或者频繁垃圾回收的时候造成系统性能瓶颈,所以不同的虚拟机还有一些其他的实现,比如阿里的 AliJVM 就是 将生命周期较长的对象从老年代中移到了 堆外内存中 以此达到降级 GC 回收频率和提升 GC 回收效率的目的
除此之外,采用栈上分配这种机制也可以降低 GC 的回收频率和提升回收效率,这样一来堆就不再是 Java 对象内存分配的唯一选择了
但是栈上分配是有前提条件的,也就是对象的内存空间不会发生逃逸问题,也就是当 JVM 确认不会发生 逃逸 的时候,会将对象拆分为标量然后在内存中创建复制并使用
我们的对象其实内部,其实是由不同的标量,组合起来的组合量, 标量就是,基本数据类型、字符串等类型
Full GC
老年代满了时就会触发full GC
会对整个堆进行整理,包括新生代(伊甸区 + 幸存者区1 + 幸存者区2)。Full GC因为需要对整个堆进行回收,所以比较慢,因此应该尽可能减少Full GC的次数。
导致Full GC的原因
- 调用了system.gc,系统建议之触发full GC,但不绝对
- 老年代空间不足
- 方法区空间不足
- 通过minor GC后,进入老年代的平均大小,大于老年代的可用内存
- 由Eden区、survivor space1(From Space)区向survivor space2(To Space)区复制时,对象大小大 于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
GC回收器
https://blog.csdn.net/qq_42742861/article/details/89813142
-
串行Serial Collector 单线程回收
串行垃圾回收器,通过持有应用程序所有的线程进行工作。
它为单线程环境设计,只使用一个单独的线程进行垃圾回收,
通过冻结所有应用程序线程进行工作,所以不太适合服务器环境。
它最适合的是简单的命令行程序。
通过JVM参数
-XX:+UseSerialGC
可以使用串行垃圾回收器。 -
并行回收器(Paraller Collector ) :JVM默认的
并行垃圾回收器也叫做 throughput collector 。
它是JVM的默认垃圾回收器。
与串行垃圾回收器不同,它使用 多线程 进行垃圾回收。
相似的是,当执行垃圾回收的时候,它也会冻结所有的应用程序的线程。
-
并发标记扫描收回器(CMS Collector) :
并发标记垃圾回收使用 多线程 扫描堆内存,
标记需要清理的实例,并且清理被标记过的实例。
并发标记垃圾回收器只会在种情况持有应用程序所有线程。
- 当标记的引用对象在tenured区域;
- 在进行垃圾回收的时候,堆内存的数据被并发的改变。
相比并行垃圾回收器,并发标记扫描垃圾回收器使用更多的CPU来确保程序的吞吐量。
如果我们可以为了更好的程序性能分配更多的CPU,
那么相比并发垃圾回收器,并发标记上扫描垃圾回收器,是更好的选择
通过JVM参数
XX:+USeParNewGC
打开并发标记扫描垃圾回收器。 -
G1 垃圾回收器
G1垃圾回收器适用于堆内存很大的情况,
他将堆内存分割成不同的区域,并发的对不同的区域进行垃圾回收。
G1也可以在回收内存之后对剩余的堆内存空间进行压缩。
并发扫描标记垃圾回收器在STW情况下压缩内存。
G1垃圾回收会优先选择第一块垃圾最多的区域进行回收
通过JVM参数
–XX:+UseG1GC
使用G1垃圾回收器
6.SQL索引
索引分 单列索引 和 组合索引 。
单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引。
组合索引,即一个索引包含多个列。
索引的数据结构是变种B+树,
B+ 树的叶子节点包含了当前树的所有节点,且叶子结点是单向链表。
索引树和B+ 树唯一不同的是,索引树的叶子结点是双向链表。
索引
索引包含单列索引 和 组合索引 共同包含以下四类索引
索引的类型是由存储引擎决定的,不同的存储引擎,支持的索引类型并不相同
1.普通索引
这是最基本的索引,它没有任何限制。普通索引(由关键字KEY或INDEX定义的索引)的唯一任务是加快对数据的访问速度。
尽量选择整型的数据列,为索引列。
2.唯一索引
它与前面的普通索引类似,不同的就是:普通索引允许被索引的数据列包含重复的值。而唯一索引列的值必须唯一,但允
许有空值。如果是组合索引,则列值的组合必须唯一。
3.主键索引
它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引
4.组合索引
多个字段共同组成当前索引。
最佳左匹配原则
索引可以包含一个或多个列的值。如果索引包含多个列,那么列的顺序也十分重要,因为MySQL只能高效地使用索引的最左前缀列。
索引的查询
逻辑页依赖于不同的存储引擎,InnoDB存储的逻辑也为16k
在不确定应该在哪些数据列上创建索引的时候,人们从explain select命令那里往往可以获得一些帮助。这其实只是简单地
给一条普通的select命令加一个explain 关键字作为前缀而已。
有了这个关键字,MySQL将不是去执行那条select命令,而是去对它进行分析。MySQL将以表格的形式把查询的执行过程
和用到的索引(如果有的话)等信息列出来。
索引优化
从两个方面考虑,分别是
-
选择索引的数据类型
(1)越小的数据类型通常更好:越小的数据类型通常在磁盘、内存和CPU缓存中都需要更少的空间,处理起来更快。
(2)简单的数据类型更好:整型数据比起字符,处理开销更小,因为字符串的比较更复杂。在MySQL中,应该用内置 的日期和时间数据类型,而不是用字符串来存储时间;以及用整型数据类型存储IP地址。
(3)尽量避免NULL:应该指定列为NOT NULL,除非你想存储NULL。在MySQL中,含有空值的列很难进行查询优化, 因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空 值。
-
选择标识符
(1)整型:通常是作为标识符的最好选择,因为可以更快的处理,而且可以设置为AUTO_INCREMENT。
(2)字符串:尽量避免使用字符串作为标识符,它们消耗更大的空间,处理起来也较慢。而且,通常来说,字符串都
是随机的,所以它们在索引中的位置也是随机的,
这会导致页面分裂、随机访问磁盘,聚簇索引分裂(对于使用聚簇索引的存储引擎)。
mysql索引失效场景
1使用like首字母模糊查询时,通配符在前 全表扫描 索引失效
2列类型是字符串,查询条件未加引号。
3在查询条件中使用 or (或者or的每个列都加索引)
4对索引列进行函数运算(select name from user where MD5(id)=215421351)
索引(Index)常见的查询算法
顺序查找,
二分查找,
二叉排序树查找,
哈希散列法,
分块查找,
平衡多路搜索树 B 树(B-tree)
7.Redis
数据类型
常用的5种数据结构:
- key-string:一个key对应一个值。
- key-hash:一个key对应一个Map。
- key-list:一个key对应一个List列表。
- key-set:一个key对应一个Set集合。
- key-zset:一个key对应一个有序的Set集合。等价于:Map<Object,Double>
另外3种数据结构:
- HyperLogLog:计算近似值的。
- GEO:地理位置。
- BIT:一般存储的也是一个字符串,存储的是一个byte[]。
持久化
RDB是Redis默认的持久化机制
RDB持久化文件,速度比较快,而且存储的是一个二进制的文件,传输起来很方便。
AOF持久化机制默认是关闭的,Redis官方推荐同时开启RDB和AOF持久化,更安全,避免数据丢失。
AOF持久化的速度,相对RDB较慢的,存储的是一个文本文件,到了后期文件会比较大,传输困难。
集群策略
1.主从复制
单机版 Redis存在读写瓶颈的问题
2.哨兵机制
哨兵可以帮助我们解决主从架构中的单点故障问题
3.集群多主多从
Redis-Cluster
Redis集群在保证主从加哨兵的基本功能之外,还能够提升Redis存储数据的能力
Redis的过期策略
key的生存时间到了,Redis会立即删除吗?不会立即删除。
有以下几种策略:
- 定期删除:Redis每隔一段时间就去会去查看Redis设置了过期时间的key,会再100ms的间隔中默认查看3个key。
- 惰性删除:如果当你去查询一个已经过了生存时间的key时,Redis会先查看当前key的生存时间,是否已经到了,直接删除当前key,并且给用户返回一个空值。
Redis的淘汰机制
在Redis内存已经满的时候,添加了一个新的数据,执行淘汰机制。
volatile-lru:在内存不足时,Redis会再设置过了生存时间的key中干掉一个最近最少使用的key。
allkeys-lru:在内存不足时,Redis会再全部的key中干掉一个最近最少使用的key。
volatile-lfu:在内存不足时,Redis会再设置过了生存时间的key中干掉一个最近最少频次使用的key。
allkeys-lfu:在内存不足时,Redis会再全部的key中干掉一个最近最少频次使用的key。
volatile-random:在内存不足时,Redis会再设置过了生存时间的key中随机干掉一个。
allkeys-random:在内存不足时,Redis会再全部的key中随机干掉一个。
volatile-ttl:在内存不足时,Redis会再设置过了生存时间的key中干掉一个剩余生存时间最少的key。
noeviction:(默认)在内存不足时,直接报错。
指定淘汰机制的方式:maxmemory-policy 具体策略,设置Redis的最大内存:maxmemory 字节大小
10.Spring
Spring IOC 原理
概念 Spring 通过一个配置文件描述 Bean 及 Bean 之间的依赖关系,
利用 Java 语言的反射功能实例化 Bean 并建立 Bean 之间的依赖关系。
Spring 的 IoC 容器在完成这些底层工作的基础上,还提供 了 Bean 实例缓存、生命周期管理、 Bean 实例代理、事件发布、资源装载等高级服务。
Spring 容器高层视图
Spring 启动时读取应用程序提供的 Bean 配置信息,并在 Spring 容器中生成一份相应的 Bean 配置注册表,
然后根据这张注册表实例化 Bean,装配好 Bean 之间的依赖关系,为上层应用提供准备就绪的运行环境。
其中 Bean 缓存池为 HashMap 实现
Spring APO 原理
概念 “横切"的技术,剖解封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块, 并将其命名为"Aspect”,即切面。
所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未 来的可操作性和可维护性。
使用"横切"技术,AOP 把软件系统分为两个部分:核心关注点和横切关注点。
业务处理的主要流 程是核心关注点,与之关系不大的部分是横切关注点。
横切关注点的一个特点是,他们经常发生 在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务。AOP 的作用在于分离系统 中的各种关注点,将核心关注点和横切关注点分离开来。
AOP 主要应用场景
- Authentication 权限
- Caching 缓存
- Context passing 内容传递
- Error handling 错误处理
- Lazy loading 懒加载
- Debugging 调试
- logging, tracing, profiling and monitoring 记录跟踪 优化 校准
- Performance optimization 性能优化
- Persistence 持久化
- Resource pooling 资源池
- Synchronization 同步
- Transactions 事务
AOP 核心概念
1、切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象
2、横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
3、连接点(joinpoint):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
4、切入点(pointcut):对连接点进行拦截的定义
5、通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、 异常、最终、环绕通知五类。
6、目标对象:代理的目标对象
7、织入(weave):将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法 或字段
SpringBean的生命周期方法
web 容器启动时,首先加载web.xml 文件,优先解析的是内容上下文加载监听器配置:org.springframework.web.context.ContextLoaderListener,
配置主要工作是:基于contextClass
以及servlet的上下文参数中context-param指定的spring配置文件去创建一个web 应用上下
文—初始化root application context
以及通过指定应用上下文的配置文件进行初始化载。
AbstractAutowireCapableBeanFactory.doCreateBean中会调用createBeanInstance()方法,
这个阶段主要是从beanDefinitionMap循环读取bean,获取它的属性,
然后利用反射(core包下有ReflectionUtil会先强行将构造方法setAccessible(true))读取对象的构造方法(spring会自动判断是否是有参数还是无参数,以及构造方法中的参数是否可用),然后再去创建(newInstance)
初始化
初始化主要包括两个步骤,一个是 属性填充 另一个就是具体的初始化过程
属性赋值** PopulateBean()会对bean的依赖属性进行填充,@AutoWired注解注入的属性就是发生这个阶段,
假如我们的bean有很多依赖的对象,那么spring会依次调用这些依赖的对象进行实例化,
这里可能会有循环依赖的问题。后面我们会讲到spring是如何解决循环依赖的问题*
初始化** Initialization
初始化的过程包括将初始化好的bean放入到spring的缓存中、填充我们预设的属性进一步做后 置处理等
使用和销毁 Destruction
在Spring将所有的bean都初始化好之后,我们的业务系统就可以调用了。
销毁主要的操作是销毁bean,主要是伴随着spring容器的关闭,
此时会将spring的bean移除容器之中。
此后spring的生命周期到这一步彻底结束,不再接受spring的管理和约束。
DI依赖注入
对象的嵌套创建,就是依赖注入。一个对象的创建使用,依赖于另外一个对象(之所以产生依赖注入,是因为在一个对象里,需要调用另外一个对象。)
用xml配置bean进行依赖注入是为了解耦(也可以spring注解注入)
这符合OOP设计原则的“合成复用原则”:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
Spring循环依赖,以及三级缓存
循环依赖就是在Spring IOC容器实例化对象的时候,
自己依赖自己或者,
有两个或者多个对象互相依赖对方,
多个对象互相依赖对方,
持有对方的引用并且需要完成注入的情况。
解决循环依赖
-
首先创建对象的时候会进入getBean方法去容器里面拿是否已经缓存了该bean对象,这个getSingleton方法里面
-
这个方法里面有三个map, singletonObjects, singletonFactories, earlySingletonObjects,
第一个是用来存放容器实例化后的单例对象,也可以说是单例池,
第二个单例工厂对象池,
第三个是提前暴露的对象池
从一级缓存中获取不到实例,就创建实例,提前暴露添加到三级缓存
从三级缓存获取到实例,并添加到二级缓存
当依赖注入成功后,然后初始化完成,再添加到一级缓存
最后注入成功,初始化完成。
SpringBoot的自动装配原理
Spring Boot启动的时候会通过@Enable-Auto-Configuration注解找到META-
INF/spring.factories配置文件中的所有自动配置类,进行加载,
这些自动配置类都是以Auto-Configu-ration结尾来命名的,它实际上就是一个JavaConfig形式
的Spring容器配置类,
它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:server.port,就
XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的
11.mybatis
缓存,懒加载
什么是缓存?
缓存就是把一些公共资源(经常使用但不经常更新的公共资源),运行一次后,就保存在栈中的缓存域里,而不去销毁,以便其他对象的调用,从而减少了频繁调用带来的系统开销(减少数据库的交互次数),进而增加运行和响应效率。
mybatis一级缓存
它指的是Mybatis中sqlSession对象的缓存,当我们执行查询以后,查询的结果会同时存入到SqlSession为我们提供的一块区域中,该区域的结构是一个Map,当我们再次查询同样的数据,mybatis会
先去sqlsession中查询是否有,有的话直接拿出来用,当SqlSession对象消失时,mybatis的一级缓存也就消失了,同时一级缓存是SqlSession范围的缓存,当调用SqlSession的修改、添加、删除、commit(),close等
方法时,就会清空一级缓存。
mybatis二级缓存
他指的是Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存,但是其中缓存的是数据而不是对象,所以从二级缓存再次查询出得结果的对象与
第一次存入的对象是不一样的。二级缓存的作用域是大于一级缓存的
缓存LRU过期策略
最少最近,使用原则
总结
mybatis的的一级缓存是SqlSession级别的缓存,一级缓存缓存的是对象,当SqlSession提交、关闭以及其他的更新数据库的操作发生后,一级缓存就会清空。
关闭一级缓存后二级缓存生效;
二级缓存是SqlSessionFactory级别的缓存,同一个SqlSessionFactory产生的SqlSession都共享一个二级缓存,二级缓存中存储的是数据,当命中二级缓存时,通过存储的数据,构造对象返回。查询数据的时候,
查询的流程是二级缓存>一级缓存>数据库。
mybatis批处理
<insert id="addResource" parameterType="java.util.List">
insert into resource (object_id, res_id, res_detail_value,
res_detail_name)
values
<foreach collection="list" item=" ResourceList " index="index" separator=",">
(#{ResourceList.objectId,jdbcType=VARCHAR},
#{ResourceList.resId,jdbcType=VARCHAR},
#{ResourceList.resDetailValue,jdbcType=VARCHAR},
#{ResourceList.resDetailName,jdbcType=VARCHAR}
)
</foreach>
</insert>
12.ORM
即Object-Relationl Mapping,它的作用是在关系型数据库和对象之间作一个映射 ,
这样我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了 。
什么是ThreadLocal
ThreadLocal是除了加锁这种同步方式之外,的一种规避多线程访问时,出现线程不安全的方法。
当我们在创建一个变量后,如果每个线程对其进行访问的时候,访问的都是线程自己的变量,
这样就不会存在线程不安全问题。
threadLocal的工作原理
ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,
那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,
操作的是自己本地内存中的变量,从而规避了线程安全问题。
13. 高并发
高并发需要参考哪些指标?
高并发由于产品类型不同,所以指标都不一样。
以电商系统来说,根据模块的不同,关注的指标不同。
商品浏览看的是 QPS,订单模块则是看 TPS。同时,他们还需要关注活跃的用户量等等。
比如限流功能出现问题,要能马上认识到这是个很重要的问题,从而把解决的优先级提到很高
QPS: 每秒处理的查询量。
吞吐量:是针对一个系统而言的,表示系统的承压能力。
是软件测试的单位,与request对CPU的消耗、外部接口、IO等秘密关联。
单个request对CPU消耗越高,外部系统接口、IO影响速度越慢,系统的吞吐能力越低,反之越高
**TPS:**Transactions Per Second(字面意思为:事务每秒)
可以理解为:每秒产生的事务数
吞吐量和TPS的区别
吞吐量是数据层的指标,指单位时间内系统传输的数据量,以MB/GB等为单位
TPS是网络协议层的指标,指一秒内成功完成的事务数。通常只在Loadrunner工具中使用。
吞吐量 = 一次性能测试过程中网络上传输的数据量的总和。
并发量与QPS之间的关系:
QPS = 并发量 / 平均响应时间
典型案例:一个OA签到系统,某公司假设有600个人进行上班打卡,8:00为签到时间
从7:50至8:00这10分钟之内,600个人访问此系统,假设每人访问签到一次为1分钟。
请问:此OA系统的QPS是多少?并发数为多少?首先确定平均响应时间,平均响应时间 = 1*60 = 60秒
QPS = 600/(10*60)=1 人/秒
并发量 = QPS * 平均响应时间 = 1*60 = 60人
14.过滤器和拦截器
使用位置
过滤器:
首先说一下Filter的使用地方,我们在配置web.xml时,总会配置一段设置字符编码,不然会导致乱码问题:
**过滤器(Filter):**它依赖于servlet容器。在实现上,基于函数回调,
它可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。
使用过滤器的目的,是用来做一些过滤操作,获取我们想要获取的数据,
比如:在Javaweb中,对传入的request、response提前过滤掉一些信息,
或者提前设置一些参数,然后再传入servlet或者Controller进行业务逻辑操作。
通常用的场景是:在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改HttpServletRequest的一些参数(XSSFilter(自定义过滤器)),如:过滤低俗文字、危险字符等。
拦截器:
拦截器的配置一般在SpringMVC的配置文件中,使用Interceptors标签,具体配置如下:
拦截器(Interceptor):它依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。
在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,
就是在service或者一个方法前,调用一个方法,或者在方法后,调用一个方法,
比如动态代理,就是拦截器的简单实现,在调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在调用方法后打印出字符串,甚至在抛出异常的时候做业务逻辑的操作。
由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,
同时一个拦截器实例在一个controller生命周期之内可以多次调用。
缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
过滤器和拦截器的区别
总结:
- 书写位置不同
- 过滤器写在web.xml中
- 拦截器写在,一般在SpringMVC的配置文件中,使用Interceptors标签
- 调用次数不同,过滤器(Filter)只能调用一次
- 实现的思想不同,拦截器是基于web容器,面向切面的(AOP),可以使用依赖注入(DI),可以调用多次。
15.MVC处理流程
- 用户向服务端发送一次请求,这个请求会先到前端控制器
DispatcherServlet
(也叫中央控制器)。- DispatcherServlet接收到请求后会调用
HandlerMapping
处理器 映射器。 由此得知,该请求该由哪个Controller来处理(并未调用Controller,只是得知),如果有拦截器执行 拦截器。- DispatcherServlet调用
HandlerAdapter
处理器适配器,告诉处理器适配器应该要去执行哪个Controller。- HandlerAdapter处理器适配器去执行Controller并得到
ModelAndView
(数据和视图),并层层返回给DispatcherServlet。- DispatcherServlet将ModelAndView交给
ViewReslover
**视图解析器解析,**然后返回真正的视图。- DispatcherServlet将模型数据填充到视图中。
- DispatcherServlet将结果响应给用户。
16.网络
TCP/UDP
TCP 面向连接的 、可靠的、安全的传输协议
- 序列号与确认号
- 超时重传
- 快速重传
- 流量控制
- 停等协议
- 后退n步协议
- 选择重传
- 拥塞控制
UDP 非面向连接 不安全 不可靠的传输协议
TCP协议三次握手 四次挥手
TCP协议的三次握手:
第一次:客户端向服务器发送连接请求
第二次:服务器向客户端响应连接请求
第三次:客户端与服务器建立连接
TCP协议的四次挥手:
第一次:客户端向服务器发送断开连接请求
第二次:服务器向客服端响应收到断开连接请求(因为TCP连接是双向的,所以此时服务器依然可以 向客户端发送信息)
第三次:客户端等待服务器发送信息完成,向服务器确定全部信息发送完毕,并且断开客户端与服务器的连接
第四次:服务器向客户端断开连接
为什么用TCP?
TCP可以处理IP层或者以下层的丢包、重复、错误问题
HTTPS
对比HTTP:
HTTP特点:
无状态:协议对客户端没有状态存储,对事物处理没有“记忆”能力,比如访问一个网站需要反复进行登录操作
无连接:HTTP/1.1之前,由于无状态特点,每次请求需要通过TCP三次握手四次挥手,和服务器重新建立连接。比如某个客户机在短时间多次请求同一个资源,服务器并不能区别是否已经响应过用户的请求,所以每次需要重新响应请求,需要耗费不必要的时间和流量。
基于请求和响应:基本的特性,由客户端发起请求,服务端响应
简单快速、灵活
通信使用明文、请求和响应不会对通信方进行确认、无法保护数据的完整性
HTTPS :
HTTPS是身披SSL外壳的HTTP。
HTTPS是一种通过计算机网络进行安全通信的传输协议,经由HTTP进行通信,利用SSL/TLS建立全信道,加密数据包。
HTTPS使用的主要目的是提供对网站服务器的身份认证,同时保护交换数据的隐私与完整性。
PS:TLS是传输层加密协议,前身是SSL协议,由网景公司1995年发布,有时候两者不区分。
HTTPS的特点:
- 内容加密:采用混合加密技术(SSL/TLS),中间者无法直接查看明文内容
- 验证身份:通过证书认证(CA数字签名),客户端访问的是自己的服务器
- 保护数据完整性:防止传输的内容被中间人冒充或者篡改
18.测试工具
网站测试: LoadRunner
**后台压力测试:**Jmeter是一款开源的测试工具软件,它是用java语言设计实现的负载性能测试,最初用来测试网络应用程序,现在扩展性能行为测试,相关开发接口api文档参见更多页。
上线压力测试:阿里云PTS压力测试
19.设计模式
OOP七大设计原则
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立
- 依赖倒置原则:要面向接口编程,不要面向实现编程。
- 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性。
- 接口隔离原则:要为各个类建立它们需要的专用接口
- 迪米特法则:只与你的直接朋友交谈,不跟"陌生人"说话。(不要在类中出现与本类相关性不强的属性)
- 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
- Spring 中的BeanFactory就是简单工厂模式的体现
- Spring AOP的实现方式,就是的代理模式,动态代理(jdk,cglib),静态代理
- Spring依赖注入Bean实例默认是单例的。单例模式
- SpringMVC中的适配器HandlerAdatper。
- spring的事件驱动模型使用的是 观察者模式 ,Spring中Observer,常用的地方是listener的实现。
- Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。
- Spring框架的资源访问Resource接口 。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。
- 模版方法模式 父类定义方法,子类定义具体实现Spring几乎所有的外接扩展都采用这种模式。
- 模版方法模式 父类定义方法,子类定义具体实现
20.springboot
自动装配
使用到的注解:@ConfigurationProperties
Spring Boot启动的时候会通过@Enable-Auto-Configuration注解
找到META-INF/spring.factories配置文件中的所有自动配置类,进行加载,
这些自动配置类都是以Auto-Configu-ration结尾来命名,它实际上就是一个JavaConfig形式
的Spring容器配置类,
它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:server.port,就
XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑
定的
SpringBoot常用注解
1、@SpringBootApplication
替代 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan
2、@ImportAutoConfiguration和@EnableAutoConfiguration
导入配置类,一般做测试的时候使用,正常优先使用@EnableAutoConfiguration
3、@SpringBootConfiguration
替代@Configuration
4、@ImportResource
将资源导入容器
5、@PropertySource
导入properties文件
不常用的
6、PropertySources
@PropertySource 的集合
8、@Scope 指定bean的作用域,默认singleton,其它包括prototype、request、session、 globalSession
9、@Lazy
使bean懒加载,取消bean预初始化。
10、@Primary
自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否者将抛出 异常。
11、@Profile
指定Bean在哪个环境下被激活
12、@DependsOn
依赖的bean注册完成,才注册当前类,依赖bean不存在会报错。用于控制bean加载顺序
13、@PostConstruct
bean的属性都注入完毕后,执行注解标注的方式进行初始化工作
14、@Autowired
默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。
15、@Lookup
根据方法返回的类型,去容器中捞出对应
16、@Qualifier
申明bean名字,且可以按bean名字加载bean
17、@Required
检查bean的属性setXXX()方法,要求属性砸死配置阶段必须已配置
18、@Description
添加bean的文字描述
19、@EnableAspectConfiguration
启动AspectJ自动配置
20、EnableLoadTimeWeaving
启动类加载器动态增强功能,使用instrumentation实现
21、@AutoConfigurationPackage
包含该注解的package会被AutoConfigurationPackages注册
22、@AutoConfigureBefore
在指定配置类初始化前加载
23、@AutoConfigureAfter
在指定配置类初始化后加载
24、@AutoConfigureOrder
指定配置类初始化顺序,越小初始化越早
25、@ModelAttribute
@ModelAttribute注解可被应用在方法和方法参数上。
21.JDK新特性
Lambda表达式,函数式变成
-
Lambda
匿名内部类的替换品
语法规则: (参数名… {方法内容}
1.是个对象2.接口中只能有一一个方法3.语法精简
优点: 1.简化代码2.避免创建方法3.事件处理(接口回调)
-
函数式编程
-
是对Lambda表达式的推广,让代码更精简
-
基于函数式接口(内部就一个方法)进行代码编写, 就叫函数式编程
-
Consumer接口
消费型有参数,无返回值一般用于打印 ,
输出accept抽象, andThen组合
-
Function
函数型有参数,也有返回值一般用于数据处理
apply计算处理, compose组合
-
Predicate:
断言型有参数,返回值为布尔类型一般用于校验处理
test验证,and并且,or或者,negate取反
-
Supplier:
给予型无参数,有返回值一般用于生成数据或者数据处理get获取数据
-
函数式接口的使用
Stream
Optional
-