java 集合 , spring springmvc springboot springcloud 数据库相关的, redis 相关 ,mq 相关 ,结合业务的场景题
HashMap是基于哈希表的Map接口的非同步实现。元素以键值对的形式存放,并且允许null键和null值,因为key值唯一(不能重复),因此,null键只有一个。另外,hashmap不保证元素存储的顺序,是一种无序的,和放入的顺序并不相同(此类不保证映射的顺序,特别是它不保证该顺序恒久不变)。HashMap是线程不安全的。
hashmap底层是利用resize方法来完成扩容的
HashMap的扩展原理是HashMap用一个新的数组替换原来的数组。重新计算原数组的所有数据并插入一个新数组,然后指向新数组。如果阵列在容量扩展前已达到最大值,阈值将直接设置为最大整数返回。
HashMap的默认初始容量为16
负载因子,默认是0.75,原因为根据hash冲突和空间利用率综合考量计算的
当HashMap中元素数超过容量*负载因子时,HashMap会进行扩容为原来的两倍。 (2倍)
在JDK1.7 中,由“数组+链表”组成,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的。
在JDK1.8 中,由“数组+链表+红黑树”组成。当链表过长,则会严重影响 HashMap 的性能,红黑树搜索时间复杂度是 O(logn),而链表是 O(n)。因此,JDK1.8 对数据结构做了进一步的优化,引入了红黑树,链表和红黑树在达到一定条件会进行转换:
当链表超过 8 且数据总量超过 64 才会转红黑树。
将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树,以减少搜索时间。
HashMap的线程不安全主要体现在两个方面:
1.在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况。
2.在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况。
因为hashmap的底层默认为链表长度为8时会转换为红黑树,至于这样设计是为了防止链表过长导致查询效率变低。而长度为8就转换的原因是根据链表和红黑树的时间复杂度综合比较得出的最佳性能点。
通常情况下,如果hash算法正常的话,链表并不会很长,转为红黑树更多的是一种保底策略,保证极端情况下的查询的效率。
解决Hash冲突方法有:开放定址法、再哈希法(再散列法)、链地址法(拉链法)、建立公共溢出区。
HashMap中采用的是 链地址法 。
开放定址法,也称为线性探测法,就是从发生冲突的那个位置开始,按照一定的次序从hash表中找到一个空闲的位置,然后把发生冲突的元素存入到这个空闲位置中。ThreadLocal就用到了线性探测法来解决hash冲突的。
包括:线性探测法、平方探测法、伪随机数法
再哈希法(双重散列,多重散列),提供多个不同的hash函数,当
R1=H1(key1)
发生冲突时,再计算R2=H2(key1)
,直到没有冲突为止。 这样做虽然不易产生堆集,但增加了计算的时间。链地址法(拉链法),将 哈希值相同 的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除的情况。
建立公共溢出区,将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区。
1.7 采用 头插法(链表死循环)
hashmap1.7中的死循环是有多个线程并发扩容形成了环状链表,随后再进行扩容的线程会循环取这个环状链表的节点,造成死循环;其次,环状链表是几个节点相互指向,并不是某个节点自己指向自己。
1.8 改为 尾插法
遍历。使用Iterator,可以遍历所有集合,如Map,List,Set;但只能在向前方向上遍历集合中的元素。
使用ListIterator,只能遍历List实现的对象,但可以向前和向后遍历集合中的元素。
添加元素。Iterator无法向集合中添加元素;而,ListIteror可以向集合添加元素。
修改元素。Iterator无法修改集合中的元素;而,ListIterator可以使用set()修改集合中的元素。
索引。Iterator无法获取集合中元素的索引;而,使用ListIterator,可以获取集合中元素的索引。
JDK1.7
JDK1.7中的ConcurrentHashMap 是由
Segment
数组结构和HashEntry
数组结构组成,即ConcurrentHashMap 把哈希桶切分成小数组(Segment ),每个小数组有 n 个 HashEntry 组成。其中,Segment 继承了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色;HashEntry 用于存储键值对数据。
首先将数据分为一段一段的存储,然后给每一段数据配一把锁(分段锁),当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问,能够实现真正的并发访问。
JDK1.8
在数据结构上, JDK1.8 中的ConcurrentHashMap 选择了与 HashMap 相同的数组+链表+红黑树结构;在锁的实现上,抛弃了原有的 Segment 分段锁, 采用
CAS + synchronized
实现更加低粒度的锁。将锁的级别控制在了更细粒度的哈希桶元素级别,也就是说只需要锁住这个链表头结点(红黑树的根节点),就不会影响其他的哈希桶元素的读写,大大提高了并发度。
ConcurrentHashMap 的效率要 高于 Hashtable,因为 Hashtable给整个哈希表加了一把大锁 从而实现线程安全。而ConcurrentHashMap 的 锁粒度更低 ,在JDK1.7中采用 分段锁 实现线程安全,在JDK1.8 中采用 CAS+Synchronized
实现线程安全。
HashMap 的优势就是查找和操作的时间复杂度都是O(1)。
HashMap是非线程安全的,Hashtable是线程安全的
HashMap:当我们进入put方法查看源码时可以发现put方法返回的方法只有个finla修饰,没有任何关于线程的存在
Hashtable:当我们点击进入之后,就能马上看到一个synchronized关键字,表示这个方法在调用的时候不能被其他的线程对象访问。
HashMap允许null作为键或值,Hashtable不允许,运行时会报NullPointerException
HsahMap在数组+链表的结构中引入了红黑树,Hashtable没有
jdk 1.8版本之后HashMap增加了一个新的结构叫做红黑树。
HashMap初始容量为16,Hashtable初始容量为11
HsahMap扩容是当前容量翻倍,Hashtable是当前容量翻倍+1
HsahMap只支持Iterator遍历,Hashtable支持Iterator和Enumeration
在jdk1.7中
ArrayList的默认容量为10,底层为数组结构,当元素存满时,会自动扩容1.5倍
在jdk1.8中,
ArrayList通过无参构造器创建的集合初始容量为0,再添加第一个元素时会扩容到默认容量的10
arraylist的特征为增删慢,查询快,因为ArrayList增删可能会涉及到数组的扩容。
底层扩容:arraylist的底层扩容机制所涉及的一个最重要的方法为grow方法,在这个方法中会先得到现有的数组长度,然后根据现有数组长度利用右移运算符去得到新的数组长度,然后会去使用arrays工具类的copeof方法去生成一个新长度的数组,再把旧的数组中的元素全部复制到新数组中,最后返回数组,即到此扩容完成。
接口的默认方法与静态方法,也就是接口中可以有实现方法
Lambda 表达式
函数式接口与静态导入
访问局部变量,等等其他新特性。
方法引用:可以直接引用已有Java类或对象的方法或构造器。
Date Time API:加强对日期与时间的处理
Stream API:可以把函数式编程风格引入到Java中
Consumer
:消费型接口(void accept(T t)) 以上为消费型接口,有参数,无返回值类型的接口。 Supplier :供给型接口(T get()) 只有返回值,没有参数 Function :函数型接口(R apply(T t)) 输入一个类型得参数,输出一个类型得参数, Predicate :断言型接口(boolean test(T t)) 输入一个参数,输出一个boolean类型得返回值。
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象.
1、互斥条件:一个资源每次只能被一个进程使用;
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
3、不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;
破坏死锁的四个条件
一旦检测出死锁,就应该立即采取措施来接触死锁。死锁解除的主要方法有:
资源剥夺法。 挂起某些死锁进程,抢占它的资源,分配给其他死锁进程。
撤销进程法。 强制撤销部分进程并剥夺这些进程的资源,让其他进程顺利执行。
进程回退法。让一个或多个死锁进程回退到足以避免死锁的地步。
#{ } 的 sql 语句带 引号
可以防止sql预处理 使用 PreparementStatement
${ } 的 sql 不带引号
在给bean属性赋值时有两种方式
@Autowired:先根据类型注入 再根据名字注入,如果找到类型有多个则根据名字找
@Resource:先根据名字注入 再根据类型注入
四个创建bean的注解: @Controller @Service @Repository @Component
注入依赖bean的注解: (@Autowired 搭配 @Qualifier 指定名字) @Resource
注入普通属性的注解:@Value
标识配置类的注解:@Configuration
指定扫描包的注解 : @ComponentScan(basePackages = {"com.glls.java2301"})
指定优先级的注解:@Primary
指定初始化方法的注解:@PostConstruct
指定销毁bean 的时候调用的方法:@PreDestroy
动态创建代理类的对象,为原始类的对象添加辅助功能。
JDK动态代理实现(基于接口) 代理对象和真实对象的关系 像是兄弟 代理对象 对真实对象进行增强
CGlib动态代理实现(基于继承) 代理对象和真实对象的关系 就像是 父子
切点: 真正要被增强的方法
通知:增强的业务代码 (实际增强的逻辑部分 )
切面:切点 + 通知
(Aspect Oriented Programming),即面向切面编程
是spring的两大特性之一
在beanpostprocessor bean后置增强中 动态代理对象替换了原来的对象
利用一种称为"横切"的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
IOC 思想基于 IOC 容器完成,IOC 容器 底层 就是 对象工厂
BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用
加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人 员进行使用
加载配置文件时候就会把在配置文件对象进行创建
重点
】Inverse Of Controll:控制反转
反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转)
解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健
补充: 什么是IOC
1.控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理
Bean管理(两个操作):
(1)Spring创建对象
(2)Spring属性注入
2.使用 IOC 目的:为了耦合度降低
3.入门案例就是 IOC 实现
IOC 底层原理:
xml 解析、工厂模式、反射
重点
】概念
在Spring创建对象的同时,为其属性赋值,称之为依赖注入。
bean 的实例化 通过构造方法创建bean 的实例 默认是无参构造
给bean 的属性赋值
把 bean 的 实例 传递给 bean的后置处理器的方法 postProcessBeforeInitialization
执行初始化的方法
把 bean 的 实例 传递给 bean的后置处理器的方法 postProcessAfterInitialization (这一步完成动态代理,狸猫换太子)
得到完整的bean 对象 ,这时的bean 对象才能够使用 (真正调用的是干活的代理对象, 动态代理对象替换了原来的对象)
销毁bean 当容器关闭的时候 调用销毁的方法
1.bean是对象,一个或者多个不限定 2.bean由Spring中一个叫IoC的东西管理 3.我们的应用程序由一个个bean构成
singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
prototype : 每次请求都会创建一个新的 bean 实例。
request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
session : :在一个HTTP Session中,一个Bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话
js 对象 转为 json 串 方法: JSON.stringify(user);
json串转为 js 对象的方法 : JSON.parse(jsonstr);
日期格式化 @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
属性名修改 @JsonProperty("new_name")
属性忽略 @JsonIgnore
null和empty属性排除 Jackson 默认会输出null值的属性,如果不需要,可以排除。
@JsonInclude(JsonInclude.Include.NON_NULL) //null值 属性不输出 @JsonInclude(value= JsonInclude.Include.NON_EMPTY) // empty属性不输出( 空串,长度为0的集合,null值)
自定义序列化 @JsonSerialize(using = MySerializer.class) // 使用MySerializer输出某属性
com.alibaba fastjson 1.2.54
日期格式化:@JSONField(format="yyyy/MM/dd")
属性名修改:@JSONField(name="birth")
忽略属性:@JSONField(serialize = false)
包含null值:
@JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue) 默认会忽略所有null值,有此注解会输出null
@JSONField(serialzeFeatures = SerializerFeature.WriteNullStringAsEmpty) null的String输出为""
自定义序列化:@JSONField(serializeUsing = MySerializer2.class)
terms和term的查询机制是一样,都不会将指定的查询关键字进行
分词
,直接去分词库中匹配,找到相应文档内容。terms是在针对一个字段包含多个值的时候使用。
term:where province = 北京;
terms:where province = 北京 or province = ?or province = ? 一个字段可以等于多个值 有点类似 in
重点
】match查询属于高层查询,他会根据你查询的字段类型不一样,采用不同的查询方式。
查询的是日期或者是数值的话,他会将你基于的字符串查询内容转换为日期或者数值对待。
如果查询的内容是一个不能被分词的内容(keyword),match查询不会对你指定的查询关键字进行分词。
如果查询的内容时一个可以被分词的内容(text),match会将你指定的查询内容根据一定的方式去分词,去分词库中匹配指定的内容。
match查询,实际底层就是多个term查询,将多个term查询的结果给你封装到了一起。
五大内存区域,分1.7和1.8
1.堆内存:引用类型的数据,内部组成:1.新生代(伊甸区和幸存者区)2.老年代。该区域经常发生垃圾回收的操作
2.虚拟机栈:方法在运行时,需要存储一些内容,存储到栈
3.本地方法栈:本地方法(被native修饰的方法),方法运行时,保存的一些信息
4.程序计数器:针对线程的,记录每个线程当前的执行的行数
5.元空间(1.7叫做方法区):存放已被加载的类信息、常量、静态变量等信息
堆内存:分为两块:1.新生代 2.老年代
新生代:内部又分为伊甸区和幸存者区1、2
创建新对象,默认进入到伊甸区,GC在运行时,会将存活的对象放在幸存者区,如果对象存活超过15次,存储到老年代
老年代:存活时间长的对象或者大对象
引用计数法
使用可达性分析算法来判定:GC Roots
标记清除算法:
第一步:利用可达性去遍历内存,把存活对象、需要回收的对象标记出来;第二步:在遍历一遍,把标记过的对象回收掉。缺点:效率不高,无法清除垃圾碎片
标记整理算法:
首先,把存活对象和垃圾对象进行标记,然后将所有的存活对象向一端进行移动,然后直接清除端以外的内存。特点:适用于存活对象多,垃圾少的情况;需要整理的过程,无空间碎片产生;
标记复制算法:
按照容量复制两个大小相等的内存空间,当有一个用完以后,就把还存活着对象复制到另一个区域中,在清除掉用完的区域,缺点:内存使用率低,只有原来的一半空间
分代收集算法:
根据内存对象的存活周期不同,将内存划分成几块,一般为新生代、老年代。
新生代一般采用复制算法,老年代一般采用标记整理算法。
Java 内存模型(下文简称 JMM)就是在底层处理器内存模型的基础上,定义自己的多线程语义。它明确指定了一组排序规则,来保证线程间的可见性。
这一组规则被称为 Happens-Before, JMM 规定,要想保证 B 操作能够看到 A 操作的结果(无论它们是否在同一个线程),那么 A 和 B 之间必须满足 Happens-Before 关系。
单线程规则:一个线程中的每个动作都 happens-before 该线程中后续的每个动作
监视器锁定规则:监听器的解锁动作 happens-before 后续对这个监听器的锁定动作
volatile 变量规则:对 volatile 字段的写入动作 happens-before 后续对这个字段的每个读取动作
线程 start 规则:线程 start() 方法的执行 happens-before 一个启动线程内的任意动作
线程 join 规则:一个线程内的所有动作 happens-before 任意其他线程在该线程 join() 成功返回之前
传递性:如果 A happens-before B, 且 B happens-before C, 那么 A happens-before C
Java 提供了几种语言结构,包括 volatile, final 和 synchronized, 它们旨在帮助程序员向编译器描述程序的并发要求,其中:
volatile - 保证可见性和有序性
synchronized - 保证可见性和有序性; 通过管程(Monitor)保证一组动作的原子性
final - 通过禁止在构造函数初始化和给 final 字段赋值这两个动作的重排序,保证可见性(如果 this 引用逃逸就不好说可见性了)
编译器在遇到这些关键字时,会插入相应的内存屏障,保证语义的正确性。
有一点需要注意的是,synchronized 不保证同步块内的代码禁止重排序,因为它通过锁保证同一时刻只有一个线程访问同步块(或临界区),也就是说同步块的代码只需满足 as-if-serial 语义 - 只要单线程的执行结果不改变,可以进行重排序。
所以说,Java 内存模型描述的是多线程对共享内存修改后彼此之间的可见性,另外,还确保正确同步的 Java 代码可以在不同体系结构的处理器上正确运行。
AQS通过一个 FIFO 队列维护线程同步状态,实现类只需要继承该类,并重写指定方法即可实现一套线程同步机制。
在 AQS
中的锁类型有两种:分别是「Exclusive(独占锁)「和」Share(共享锁)」
「独占锁」就是「每次都只有一个线程运行」,例如 ReentrantLock。
「共享锁」就是「同时可以多个线程运行」,如 Semaphore、CountDownLatch、ReentrantReadWriteLock。
JAVA中CAS是通过自旋操作完成赋值,若值不相等再更新预期值、重新计算新值,接着进行CAS操作,直到成功为止。底层是JVM调用操作系统原语指令unsafe
,并由CPU完成原子操作,你要知道并发/多线程环境下如果CPU没有原子操作我们是无法完成
没有引用锁的概念,并发量不高情况下提高效率
减少线程上下文切换
cpu开销大,在高并发下,许多线程,更新一变量,多次更新不成功,循环反复,给cpu带来大量压力。
循环时间过长:由于 CAS 经常需要重试,当重试次数过多时,会导致线程等待时间太长,影响程序的性能
解决方法:设置自旋次数,避开这个耗时问题
只是一个变量的原子性操作,不能保证代码块的原子性。
只能保证一个变量的原子操作:CAS 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性的
解决方法:使用互斥锁来保证原子性 将多个变量封装成对象
ABA问题 (马蓉问题)
ABA 问题:CAS 无法解决 ABA 问题,即在多线程环境下,一个变量的值被改变后,又被恢复到原来的值,导致 CAS 无法感知到这种变化。
解决方法:设置一个带有标记的原子引用类,通过控制变量值的版本来保证CAS的正确性
是**CAS的核心类**,由于JAVA方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定的内存数据。Unsafe存在于sun.misc包中,其内部方法可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法
1、CAS比较并交换方法只能保证数据的稳定性 ,并不能保证原子性,原子性的实质底层是通过JAVA提供的Unsafe类调用C语言外部的接口,底层是调用汇编的一个原子指令给数据总线加锁所以线程是安全的。
2、 CAS缺点通过 期望值和实际值是否相等 并不能保证中间有空档期 所以需要通过版本号来控制 JUC提供的AtomicStampedReference 时间戳原子类 保证了没改变一次主内存数据 版本号+1 在修改主内存数据时 除了通过值判断相等外还加上版本号预计值和实际值是否相等 双重控制 解决了ABA问题。
1.Lock是显示锁,需要手动开启和关闭。synchronized是隐士锁,可以自动释放锁。 2.Lock是一个接口,是JDK实现的。synchronized是一个关键字,要依赖JVM实现。 3.Lock是可中断锁,synchronized是不可中断锁,需要线程执行完才能释放锁。 4.发生异常时,Lock不会主动释放占有的锁,必须通过unlock进行手动释放,如果执行过程中发生异常未正 常释放锁的话,可能引发死锁。synchronized在发生异常的情况下会自动释放占有的锁,不会出现死锁的情 况。 5.Lock可以判断锁的状态,synchronized不可以判断锁的状态。 6.Lock实现锁的类型是可重入锁、公平锁。synchronized实现锁的类型是可重入锁、非公平锁。 7.Lock适用于大量同步代码块的场景,synchronized适用于少量同步代码块的场景。
1.volatile关键字解决的是变量在多个线程之间的可见性;而sychronized关键字解决的是多个线程之间访问共享资源的同步性。 2.volatile只能用于修饰变量,而synchronized可以修饰方法,以及代码块。(volatile是线程同步的轻量级实现,所以volatile性能比synchronized要好,并且随着JDK新版本的发布,sychronized关键字在执行上得到很大的提升,在开发中使用synchronized关键字的比率还是比较大) 3.多线程访问volatile不会发生阻塞,而sychronized会出现阻塞。 4.volatile能保证变量在多个线程之间的可见性,但不能保证原子性;而sychronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公有内存中的数据做同步。 线程安全包含原子性和可见性两个方面。 对于用volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的。 一句话说明volatile的作用:实现变量在多个线程之间的可见性。
1、保证可见性:
在多线程的场景下,当一个线程修改一个被Volatitle修饰的变量,另外一个线程也能读到该变量修改后的值,该变量即为共享变量,被所有线程共享。
2、禁止指令重排:
禁止CPU执行时进行指令重排操作(内存屏障)从而能保证有序执性,但是它并不能保证原子性。
观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令。
lock前缀指令相当于一个内存屏障(也称为内存栅栏),内存屏障会提供3个功能:
(1)它确保指令重排序时,不会把后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;也即是,在执行到内存屏障这个指令时,在他前面的操作已经完成。
(2)它会强制将缓存的修改操作立即写入主存。
(3)如果是写操作,他会导致其他CPU中对应的缓存无效。
3、不保证原子性:
对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作。
可以通过AtomicInteger或者Synchronized来保证+1操作的原子性。
强引用是默认支持,当内存不足的时候,JVM开始垃圾回收。
强引用是最常见的普通对象引用,只要还有强引用指向对象,对象就存活,垃圾回收器不会处理存活对象。比如:
StringBuilder sb = new StringBuilder();
强引用可以直接访问目标对象
强引用(存在)指向的对象任何时候都不会被回收。
强引用可能会导致内存泄漏
软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
弱引用中的对象具有很短的声明周期,因为在系统GC时,只要发现弱引用,不管堆空间是否足够,都会将对象进行回收。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
软引用 和 弱引用 都非常 适合保存那些 可有可无 的 缓存数据,当内存不足时,缓存数据被回收,当内存充足时,也可以存在较长时间,起到加速的作用。
1.内存溢出(out of memory) 是指 程序在申请内存时,没有足够的内存空间供其使用,出现 out of memory
2.内存泄漏(memory leak) 是指 程序申请内存后,无法释放已申请的内存空间,这样的泄漏积少成多,造成泄漏。
3.为了尽量避免浪费内存的情况,我们有时可以在变量sb不再使用后通过将变量sb置为null(sb = null)中断强引用与对象之前的联系,来加速对象的回收··
①拦截器是基于java的反射机制的,而过滤器是基于函数回调。 ②拦截器不依赖与servlet容器,过滤器依赖与servlet容器。 ③拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。 ④拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。 ⑤在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
⑥拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
拦截器可以获取ioc中的service bean实现业务逻辑,拦截器可以获取ioc中的service bean实现业务逻辑,拦截器可以获取ioc中的service bean实现业务逻辑,
触发时机
有个专业词语叫触发时机
1.过滤器和拦截器触发时机、触发时间和地点不一样:
过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。
总结:过滤器包裹住servlet,servlet包裹住拦截器。
2.过滤器的触发时机是容器后,servlet之前,所以过滤器的doFilter(
ServletRequest request, ServletResponse response, FilterChain chain
)的入参是ServletRequest ,而不是httpservletrequest。因为过滤器是在httpservlet之前。
1)过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。(理解:就是一堆字母中取一个B)
(2)拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。(理解:就是一堆字母中,干预他,通过验证的少点,顺便干点别的东西)。
STW: Stop-The-World: 是在垃圾回收算法
执⾏过程当中,将JVM内存冻结、应用程序停顿的⼀种状态。
在STW 状态下,JAVA的所有线程都是停⽌执⾏的 -> GC线程除外
一旦Stop-the-world发生,除了GC所需的线程外,其他线程都将停止工作,中断了的线程直到GC任务结束才继续它们的任务
STW是不可避免的,垃圾回收算法执⾏
一定会出现STW,我们要做的只是减少停顿的时间
GC各种算法优化的重点,就是 减少STW(暂停)
,同时这也是JVM调优的重点
Redis(Remote Dictionary Server)即远程字典服务,是由C语言去编写的,基于 Key-Value的NoSQL、基于内存存储数据,Redis提供了多种持久化机制
(NoSQL只是一种概念,泛指非关系型数据库,和关系型数据库做一个区分。)
常用的5种数据结构:
key-string:一个key对应一个值。 (可以用来存储用户验证码)
key-list:一个key对应一个列表。 有序的 可以重复的
key-set:一个key对应一个集合。 无序的 不可以重复的
key-zset:一个key对应一个有序的集合。 有序的 不可以重复的
key-hash:一个key对应一个Map。
另外三种数据结构:
HyperLogLog:计算近似值的。
GEO:地理位置。
BIT:一般存储的也是一个字符串,存储的是一个byte[]。
Redis使用单个线程处理请求,避免了多个线程之间线程切换和锁资源争用的开销。注意:单线程是指的是在核心网络模型中,网络请求模块使用一个线程来处理,即一个线程处理所有网络请求。
除此之外redis还有其他的特点使其性能高:
内存存储:Redis是使用内存(in-memeroy)存储,没有磁盘IO上的开销。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1)。
非阻塞IO:Redis使用多路复用IO技术,将epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。
优化的数据结构:Redis有诸多可以直接应用的优化数据结构的实现,应用层可以直接使用原生的数据结构提升性能。
使用底层模型不同:Redis直接自己构建了 VM (虚拟内存)机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
有自己的一套通信协议
缓存击穿跟缓存雪崩有点类似,缓存雪崩是大规模的key失效,而缓存击穿是某个热点的key失效,大并发集中对其进行请求,就会造成大量请求读缓存没读到数据,从而导致高并发访问数据库,引起数据库压力剧增。这种现象就叫做缓存击穿。
从两个方面解决,第一是否可以考虑热点key不设置过期时间,第二是否可以考虑降低打在数据库上的请求数量。
解决方案:
在缓存失效后,通过 互斥锁 或者 队列 来控制读数据写缓存的线程数量,比如某个key只允许一个线程查询数据和写缓存,其他线程等待。这种方式会阻塞其他的线程,此时系统的吞吐量会下降
热点数据缓存永远不过期。永不过期实际包含两层意思:
物理不过期,针对热点key不设置过期时间
逻辑过期,把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建
穿透
缓存穿透是指用户请求的数据在缓存中不存在即没有命中,同时在数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍。如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至导致数据库承受不住而宕机崩溃。
缓存穿透的关键在于在Redis中查不到key值,它和缓存击穿的根本区别在于传进来的key在Redis中是不存在的。假如有黑客传进大量的不存在的key,那么大量的请求打在数据库上是很致命的问题,所以在日常开发中要对参数做好校验,一些非法的参数,不可能存在的key就直接返回错误提示。
解决方法:
将无效的key存放进Redis中:
当出现Redis查不到数据,数据库也查不到数据的情况,我们就把这个key保存到Redis中,设置value="null",并设置其过期时间极短,后面再出现查询这个key的请求的时候,直接返回null,就不需要再查询数据库了。但这种处理方式是有问题的,假如传进来的这个不存在的Key值每次都是随机的,那存进Redis也没有意义。
使用布隆过滤器:
如果布隆过滤器判定某个 key 不存在布隆过滤器中,那么就一定不存在,如果判定某个 key 存在,那么很大可能是存在(存在一定的误判率)。于是我们可以在缓存之前再加一个布隆过滤器,将数据库中的所有key都存储在布隆过滤器中,在查询Redis前先去布隆过滤器查询 key 是否存在,如果不存在就直接返回,不让其访问数据库,从而避免了对底层存储系统的查询压力。
如果缓在某一个时刻出现大规模的key失效,那么就会导致大量的请求打在了数据库上面,导致数据库压力巨大,如果在高并发的情况下,可能瞬间就会导致数据库宕机。这时候如果运维马上又重启数据库,马上又会有新的流量把数据库打死。这就是缓存雪崩。
造成缓存雪崩的关键在于同一时间的大规模的key失效,
主要有两种可能:第一种是Redis宕机,第二种可能就是采用了相同的过期时间。
解决方案:
1、事前:
均匀过期:设置不同的过期时间,让缓存失效的时间尽量均匀,避免相同的过期时间导致缓存雪崩,造成大量数据库的访问。如把每个Key的失效时间都加个随机值,setRedis(Key,value,time + Math.random() * 10000);
,保证数据不会在同一时间大面积失效。
分级缓存:第一级缓存失效的基础上,访问二级缓存,每一级缓存的失效时间都不同
。
热点数据缓存永远不过期。永不过期实际包含两层意思:
物理不过期,针对热点key不设置过期时间
逻辑过期,把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建
保证Redis缓存的高可用,防止Redis宕机导致缓存雪崩的问题。可以使用 主从+ 哨兵,Redis集群来避免 Redis 全盘崩溃的情况。
2、事中:
互斥锁:在缓存失效后,通过互斥锁或者队列来控制读数据写缓存的线程数量,比如某个key只允许一个线程查询数据和写缓存,其他线程等待。这种方式会阻塞其他的线程,此时系统的吞吐量会下降
使用熔断机制,限流降级。当流量达到一定的阈值,直接返回“系统拥挤”之类的提示,防止过多的请求打在数据库上将数据库击垮,至少能保证一部分用户是可以正常使用,其他用户多刷新几次也能得到结果。
3、事后:
开启Redis持久化机制,尽快恢复缓存数据,一旦重启,就能从磁盘上自动加载数据恢复内存中的数据。
共有四种方案:
先更新数据库,后更新缓存
先更新缓存,后更新数据库
先删除缓存,后更新数据库
先更新数据库,后删除缓存
第一种和第二种方案,没有人使用的,因为第一种方案存在问题是:并发更新数据库场景下,会将脏数据刷到缓存。
第二种方案存在的问题是:如果先更新缓存成功,但是数据库更新失败,则肯定会造成数据不一致。
目前主要用第三和第四种方案。
该方案也会出问题,此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)
请求A进行写操作,删除缓存
请求B查询发现缓存不存在
请求B去数据库查询得到旧值
请求B将旧值写入缓存
请求A将新值写入数据库
上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。
使用伪代码如下:(删一次,睡一会儿再删一次)
public void write(String key,Object data){ Redis.delKey(key); db.updateData(data); Thread.sleep(1000); Redis.delKey(key); }
转化为中文描述就是 (1)先淘汰缓存 (2)再写数据库(这两步和原来一样) (3)休眠1秒,再次淘汰缓存,这么做,可以将1秒内所造成的缓存脏数据,再次删除。确保读请求结束,写请求可以删除读请求造成的缓存脏数据。自行评估自己的项目的读数据业务逻辑的耗时,写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。
如果使用的是 Mysql 的读写分离的架构的话,那么其实主从同步之间也会有时间差。
此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)
请求 A 更新操作,删除了 Redis
请求主库进行更新操作,主库与从库进行同步数据的操作
请 B 查询操作,发现 Redis 中没有数据
去从库中拿去数据
此时同步数据还未完成,拿到的数据是旧数据
此时的解决办法就是如果是对 Redis 进行填充数据的查询数据库操作,那么就强制将其指向主库进行查询。
这一种情况也会出现问题,比如更新数据库成功了,但是在删除缓存的阶段出错了没有删除成功,那么此时再读取缓存的时候每次都是错误的数据了。
此时解决方案就是利用消息队列进行 删除的补偿。具体的业务逻辑用语言描述如下:
请求 A 先对数据库进行更新操作
在对 Redis 进行删除操作的时候发现报错,删除失败
此时将Redis 的 key 作为消息体发送到消息队列中
系统接收到消息队列发送的消息后再次对 Redis 进行删除操作
但是这个方案会有一个缺点就是会对业务代码造成大量的侵入,深深的耦合在一起,所以这时会有一个优化的方案,我们知道对 Mysql 数据库更新操作后在 binlog 日志中我们都能够找到相应的操作,那么我们可以订阅 Mysql 数据库的 binlog 日志对缓存进行操作。
二进制日志(binnary log)以事件形式记录了对MySQL数据库执行更改的所有操作。
binlog是记录所有数据库表结构变更(例如CREATE、ALTER TABLE…)以及表数据修改(INSERT、UPDATE、DELETE…)的二进制日志。不会记录SELECT和SHOW这类操作,因为这类操作对数据本身并没有修改,但可以通过查询通用日志来查看MySQL执行过的所有语句。
需要注意的一点是,即便update操作没有造成数据变化,也是会记入binlog。
binlog有两个常用的使用场景:
主从复制:mysql replication在master端开启binlog,master把它的二进制日志传递给slaves来达到master-slave数据一致的目的。
数据恢复:通过mysqlbinlog工具来恢复数据。
一种是可以将数据分割到多个RedisServer上,集群;
另一种是使用虚拟内存把那些不经常访问的数据交换到磁盘上。需要特别注意的是Redis并没有使用OS提供的Swap,而是自己实现。
C:consistency,数据在多个副本中能保持一致的状态。
A:Availability,整个系统在任何时刻都能提供可用的服务,通常达到99.99%四个九可以称为高可用
P:Partition tolerance,分区容错性,在分布式中,由于网络的原因无法避免有时候出现数据不一致的情况,系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择,换句话说,系统可以跨网络分区线性的伸缩和扩展。
CAP理论的核心:一个分布式系统不可能同时
很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个
。
CA:单点集群,满足一致性,可用性的系统,通常在可扩展上不太强大。应用:传统的Oracle数据库
CP:满足一致性,分区容错性的系统,通常性能不是特别高。应用:Redis,MongoDB,银行
AP:满足可用性,分区容错性,通常可能对一致性要求低一些。应用:大多数网站架构的选择
Base就是为了解决关系型数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。
Base其实是下面三个术语的缩写:
基本可用(Basically Available)
软状态(Soft state)状态可以有一段时间不同步
最终一致(Eventually consistent)最终数据是一致的就可以了,而不是时时保持强一致
AOF:
RDB:fork出来一份替换
主从模式:从节点分担读压力
哨兵模式:解决单点故障
集群:去中心化,解决数据存储容量不足,分担读、写压力
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:(默认)在内存不足时,直接报错。
主要有一下四种类加载器:
1 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
2 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供 一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
3 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。
4 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。
原子性:事务的不可分割,组成事务的各个逻辑单元不可分割。
一致性:事务执行的前后,数据完整性保持一致。
隔离性:事务执行不应该受到其他事务的干扰。
持久性:事务一旦结束,数据就持久化到数据库中。
查看/设置隔离级别 查看:SELECT @@tx_isolation 设置:set tx_isolation='xxx'
事务的隔离级别 如果不考虑隔离性,引发一些安全问题
隔离性:一个事务的执行,不应该受到其他事务的干扰。
脏读:一个事务读到了另一个事务未提交的数据,导致查询结果不一致 不可重复读:一个事务读到了另一个事务已经提交的update的数据,导致多次查询结果不一致。 虚读/幻读:一个事务读到了另一个事务已经提交的insert的数据,导致多次查询结果不一致。
名称 | 描述 |
---|---|
default | (默认值)(采用数据库的默认的设置) (建议) |
read-uncommited | 读未提交 |
read-commited | 读已提交 (Oracle数据库默认的隔离级别) |
repeatable-read | 可重复读 (MySQL数据库默认的隔离级别) |
serialized-read | 序列化读 |
read uncommitted (读取未提交内容) :脏读,不可重复读,虚读都有可能发生 read committed (读取提交内容) :避免脏读。但是不可重复读和虚读是有可能发生 repeatable read (可重读) :避免脏读和不可重复读,但是虚读有可能发生。 serializable(可串行化) :避免脏读,不可重复读,虚读。
1、PROPAGATION_REQUIRED(required):如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
2、PROPAGATIONNESTED(nested):如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATIONREQUIRED类似的操作
3、PROPAGATION_SUPPORTS(supports):支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
4、PROPAGATION_MANDATORY(mandatory):支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
5、PROPAGATION_REQUIRES_NEW:支持当前事务,创建新事务,无论当前存不存在事务,都创建新事务。
6、PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
7、PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
mysql数据更新是先到 内存 再到 磁盘(mysql)的,因此改完后如果还没写入磁盘就出错的话就会出现 脏数据。
确保事务的持久性。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。
撤销日志 ,在 数据库事务 开始之前,MYSQL会去记录更新前的数据到undo log文件中。如果事务回滚或者数据库崩溃时,可以利用undo log日志中记录的日志信息进行回退。同时也可以提供多版本并发控制下的读(MVCC)。
保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即 非锁定读
MVCC,全称Multi-Version Concurrency Control,即 多版本并发控制 。MVCC是一种并发控制的方法,一般在数据库管理系统 中,实现对数据库的并发访问,在编程语言中实现事务内存。
它主要是用来 处理mysql在多线程操作缓存 数据时出现的一系列 并发问题。
用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。
查询上优化,公司不让设置外键(不让多表查询)有时候为了查询效率,会在表中添加一些冗余字段
看看是不是存在 exist 或者 in 导致全表扫描了
1、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 2、应尽量避免在 where 子句中使用!=或<>操作符,否则将导致引擎放弃使用索引而进行全表扫描。 3、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描, 4、应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描 5、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描 6、任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。 7、索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。 一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
非聚集索引和聚集索引的区别在于:
通过聚集索引可以一次查到需要查找的数据, 而通过非聚集索引第一次只能查到记录对应的主键值 , 再使用主键的值通过聚集索引查找到需要的数据。
聚集索引一张表只能有一个,而非聚集索引一张表可以有多个。
MySQL调优、tomcat调优
结合场景:曾经遇见过sql优化的问题,这个是因为表数据太多,执行sql时间太长,于是我加了索引
配置中心:配置集中化管理,立即生效
@LoadBalanced // 这个注解打开 就会依据 服务名 进行负载均衡 //让ribbon 拦截 RestTemplate 发出的所有的请求 //ribbon 获取 url 中的 service name //从 nacos注册中心 获取 实例列表 //负责从 实例列表中 通过相应的 负载均衡算法 ,获取一个实例 //RestTemplate 请求实例
可以做到使用 HTTP请求远程服务时能与调用本地方法一样的编码体验,开发者完全感知不到这是远程方法,更感知不到这是个 HTTP 请求。同时OpenFeign通过集成Ribbon实现客户端的负载均衡
nacos-server : 注册中心,解决是服务的注册与发现
Ribbon:客户端负载均衡器,解决的是服务集群负载均衡的问题
OpenFeign:声明式 HTTP 客户端 、代替Resttemplate组件,实现远程调用
, {field: 'status', title: '状态',templet: function(d){ if(d.status==0){ return '待通过' }else if(d.status==2){ return '已驳回' }else if(d.status==1){ return '已通过' }else if(d.status==3){ return '已撤销' }
var index = parent.layer.getFrameIndex(window.name); //先得到当前iframe层的索引 parent.layer.close(index); //再执行关闭
var iframeIndex = parent.layer.getFrameIndex(![img](file:///C:\Users\86158\AppData\Roaming\Tencent\QQ\Temp\%W@GJ$ACOF(TYDYECOKVDYB.png)window.name); parent.layer.msg("更新成功"); parent.layer.close(iframeIndex);
localStorage.setItem("token", JSON.stringify(result.data.user));
document.getElementById('time').value = (new Date()).format('yyyy-MM-dd');
指令: 指令 (Directives) 是带有 v- 前缀的特殊 attribute。 指令 attribute 的值预期是单个 JavaScript 表达式 (v-for 是例外情况,稍后我们再讨论)。 指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。 指令: v-model="vuedata" 数据双向绑定 v-bind:htmlattr="vuedata" 属性动态赋值 v-bind的简写 :htmlattr="vuedata" v-on:htmlevent="vuemethods" 事件动态绑定 v-on的简写 @htmlevent = "vuemethods" v-html : 可以解析 html 标签 v-once : 一次性的插值 v-if="vuedata" v-if 指令将根据表达式 vuedata 的值的真假来插入/移除 x 元素。
计算属性有缓存不用每次都调用,而方法每次都需要调用
第一范式:要求数据表中的字段(列)不可再分(原子性)
第二范式:不存在非关键字段对关键字段(主键)的部分依赖
ps: 主要是针对联合主键,非主键不能只依赖联合主键的一部分
联合主键,即多个列组成的主键
第三范式:不存在非关键字段之间的传递依赖
# 看防火墙 systemctl start|stop|restart|enable|disable|status 服务名称 systemctl status iptables # yum下载 yum install -y xxx # 查看端口是否运行 netstat -naop|grep 6379 # 将宿主机的文件复制到容器内部的指定目录 docker cp 文件名称 容器id:容器内部路径 # 将容器内部的文件复制到宿主机 docker cp 容器id:容器目录 宿主机目录 # 只查看后几行 tail 文件名 tail -f 日志 监控日志 # 常用的参数 docker run -d -p 宿主机端口:容器端口 --name 容器名称 镜像的标识|镜像名称[:tag]
6379 redis
9200 elasticSearch
5601 kibana
用户支付时,将支付数据存放在redis中