多态
(了解)解释:同一个行为的多个不同表现形式。
目的:使程序更灵活,扩展性更强。(以不变应万变,usb接口)
(**经常问**)必要条件:
(1)继承或实现接口
(2)父类引用指向子类
(3)重写
(1)==是一个比较运算符,基本数据类型比较的是值,引用数据类型比较的是地址值。(比较地址值即是指是否为同一个对象的引用)
(2)equals()是一个方法,只能比较引用数据类型。重写前比较的是地址值,重写后比一般是比较对象的属性。
(1)ArrayList的底层结构是一个数组。默认长度是10。
(2)当数组被存储占满时触发扩容,新数组长度为原长度的1.5倍。
(3)当数组扩容时,会创建一个原数组长度1.5倍的新数组,然后再把数据从原数组中拷贝到新数组中。
(1)底层数据存储结构是数组+链表+红黑树
(2)数组的默认长度为16
(3)负载因子为0.75,也就是说当使用容量达到总长度的四分之三时,触发扩容,新的数组长度为原数组长度的2倍。
(4)当key发生hash碰撞时,产生链表。当链表的长度大于8并且数组长度大于64时,产生红黑树。
(5)容量小于64,链表长度大于等于8也会触发一次扩容。
(1)单例模式
(2)工厂模式
(3)代理模式:动态代理和静态代理。其中动态代理包括JDK代理和CGLIB代理
饿汉式
(1)在程序启动时创建对象
(2)优点:线程安全的
(3)缺点:影响启动速度。如果这个对象从来被使用过,会导致占用多一点内存
懒汉式
(1)在使用对象的时候才创建对象
(2)优点:不会占用更多内存
(3)缺点:不是线程安全的,因此一般会在创建对象时加锁
(1)判断两个对象是否为同一个对象时,优先判断hashcode是否相同。
(2)如果hashcode相同,会再次调用equals来判断是否为同一个对象。
(3)一般重载equals时,需要先重载hashcode,否则在判断对象的值是否相同时,可能不会被调用。
(4)hashcode的作用是加快查找和比较对象。
(1)接口中的默认方法
(2)函数式接口
(3)Lambda表达式
(4)Stream API
(1)集合(Collect)主要侧重的是数据和存储。
(2)流(Stream)主要侧重的是计算。
(1)通过new关键字创建
(2)通过反射创建
(4)使用克隆【clone()】的方法
(5)通过反序列化方法
(1)JVM,全称Java Virtual Machine(Java虚拟机),是一种用于计算设备的规范,它是一个虚构出来的计算机,引入JVM后,Java语言在不同平台上运行时不需要重新编译。JVM是Java跨平台的核心。
(2)JRE,全称Java Runtime Environment,是指Java的运行环境,是可以在其上运行、测试和传输应用程序的Java平台。
(3)JDK,全称Java Development Kit,是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的Java应用程序。JDK是整个Java开发的核心。
(4)三者关系如下:
JDK = JRE + 开发工具集(例如Javac编译工具等)
JRE = JVM + Java SE标准类库
String | StringBuffer | StringBuilder |
---|---|---|
String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间 | StringBuffer是可变类,是线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量 | 可变类,速度更快 |
不可变 | 可变 | 可变 |
线程安全 | 线程安全 | 线程不安全 |
多线程操作字符串 | 单线程操作字符串 |
处理内容变换时的速度对比:
StringBuilder 》 StringBuffer 》 String
(1)继承Thread类,重写run方法。
(2)实现Runnable接口,重写run方法。
(3)实现Callable接口,重写call方法
(4)通过线程池创建。
(1)corePoolSize 线程池核心线程大小
(2)maximumPoolSize 线程池最大线程数量
(3)keepAliveTime 空闲线程存活时间
(4)unit 空闲线程存活时间单位
(5)workQueue 工作队列
ArrayBlockingQueue
LinkedBlockingQuene
SynchronousQuene
PriorityBlockingQueue
(6)threadFactory 线程工厂
(7)handler 拒绝策略
(1)缓存型线程池:
(2)固定长度线程池
(3)可定时执行的线程池
(4)单线程化线程池
(1)synchronized是java内置关键字,在jvm层面,Lock是个java接口;
(2)synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
(3)synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
(4)用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
(5)synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
(6)lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
(1)start()方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码。
(2)通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。
(3)方法 run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行 run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。
(1)对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于Object 类中的。
(2)sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
(3)在调用 sleep()方法的过程中,线程不会释放对象锁。
(4)而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify() 方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
(5)wait():当线程等待状态为真,其他程序申请线程时,该线程会释放线程锁;如果该线程调用notify()方法,本线程 会进入对象锁定池准备,获取对象锁进入运行状态。
(6)sleep():程序暂停执行指定的时间,释放cpu资源,在调用sleep()方法的过程中,线程不会释放对象锁。当指定时间到了,就会自动恢复运行状态。
(1)notify()随机唤醒一条线程;
(2)notifyAll()唤醒全部的线程。
(1)悲观锁认为对于同一个数据的并发操作一定是会发生修改的,采取加锁的形式,悲观地认为,不加锁的并发操作一定会出问题。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中Synchronized和ReentrantLock等独占锁就是悲观锁思想实现的。
(2)乐观锁正好和悲观锁相反,它获取数据的时候,并不担心数据被修改,每次获取数据的时候也不会加锁,只是在更新数据的时候,通过判断现有的数据是否和原数据一致来判断数据是否被其他线程操作,如果没被其他线程修改则进行数据更新,如果被其他线程修改则不进行数据更新。Lock 是乐观锁的典型实现案例。
(1)原子性
(2)可见性:保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
(3)有序性:通过synchronized和lock来保证有序性
(1)InnoDB存储引擎:提供了良好的事务处理、崩溃修复能力和并发控制。缺点是读写效率较差,占用的数据空间相对较大。
(2)MyISAM存储引擎:优势在于占用空间小,处理速度快。缺点是不支持事务的完整性和并发性。
(3)MEMORY存储引擎:数据读写速度快。缺点是如果重启或者关机,所有数据都会消失。
(1)第一范式(1NF):列不可再分
(2)第二范式(2NF)属性完全依赖于主键
(3)第三范式(3NF)属性不依赖于其它非主属性,属性直接依赖于主键
(1)缺点:占用存储空间、修改效率底
(2)优势:适当的冗余可以提升查询速度
(1)原子性(Atomicity):最小的执行单元,不可分割,事务要么全部被执行,要么全部不执行
(2)一致性(Consistency):数据库从一种正确状态转换成另外一种正确状态,同步更新
(3)隔离性(Isolation):每个事务都是独立存在,互不影响。
(4)持久性(Durability):事务正确提交之后,其结果将永远保存在数据库之中。
(1)read_commited(读已提交)
(2)read_uncommitted(读未提交)
(3)repeatable_read(可重复读)默认
(4)serlalizable(串行化)
(1)脏读:事务A读到了事务B还没有提交的数据
(2)不可重复度(虚读):在同一个事务中前后多次,读出来的数据内容不一致。
(3)幻读:在一个事务里面的操作中发现了未被操作的数据。(前后多次读取,数据总量不一致,常发生在添加 或者 删除)
(1)通过undo log和redo log保证了事务的原子性和持久性
(2)通过mvcc和锁来保证事务的隔离性。
(3)保证了事务的原子性,持久性和隔离性就做到了事务的一致性。
(1)不会阻塞,允许多个线程同时访问,但是只能有一个线程操作成功。
(2)一般会使用版本号或CAS实现,适用于读多写少的场景。
(1)会阻塞,保证同一时刻,只有一个线程能够操作数据,其他线程阻塞挂起。
(2)Java中synchronized,lock,数据库中读锁,写锁等都是悲观锁的实现,适用于读少写多的场景。
(1)读多写少的时候用乐观锁
(2)写多读少的时候用悲观锁
(1)表锁
- 读锁:所有线程只允许对当前表进行读操作,不允许写操作。
- 写锁:只允许当前线程对表进行读写操作,其他线程既不能读也不能写。
(2)行锁
- 共享锁:多个事务可以共同对同一数据加锁,共同使用一把锁,所有事务都可以进行读操作,但是只有当其他事务操作完成(释放锁)之后才能进行写入或更新。
- 排它锁(独占锁):同一数据只能有一个事务进行加锁,当一个事务的操作未完成时候,其他事务可以读取但是不能写入或更新。
(1)问题定位:找出慢查询语句或者CPU飙高的语句
(2)SQL语句分析:通过explain查看SQL的执行计划
(3)解决: 表设计、表索引、SQL语句中的问题
(1)方法一:我们使用的是德鲁伊数据库连接池,他有一个web页面,可以查看哪些语句是慢查询语句。
(2)方法二:开启MySQL的慢查询日志,通过日志也可以查看哪些语句是慢查询语句。
(1)查询条件中索引列使用了函数,比如日期转换,字符串处理,计算,统计等等,会导致索引失效。
(2)模糊查询like以%号开头会导致索引失效。
(3)范围查询可能会导致索引失效,in,or,is null,<>等。
(4)如果数据库中存储的数据很少,mysql的执行引擎认为全表扫描会比索引扫描更快,就不会再去查索引了。
(5)复合索引要遵循最左侧匹配原则,查询条件中如果没有最左侧列会导致索引失效。
Mysql支持多种索引结构类型,哈希表,全文索引,B-Tree,B+Tree等。
(1)哈希表:只有memory存储引擎下支持哈希索引,底层哈希表,存储原理和HashMap类似。
(2)全文索引:MyISAM支持全文索引,生成全文索引非常消耗时间和空间,所以一般不会通过数据库做全文索引,通过sorl或es搜索引擎解决。
(3)B-Tree:B-Tree是多叉平衡树,每个节点保存索引值和数据。
(4)B+Tree:B-Tree的优化,数据保存到叶子节点,非叶子节点存储数据的地址。
(1)主键索引:是一种特殊的唯一索引,在一张表中只能定义一个主键索引。
(2)唯一索引:唯一索引,不允许被索引列数据重复,可以为null,比如:学号,身份证号,手机号,用户名等都可以创建唯一索引。
(3)单值索引:允许被索引列的数据重复,性能比唯一索引,主键索引要差。
(4)复合索引:复合索引和单值索引的区别就是一个复合索引可以包含多个列。
(1)频繁作为查询条件且过滤性好的字段应该创建索引。
(2)查询中与其它表关联的字段,关联字段上建立索引。
(3)排序、分组查询、排序字段、分组字段建议加索引。
(4)单键/复合索引的选择问题, 复合索引性价比更高;
(1)查询条件中必须要包含最左侧的索引列。
(2)查询条件中索引列要连续,中间不能断开。
(1)B+ tree索引主要可以分为两种索引,聚集索引和非聚集索引。
(2)聚集索引:也就是平常我们说的主键索引,在 B+ 树中叶子节点存的是整行数据。
(3)非聚集索引:也叫二级索引,也就是一般的普通索引,在 B+ 树中叶子节点存的是主键的值。
(4)我们如果直接用主键查找,用的是聚集索引,能找到全部的数据。
(5)如果我们是用非聚集索引查找,如果索引里不包含全部要查找的字段,则需要根据索引叶子节点存的主键值,再到聚集索引里查找需要的字段,这个过程也叫做回表。
(1)btree是为了磁盘或其它存储设备而设计的一种多叉平衡查找树(相对于二叉,btree每个内结点有多个分支,即多叉),而b+tree是btree的一个变种,是b+tree在数据库中的一种实现,是最常见也是数据库中使用最为频繁的一种索引。
(2)btree的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;b+tree的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
(3)在btree中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而b+tree中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。
(1)回表:先定位【主键值】,再定位【行记录】,扫描了两次B+树,这就是回表。
(2)避免方法:用覆盖索引可以避免回表。将被查询的字段,建立到联合索引里去。
产生原因
当多个事务同时持有和请求同一资源上的锁而产生循环依赖的时候就产生了死锁。死锁发生在事务试图以不同的顺序锁定资源。以StockPrice表上的两个事务为例:(看下图)
如果不走运的话,每个事务都可以执行完第一个语句,并在过程中锁住资源。然后每个事务都试图去执行第二行语句,当时却发现它被锁住了。两个事务将永远的等待对方完成,除非有其他原因打断死锁。
解决方案
(1)使用更小的事务,尽量避免死锁
(2)修改事务的隔离级别
(3)使用更少的锁定。如果你可以接受允许一个SELECT从一个旧的快照返回数据,不要给它添加FOR UPDATE或LOCK IN SHARE MODE子句。
(1)String(字符串)
(2)Hash(哈希)
(3)List(列表)
(4)Set(集合)
(5)zset(sorted set:有序集合)
它还有三种特殊的数据结构类型(了解)
Geospatial
Hyperloglog
Bitmap
(1)RDB:全量备份,节省磁盘空间,恢复速度快。但是可能会丢失数据。
(2)AOF:日志增量备份,数据保存完整。但是整体的恢复效率低,占空间。
(3)如果数据比较敏感的情况下,建议两个都开启。如果只是做纯缓存,数据不敏感,可以都不开启。
(4)默认开启的时RDB,通常使用默认就行,对于一些数据敏感的场景全部开启,不建议单独使用AOF,AOF在高频读写情况下可能会出现bug。
(1)可以存储,项目中使用的redisTemplate默认的是将Vule值序列化后存储到redis。
(2)也可以将Java对象转成json字符串存储到redis。
(1)StringRedisTemplate继承了RedisTemplate但是两者的数据不能共用,RedisTemplate存储的数据只能通过RedisTemplate获取。
(2)StringRedisTemplate和RedisTemplate最大的区别就是默认采用的序列化策略不同,StringRedisTemplate默认的是String序列化,RedisTemplate默认使用的是JDK的序列化方式。
(3)StringRedisTemplate存储效率比RedisTemplate高,同时存储占用空间小。但是不如RedisTemplate灵活方便。当操作的数据是复杂的Java对象而不想做任何数据类型转换的时候那么建议使用RedisTemplate
(1)设置redis的最大存储空间,建议设置为服务器内存的3/4。
(2)设置redis的内存淘汰策略,设置为volatile-lru或allkeys-lru。
(3)到达redis的最大值触发内存淘汰机制:
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
- allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰)
是针对缓存中没有但数据库有的数据。场景是,当一个热Key失效后,假如瞬间突然涌入大量的请求,来请求同一个Key,这些请求不会命中Redis,都会请求到DB,导致数据库压力过大,甚至扛不住,挂掉。
(1)是指大量的热Key同时失效,对这些Key的请求就会瞬间打到DB上,同样会导致数据库压力过大甚至挂掉。
(2)解决方案:
- 设置KEY永不过期,但缺点是占用大量内存
- 设置KEY的过期时间为一个随机值,这样就能够防止大量的热KEY同时过期
(1)Redis缓存穿透指的是,在Redis缓存和数据库中都找不到相关的数据。也就是说这是个非法的查询,客户端发出了大量非法的查询 比如id是负的 ,导致每次这个查询都需要去Redis和数据库中查询。导致MySql直接爆炸!
(2)解决方案:
- 缓存空对象:如果他的查询数据是合法的,但是确实Redis和MySql中都没有,那么我们就在Redis中储存一个空对象,这样下次客户端继续查询的时候就能在Redis中返回了。但是,如果客户端一直发送这种恶意查询,就会导致Redis中有很多这种空对象,浪费很多空间
- 布隆过滤器:当我们想新增一个元素时(例如新增python),布隆过滤器就会使用hash函数计算出几个索引值,然后将二进制数组中对应的位置修改为1。
(1)Redis 在设计上采用将网络数据读写和协议解析通过多线程的方式来处理,对于命令执行来说,仍然使用单线程操作。
(2)从Redis6 版本开始,已经引入了多线程。
1.noeviction(默认策略):对于写请求不再提供服务,直接返回错误(DEL请求和部分特殊请求除外)
2.allkeys-lru:从所有key中使用LRU算法进行淘汰(LRU算法:即最近最少使用算法)
3.volatile-lru:从设置了过期时间的key中使用LRU算法进行淘汰
4.allkeys-random:从所有key中随机淘汰数据
5.volatile-random:从设置了过期时间的key中随机淘汰
6.volatile-ttl:在设置了过期时间的key中,淘汰过期时间剩余最短的
当使用volatile-lru、volatile-random、volatile-ttl这三种策略时,如果没有key可以被淘汰,则和noeviction一样返回错误
(1)slave节点请求增量同步
(2)master节点判断replid,发现不一致,拒绝增量同步,使用全量同步
(3)master将完整数据生成RDB,发送RDB到slave
(4)slave清空本地数据,加载RDB文件
(5)master将RDB期间产生的命令记录在repl_baklog,并持续性的将repl_baklog发送到slave
(6)slave接收到这些命令,保持与master数据同步。
(1)slave节点请求增量同步
(2)master节点判断replid,如果一致返回continue
(3)从repl_baklog中获取增量数据
(4)发送offset后面的命令
repl_baklog的容量是有上线的,写满后会覆盖最早的数据。如果slave断开的时间太久,导致尚未备份的数据被覆盖(offset会标记repl_baklog中备份的数据),就无法做增量同步,只能去做全量同步。
(1)给Redis设置持久化,这样服务器重启能够快速恢复到工作状态。
(2)搭建Redis集群实现高可用。
我们在项目中使用Redis,肯定不会是单点部署Redis服务的。因为,单点部署一旦宕机,就不可用了。为了实现高可用,通常的做法是,将数据库复制多个副本以部署在不同的服务器上,其中一台挂了也可以继续提供服务。
Redis 实现高可用有三种部署模式:
- 主从模式
- 哨兵模式
- 集群模式。
主从模式中,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址。显然,多数业务场景都不能接受这种故障处理方式。Redis从2.8开始正式提供了Redis Sentinel(哨兵)架构来解决这个问题。
哨兵模式,由一个或多个Sentinel实例组成的Sentinel系统,它可以监视所有的Redis主节点和从节点,并在被监视的主节点进入下线状态时,自动将下线主服务器属下的某个从节点升级为新的主节点。但是呢,一个哨兵进程对Redis节点进行监控,就可能会出现问题(单点问题),因此,可以使用多个哨兵来进行监控Redis节点,并且各个哨兵之间还会进行监控。
简单来说,哨兵模式就三个作用:
(1)发送命令,等待Redis服务器(包括主服务器和从服务器)返回监控其运行状态;
(2)哨兵监测到主节点宕机,会自动将从节点切换成主节点,然后通过发布订阅模式通知其他的从节点,修改配置文件,让它们切换主机;
(3)哨兵之间还会相互监控,从而达到高可用。
哨兵模式基于主从模式,实现读写分离,它还可以自动切换,系统可用性更高。但是它每个节点存储的数据是一样的,浪费内存,并且不好在线扩容。因此,Cluster集群应运而生,它在Redis3.0加入的,实现了Redis的分布式存储。对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题。并且,它也提供复制和故障转移的功能。
Hash Slot插槽算法
既然是分布式存储,Cluster集群使用的分布式算法是一致性Hash嘛?并不是,而是Hash Slot插槽算法。
插槽算法把整个数据库被分为16384个slot(槽),每个进入Redis的键值对,根据key进行散列,分配到这16384插槽中的一个。使用的哈希映射也比较简单,用CRC16算法计算出一个16 位的值,再对16384取模。数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点都可以处理这16384个槽。
集群中的每个节点负责一部分的hash槽,比如当前集群有A、B、C个节点,每个节点上的哈希槽数 =16384/3,那么就有:
节点A负责0~5460号哈希槽
节点B负责5461~10922号哈希槽
节点C负责10923~16383号哈希槽
(1)增加程序的灵活性,可扩展性,动态创建对象。
(2)框架必备,任何框架的封装都要用反射。(框架的灵魂)
(1)Class.forName();
(2)类名.class;
(3)对象.getClass();
(1)new 关键字。
(2)使用类的反射机制
(3)使用类的clone
(4)采用反序列化机制
IoC(Inversion of Control)翻译过来是控制翻转,是一种设计思想,将创建对象的权力交给spring容器去控制,用来创建和管理bean
(1)改变了创建对象的方式和获取资源的方式
容器控制应用程序的对象创建,实例化,和对象之间的依赖
程序不能主动获取外部资源了,而是ioc容器给他注入,
(1)因为程序需要运行这些外部资源,所以依赖注入就是给对象中的属性赋值
(1)setter注入
(2)构造方法注入
(3)基于注解的方式注入
(1)相同点:
@Autowired和@Resource功能相同,都可以用来装配bean;
两个注解可以加载属性字段或写在setter方法上;
(2)不同点:
提供方不同:@Autowired是Spring框架提供,@Resource是Jdk自带
装配方式不同:
@Autowired默认按照byType装配;
@Resource默认按照byName装配,如果匹配不到,则继续使用byType装配;
当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:
singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
session:对于每次HTTP Session,使用session定义的Bean都将产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
globalSession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效
(1)BeanFactory:是一个工厂,IOC的顶级接口,用来管理和获取Bean对象。
(2)FactoryBean:是一个Bean生成工具,是用来获取一种类型对象的Bean,它是构造Bean实例的一种方式。用户可以通过实现该接口定制实例化 Bean 的逻辑。
Bean定义
实例化
属性赋值
初始化
生存期
销毁
Spring是通过三级缓存机制来解决Bean创建的循环依赖问题的,具体如下:
(1)一级缓存,singletonObjects,存储所有已创建完毕的单例 Bean (完整的 Bean)。
(2)二级缓存,earlySingletonObjects,存储所有仅完成实例化,但还未进行属性注入和初始化的 Bean。
(3)三级缓存,singletonFactories,存储能建立这个 Bean 的一个工厂,通过工厂能获取这个 Bean,延迟化 Bean 的生成,工厂生成的 Bean 会塞入二级缓存。
具体流程如下:
(1)A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
(2)B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A
(3)B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A放到一级缓存中。
面向切面编程,一种编程思想,在不改变原有的逻辑的基础上,增加一些额外的功能。
(1)权限验证
(2)日志跟踪
(3)事务
(4)读写分离
(1)前置通知:在我们执行目标方法之前运行(@Before)
(2)后置通知:在我们目标方法运行结束之后,不管有没有异常(@After)
(3)返回通知:在我们的目标方法正常返回值后运行(@AfterReturning)
(4)异常通知:在我们的目标方法出现异常后运行(@AfterThrowing)
(5)环绕通知:非常灵活,目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,joinPoint.procced()就是执行目标方法的代码 。环绕通知可以控制返回对象(@Around)
AOP底层是通过动态代理实现,两种实现方式:
(1)jdk动态代理:如果目标对象实现了接口,spring默认会使用jdk动态代理实现。
(2)cglib动态代理:如果目标对象没有实现接口,spring默认使用cglib代理实现。
可以强制使用cglib
(1)jdk动态代理只能对实现了接口的类生成代理,而不能针对类。cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)。
(2)在低版本的jdk(jdk1.7之前),cglib代理的效率要比jdk动态代理的效率高。在jdk1.7,1.8及之后的版本对jdk动态代理做了优化,jdk动态代理效率要高于cglib代理。
(1)@Controller和@RestController
(2)@RequestMapping
(3)@PathVariable
(4)@RequestParam
(5)@RequestBody
(5)@ResponseBody
(1)no - 这是默认设置,表示没有自动装配。应使用显式 bean 引用进行装配。
(2)byName - 它根据 bean 的名称注入对象依赖项。它匹配并装配其属性与 XML文件中由相同名称定义的 bean。
(3)byType - 它根据类型注入对象依赖项。如果属性的类型与 XML 文件中的一个 bean 名称匹配,则匹配并装配属性。
(4)构造函数- 它通过调用类的构造函数来注入依赖项。它有大量的参数。
(5)autodetect - 首先容器尝试通过构造函数使用 autowire 装配,如果不能,则尝试通过 byType 自动装配。
工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
单例模式:Bean默认为单例模式。
代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新
Set方法注入;
构造器注入:
静态工厂注入;
实例工厂;
@Component:这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。
@Controller:这将一个类标记为 Spring Web MVC 控制器。标有它的 Bean 会自动导入到 IoC 容器中。
@Service:此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。您可以在服务层类中使用 @Service 而不是 @Component,因为它以更好的方式指定了意图。
@Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException。
(1)如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
(2)支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
(3)支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
(4)创建新事务,无论当前存不存在事务,都创建新事务。
(5)以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
(6)以非事务方式执行,如果当前存在事务,则抛出异常。
(7)如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
(1)相同点:
分布式和集群都是需要有很多节点服务器通过网络协同工作完成整体的任务目标。
(2)不同点:
分布式是指将业务系统进行拆分,即分布式的每一个节点都是实现不同的功能。而集群每个节点做的是同一件事情。
能够独立部署,运行,提供功能的系统服务。
(2)思想:
大的系统模块拆分成一个个的小功能模块,独立部署,共同组合成一个大的系统。
微服务是一种特殊的分布式架构,架构思想相同,微服务的拆分力度更细,可以说微服务架构是分布式架构的子集。
(3)优点:
- 灵活,扩展性极强。
- 易于协同开发,责任划分明确。
- 符合“高内聚,低耦合” 的编程思想。
- 不受技术栈限制,方便系统迭代。
(4)缺点
- 运维成本增加。(容器docker)
- 跟踪问题复杂了。(统一的日志,链路追踪)
(1)维度:时间,空间,成本,地域,业务….
(2)简单实用:横向提取,纵向切割。
横向(水平):将系统中公共独立的功能提取出来。
纵向(垂直):将系统按照业务功能模块切分,每个模块就是一个微服务。
(1)目前主流的远程调用技术有基于HTTP的RESTful接口以及基于TCP的RPC协议。
(2)RESTful通讯协议HTTP,经常写的RestController就是RESTful风格,特点是比较灵活,通常用于微服务架构。
(3)RPC通讯协议一般使用TCP,性能较好,不如RESTful灵活,通常用于SOA架构。
Spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接,我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。
特点
(1)Consistency(一致性)
数据一致更新,所有数据的变化都是同步的
(2)Availability(可用性)
在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求
(3)Partition tolerance(分区容忍性)
原理
(1)CAP目标:某个节点的故障,并不影响整个系统的运行
(2)CAP原理:任何分布式系统只可同时满足二点,没法三者兼顾,既然一个分布式系统无法同时满足一致性、可用性、分区容错性三个特点, 所以我们就需要抛弃一样;
(1)Eureka(注册中心)、Nacos
(2)Zuul(网关)、Gateway
(3)Fegin(远程调用)、OpenFeign
(4)Ribbon(负责均衡)
(5)Hystrix(熔断器)
(1)Nacos(配置中心与注册中心)
(2)Sentinel(熔断与限流)
(3)Seata(分布式事务)
相同点
(1)目的是一致的:都是从可用性和可靠性出发,为了防止系统崩溃;
(2)用户体验类似:最终都让用户体验到的是某些功能暂时不可用;
不同点
触发原因不同:
(1)服务熔断一般是某个服务(下游服务)故障引起;所以熔断是被动的。
(2)服务降级一般是从整体负荷考虑,并发太大,先保证整体的可用;所以降级是主动。
相同点
(1)底层都是servlet
(2)两者均是web网关,处理的是http请求
不同点
(1)内部实现不同
gateway对比zuul多依赖了spring-webflux,在spring的支持下,功能更强大,内部实现了限流、负载均衡等,扩展性也更强,但同时也限制了仅适合于Spring Cloud套件
zuul则可以扩展至其他微服务框架中,其内部没有实现限流、负载均衡等。
(2)是否支持异步
zuul仅支持同步
gateway支持异步。理论上gateway则更适合于提高系统吞吐量(但不一定能有更好的性能),最终性能还需要通过严密的压测来决定
@Controller
@RestController
@Service
@RequestMapping
@RequestBody
@PathVariable
@RequestParam
@ResponseBody
@Autowired
@Component
@Bean
(1)顺序消费
(2)定时任务
(3)请求削峰
(4)服务间异步通信
工作模式(Work)
发布订阅模式(Fanout)
路由模式(Routing)
主题模式(Topic)
通过消费端处理,每条消息分配唯一的id,消费端在消费的时候验证,过滤掉重复的消息。
使用乐观锁实现,保证接口的幂等性。
发送端:
(1)开启Confirm模式(消息确认机制或者事务模式)。
(2)如果消息发送失败做补偿处理,记录日志重新发送等,确保消息最终发送成功。
队列:
(1)声明交换机,队列的时候设置持久化参数。
(2)发送消息的时候设置消息的持久化参数,队列消息持久化到磁盘上,即便是宕机,重启之后消息依旧还在。
接收端:
(1)接收端开启手动应答模式。
(2)确定消息处理完成,消费端再向队列确认消费,删除消息。
(3)消费端如果处理异常,记录日志,通过其他补偿机制处理,确保最终消息处理成功。
Direct Exchange:直连交换机,根据Routing Key(路由键)进行投递到不同队列。
Fanout Exchange:扇形交换机,采用广播模式,根据绑定的交换机,路由到与之对应的所有队列。
Topic Exchange:主题交换机,对路由键进行模式匹配后进行投递,符号#表示一个或多个词,*表示一个词。
Header Exchange:头交换机,不处理路由键。而是根据发送的消息内容中的headers属性进行匹配。
#号标识0个字符、1个字符、多个字符
*号表示1个或者多个字符
什么是倒排索引?
倒排索引,倒排索引又叫反向索引(inverted index)。
反向索引
把“文档→单词”的形式变为“单词→文档”的形式。
正向索引
当用户发起查询时(假设查询为一个关键词),搜索引擎会扫描索引库中的所有文档,找出所有包含关键词的文档,这样依次从文档中去查找是否含有关键词的方法叫做正向索引。
(1)区别
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造SqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的SqlSession之间的缓存数据区域(HashMap)是互不影响的。
MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能。
Mybatis中,一级缓存默认是开启的。而二级缓存需要手动开启。
(2)开启
开启一级缓存
默认开启,无需特殊处理
开启二级缓存
在yml中添加以下代码:
mybatis:
configuration:
cache-enabled: true
两者在 MyBatis 中都可以作为 SQL 的参数占位符,在处理方式上不同
#{}:在解析 SQL 的时候会将其替换成 ? 占位符,然后通过 JDBC 的 PreparedStatement 对象添加参数值,这里会进行预编译处理,可以有效地防止 SQL 注入,提高系统的安全性
${}:在 MyBatis 中带有该占位符的 SQL 片段会被解析成动态 SQL 语句,根据入参直接替换掉这个值,然后执行数据库相关操作,存在 SQL注入 的安全性问题
加载、验证、准备、解析、初始化。然后是使用和卸载了
标记-清除:
这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:
1.效率不高,标记和清除的效率都很低;
2.会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次 GC 动作。
复制算法:
为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。
于是将该算法进行了改进,内存区域不再是按照 1:1 去划分,而是将内存划分为8:1:1 三部分,较大那份内存交 Eden 区,其余是两块较小的内存区叫 Survior 区。
每次都会优先使用 Eden 区,若 Eden 区满,就将对象复制到第二块内存区上,然后清除 Eden 区,如果此时存活的对象太多,以至于 Survivor 不够时,会将这些对象通过分配担保机制复制到老年代中。(java 堆又分为新生代和老年代)
标记-整理:
该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。
分代收集:
现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理或者 标记-清除。
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
主要有一下四种类加载器:
当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。
优点
(1)系统类防止内存中出现多份同样的字节码
(2)保证Java程序安全稳定运行
自定义类加载器,继承ClassLoader类,重写loadClass方法和findClass方法;
(1)栈是线程私有的,栈的生命周期和线程一样,每个方法在执行的时候就会创建一个栈帧,它包含局部变量表、操作数栈、动态链接、方法出口等信息,局部变量表又包括基本数据类型和对象的引用;
(2)当线程请求的栈深度超过了虚拟机允许的最大深度时,会抛出StackOverFlowError异常,方法的递归调用有可能会出现该问题;
(3)调整参数-xss去调整jvm栈的大小
强引用
强引用是我们使用最广泛的引用,如果一个对象具有强引用,那么垃圾回收期绝对不会回收它,当内存空间不足时,垃圾回收器宁愿抛出OutOfMemoryError,也不会回收具有强引用的对象;我们可以通过显示的将强引用对象置为null,让gc认为该对象不存在引用,从而来回收它;
软引用
软应用是用来描述一些有用但不是必须的对象,在java中用SoftReference来表示,当一个对象只有软应用时,只有当内存不足时,才会回收它;
软引用可以和引用队列联合使用,如果软引用所引用的对象被垃圾回收器所回收了,虚拟机会把这个软引用加入到与之对应的引用队列中;
弱引用
弱引用是用来描述一些可有可无的对象,在java中用WeakReference来表示,在垃圾回收时,一旦发现一个对象只具有软引用的时候,无论当前内存空间是否充足,都会回收掉该对象;
弱引用可以和引用队列联合使用,如果弱引用所引用的对象被垃圾回收了,虚拟机会将该对象的引用加入到与之关联的引用队列中;
虚引用
虚引用就是一种可有可无的引用,无法用来表示对象的生命周期,任何时候都可能被回收,虚引用主要使用来跟踪对象被垃圾回收的活动,虚引用和软引用与弱引用的区别在于:虚引用必须和引用队列联合使用;在进行垃圾回收的时候,如果发现一个对象只有虚引用,那么就会将这个对象的引用加入到与之关联的引用队列中,程序可以通过发现一个引用队列中是否已经加入了虚引用,来了解被引用的对象是否需要被进行垃圾回收;
GC 是垃 圾收 集的 意思 ,内存 处理 是编 程人 员容 易出 现问 题的 地方 ,忘记 或者 错误的内 存回 收会 导致 程序 或系 统的 不稳 定甚 至崩 溃, Java 提供 的 GC 功能 可以 自动监测 对象 是否 超过 作用 域从 而达 到自 动回 收内 存的 目的 ,Java 语言 没有 提供 释放已分配内存的 显示 操作 方法 。Java 程序 员不 用担 心内 存管 理, 因为 垃圾 收集 器会自动 进行 管理 。要 请求 垃圾 收集 ,可 以调 用下 面的 方法 之一 :System.gc() 或Runtime.getRuntime().gc() ,但 JVM 可以 屏蔽 掉线 示的 垃圾 回收 调用 。
垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在 Java 诞生初期,垃圾回收是 Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今 Java 的垃圾回收机制已经成为被诟病的东。移动智能终端用户通常觉得 iOS 的系统比 Android 系统有更好的用户体验,其中一个深层次的原因就在于 Android 系统中垃圾回收的不可预知性。
MinorGC在年轻代空间不足的时候发生,MajorGC指的是老年代的GC,出现MajorGC一般经常伴有MinorGC。
FullGC有三种情况。
1、 当老年代无法再分配内存的时候
2、 元空间不足的时候
3、 显示调用System.gc的时候。另外,像CMS一类的垃圾回收器,在MinorGC出现promotion failure的时候也会发生FullGC
1、 部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代。
2、 如果对象的大小大于Eden的二分之一会直接分配在old,如果old也分配不下,会做一次majorGC,如果小于eden的一半但是没有足够的空间,就进行minorgc也就是新生代GC。
3、 minor gc后,survivor仍然放不下,则放到老年代
4、 动态年龄判断 ,大于等于某个年龄的对象超过了survivor空间一半 ,大于等于某个年龄的对象直接进入老年代
除了数据运行区,其他区域均有可能造成 OOM 的情况。
堆溢出: java.lang.OutOfMemoryError: Java heap space
栈溢出: java.lang.StackOverflowError
永久代溢出: java.lang.OutOfMemoryError: PermGen space
存放对象实例,所有的对象和数组都要在堆上分配。 是 JVM 所管理的内存中最大的一块区域。
当前线程所执行的行号指示器。是 JVM 内存区域最小的一块区域。执行字节码工作时就是利用程序计数器来选取下一条需要执行的字节码指令。
1、 Serial收集器,串行收集器是最古老,最稳定以及效率高的收集器,但可能会产生较长的停顿,只使用一个线程去回收。
2、 ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本。
3、 Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。
4、 Parallel Old收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程“标记-整理”算法
5、 CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它需要消耗额外的CPU和内存资源,在CPU和内存资源紧张,CPU较少时,会加重系统负担。CMS无法处理浮动垃圾。CMS的“标记-清除”算法,会导致大量空间碎片的产生。
6、 G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器、以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。
比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,他们的执行顺序
先静态、先父后子。
先静态:父静态 > 子静态
优先级:父类 > 子类 静态代码块 > 非静态代码块 > 构造函数
一个类的实例化过程:
1、 父类中的static代码块,当前类的static
2、 顺序执行父类的普通代码块
3、 父类的构造函数
4、 子类普通代码块
5、 子类(当前类)的构造函数,按顺序执行。
6、 子类方法的执行,
Serial 与 Parallel 在 GC 执行的时候都会引起 stop-the-world。它们之间主要不同 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,而parallel 收集器使用多个 GC 线程来执行。
jps:
用来显示本地的 Java 进程,可以查看本地运行着几个 Java 程序,并显示他们的进程号。 命令格式:jps
jinfo:
运行环境参数:Java System 属性和 JVM 命令行参数,Java class path 等信息。 命令格式:jinfo 进程 pid
jstat:
监视虚拟机各种运行状态信息的命令行工具。 命令格式:jstat -gc 123 250 20
jstack:
可以观察到 JVM 中当前所有线程的运行情况和线程当前状态。 命令格式:jstack 进程 pid
jmap:
观察运行中的 JVM 物理内存的占用情况(如:产生哪些对象,及其数量)。 命令格式:jmap [option] pid
不是。当新生代内存不够时,老年代分配担保。而大对象则是直接在老年代分配
设定堆内存大小
1、 -Xmx:堆内存最大限制。设定新生代大小。新生代不宜太小,否则会有大量对象涌入老年代
2、 -XX:NewSize:新生代大小
3、 -XX:NewRatio 新生代和老生代占比
4、 -XX:SurvivorRatio:伊甸园空间和幸存者空间的占比
5、 设定垃圾回收器 年轻代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC