1. Java并发类:
1、ConcurrentHashMap
01、和HashMap功能基本一致,主要是为了解决HashMap线程不安全问题;
02、java7中的基本设计理念就是切分成多个Segment块,默认是16个,也就是说并发度是16,可以初始化时显式指定,后期不能修改,每个Segment里面可以近似看成一个HashMap,每个Segment块都有自己独立的ReentrantLock锁,所以并发操作时每个Segment互不影响;
03、java8中将Segment块换成了Node,每个Node有自己的锁,即每个Node都有自己的并发度;
04、不允许空值和空键,否则会抛出异常;
那ConcurrentHashMap是如何保证数据操作的一致性呢? 对于数据元素的大小,ConcurrentHashMap将对应数组(HashEntry的长度)的变量为voliate类型的,也就是任何HashEntry发生变更,所有的地方都会知道数据的大小。对于元素,如何保证我取出的元素的next不发生变更呢?(HashEntry中的数据采用链表存储,当读取数据的时候可能又发生了变更),这一点,ConcurrentHashMap采取了最简单的做法,hash值、key和next取出后都为final类型的,其next等数据永远不会发生变更。
另外ConcurrentHashMap采用的锁结构是将读和写分开的,大大的提升了性能。
2.CopyOnWriteArrayList CopyOnWriteArrayList这是一个ArrayList的线程安全的变体,其原理大概可以通俗的理解为:初始化的时候只有一个容器,很常一段时间,这个容器数据、数量等没有发生变化的时候,大家(多个线程),都是读取(假设这段时间里只发生读取的操作)同一个容器中的数据,所以这样大家读到的数据都是唯一、一致、安全的,但是后来有人往里面增加了一个数据,这个时候CopyOnWriteArrayList 底层实现添加的原理是先copy出一个容器(可以简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据。
优点:
1.解决的开发工作中的多线程的并发问题。
缺点:
1.内存占有问题:很明显,两个数组同时驻扎在内存中,如果实际应用中,数据比较多,而且比较大的情况下,占用内存会比较大,针对这个其实可以用ConcurrentHashMap来代替。
2.数据一致性:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器
2.Spring的IOC以及AOP.
IOC(DI):把对象的创建权限交给Spring容器,让spring帮我们实例化对象,我们只是从spring容器中取得实例。
AOP:把一些非核心业务的代码抽取到一个通知类(增强),再创建需要被增强的类的代理对象,在调用代理对象的方法时,织入增强代码,并调用目标方法的一种面向切面技术,一种对OOP进行补充的编程方式。
3.Spring的Bean的加载过程.
Spring通过资源加载器加载相应的XML文件,使用读取器读取资源加载器中的文件到读取器中,在读取过程中,解析相应的xml文件元素,转化为spring定义的数据结BeanDefinition,把相应的BeanDefinition注册到注册表中。注册表中包含的BeanDefinition的数据结构,没有经过加工处理过,无法得到我们想要的bean对象。 我们如何得到Bean对象,spring都做了那些工作?BeanFactory提供了多种方式得到bean对象,getBean()方法是最核心得到bean对象 getBean主要由AbstractBeanFactory、AbstractAutowireCapableBeanFactory、以及DefaultListableBeanFactory实现 AbstractBeanFactory 实现了依赖关系处理 AbstractAutowireCapableBeanFactory 实现了bean的create过程 DefaultListableBeanFactory 实现了BeanDefinition的管理
以下是getBean方法的实现流程。
getBean经过方法重载后,最终调用的是doGetBean方法, 需要的方法参数如下: 1.name 你要得到bean对象的名称 不能为空 2.requiredType 这个bean对象的Class类型,可以为null 3.args 可以为null,如果有参数,则代表在找到这个bean定义后,通过构造方法或工厂方法或其他方法传入args参数来改变这个bean实例。 spring 工厂开始自动化处理了.
4. Spring的循环依赖处理方式.
所谓Spring的循环依赖,指的是这样一种场景:
当我们注入一个对象A时,需要注入对象A中标记了某些注解的属性,这些属性也就是对象A的依赖,把对象A中的依赖都初始化完成,对象A才算是创建成功。那么,如果对象A中有个属性是对象B,而且对象B中有个属性是对象A,那么对象A和对象B就算是循环依赖,如果不加处理,就会出现:创建对象A–>处理A的依赖B–>创建对象B–>处理B的对象A–>创建对象A–>处理A的依赖B–>创建对象B…这样无限的循环下去。
这事显然不靠谱。
Spring处理循环依赖的基本思路是这样的:
虽说要初始化一个Bean,必须要注入Bean里的依赖,才算初始化成功,但并不要求此时依赖的依赖也都注入成功,只要依赖对象的构造方法执行完了,这个依赖对象就算存在了,注入就算成功了,至于依赖的依赖,以后再初始化也来得及(参考Java的内存模型)。
因此,我们初始化一个Bean时,先调用Bean的构造方法,这个对象就在内存中存在了(对象里面的依赖还没有被注入),然后把这个对象保存下来,当循环依赖产生时,直接拿到之前保存的对象,于是循环依赖就被终止了,依赖注入也就顺利完成了。
举个例子:
假设对象A中有属性是对象B,对象B中也有属性是对象A,即A和B循环依赖。
创建对象A,调用A的构造,并把A保存下来。 然后准备注入对象A中的依赖,发现对象A依赖对象B,那么开始创建对象B。 调用B的构造,并把B保存下来。 然后准备注入B的构造,发现B依赖对象A,对象A之前已经创建了,直接获取A并把A注入B(注意此时的对象A还没有完全注入成功,对象A中的对象B还没有注入),于是B创建成功。 把创建成功的B注入A,于是A也创建成功了。 于是循环依赖就被解决了。
5.缓存穿透与缓存击穿问题
5.1缓存穿透(数据查询不到—>假数据、set集合放ID布隆过滤器过滤)
解决方案(防止mysql宕机) 在Redis中放入 1.假数据 2.set集合,里面放入所有mysql中的id,再通过布隆过滤器过滤,没有这个id的请求就不在mysql中找了
5.2、缓存击穿(热点数据到期—>访问一次加一次访问时间、加锁)
解决方案 1.从Redis处理:一个请求,给这个热点数据加一点时间(避免热点数据过期) 2.分布式锁:Tomcat集群synchronized-Tomcat分布式锁-Redis(避免大量数据访问数据库)
6、MySql存储引擎
mysql支持存储引擎有好几种,咱们这里主要讨论一下常用的几种存储引擎。Innodb,myisam
索引是 MySQL数据库很重要的一部分,它对数据表查询性能的好坏起着决定性的作用,尤其是对大表作用更加明显。 作为索引中最为常见的一种类型,B-Tree索引大都采用的是 B Tree数据结构来存储数据(NDB集群存储引擎内部实际上采用 T-Tree结构存储这种索引)。B-Tree通常也意味着所有的值都是按顺序存储的。 大多数的 MySQL引擎都支持这种索引,而不同的存储引擎以不同的方式来实现 B-Tree索引,性能方面各有优劣。
1.INNODB索引实现
与 MyISAM相同的一点是,InnoDB 也采用 B Tree这种数据结构来实现 B-Tree索引。而很大的区别在于,InnoDB 存储引擎采用“聚集索引”的数据存储方式实现B-Tree索引,所谓“聚集”,就是指数据行和相邻的键值紧凑地存储在一起,注意 InnoDB 只能聚集一个叶子页(16K)的记录(即聚集索引满足一定的范围的记录),因此包含相邻键值的记录可能会相距甚远。
在 InnoDB 中,表被称为 索引组织表(index organized table),InnoDB 按照主键构造一颗 B Tree (如果没有主键,则会选择一个唯一的并且非空索引替代,如果没有这样的索引,InnoDB则会隐式地定义一个主键来作为聚集索引),同时叶子页中存放整张表的行记录数据,也可以将聚集索引的叶子节点称为数据页,非叶子页可以看做是叶子页的稀疏索引。
全表扫描: 当InnoDB做全表扫描时并不高效,因为 InnoDB 实际上并没有顺序读取,在大多情况下是在随机读取。做全表扫描时,InnoDB 会按主键顺序扫描页面和行。这应用于所有的InnoDB 表,包括碎片化的表。如果主键页表没有碎片(存储主键和行的页表),全表扫描是相当快,因为读取顺序接近物理存储顺序。但是当主键页有碎片时,该扫描就会变得十分缓慢
行级锁 提供行锁(locking on row level),提供与 Oracle 类型一致的不加锁读取(non-locking read in SELECTs),另外,InnoDB表的行锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表,例如update table set num=1 where name like “�a%”
2.MyISAM索引的实现 每个MyISAM在磁盘上存储成三个文件。 树中叶子保存的是对应行的物理位置。通过该值,存储引擎能顺利地进行回表查询,得到一行完整记录。同时,每个叶子页也保存了指向下一个叶子页的指针。从而方便叶子节点的范围遍历。 而对于二级索引,在 MyISAM存储引擎中以与上图同样的方式实现,这也说明了 MyISAM的索引方式是“非聚集的”,与 Innodb的“聚集索引”形成了对比
MyISAM 默认会把索引读入内存,直接在内存中操作;
小结:Innodb强调多功能性,支持的拓展功能比较多,myisam主要侧重于性能
区别 InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务; InnoDB是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而MyISAM是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。 InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快; Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高;
如何选择 是否要支持事务,如果要请选择innodb,如果不需要可以考虑MyISAM; 如果表中绝大多数都只是读查询,可以考虑MyISAM,如果既有读写也挺频繁,请使用InnoDB。 系统奔溃后,MyISAM恢复起来更困难,能否接受; MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM),说明其优势是有目共睹的,如果你不知道用什么,那就用InnoDB,至少不会差。
7.Mybatis的一级缓存和二级缓存的区别?
Mybatis的一级缓存和二级缓存的区别
一级缓存(默认开启) SqlSession级别的缓存,实现在同一个会话中数据的共享. 基于sqlSession默认开启,在操作数据库时需要构造SqlSession对象,在对象中有一个HashMap用于存储缓存数据。不同的SqlSession之间的缓存数据区域是互相不影响的。 二级缓存 SqlSessionFactory级别的缓存,实现不同会话中数据的共享,是一个全局变量。 不同的sqlSession两次执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数据写到缓存,第二次查询会从缓存中获取数据,不再去底层数据库查询,从而提高效率。
8.HashMap
HashMap刚创建时,table是null,为了节省空间,当添加第一个元素时,table容量 调整为16,当元素个数大于阈值(16*0.75=12)时,会进行扩容,扩容后大小为原来的2倍。目的是 减少调整元素的个数,jdk1.8 当每个链表长度大于8,并且数组元素个数大于等于64时,会调整为红黑树,目 的提高执行效率,jdk1.8 当链表长度小于等于6时,调整成链表,jdk1.8以前,链表是头插入(容易在扩容的时候,形成循环链表),jdk1.8以后是尾插入(避免循环链表)
HashMap的安全问题
代码语言:javascript复制jdk1.7及之前的HashMap
在HashMap扩容的是时候会调用resize()方法中的transfer()方法,在这里由于是头插法所以在多线程情况下可能出现循环链表,所以后面的数据定位到这条链表的时候会造成数据丢失。和读取的可能导致死循环。
代码语言:javascript复制jdk1.8HashMap
1.8的HashMap对此做了优化,resize采用了尾插法,即不改变原来链表的顺序,所以不会出现1.7的循环链表的问题。但是它也不是线程线程安全的。不安全性如下:
在多线程情况下put时计算出的插入的数组下标可能是相同的,这时可能出现值的覆盖从而导致size也是不准确的。
9. final , finally , finalize区别
final修饰成员变量,成员方法,类 finally修饰代码块 finalize是Object类中的一个方法 -> 复活币 × 1
10.static关键字修饰什么
内部类 方法 变量 代码块 导包
11.volatile关键字
1.被volatile修饰的变量保证对所有线程可见。 2.禁止指令重排优化
12.CAS乐观锁(比较和交换)
CAS介绍(compare and swap比较和交换): CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS是一种非阻塞式的同步方式。 CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。 CAS导致ABA问题:添加版本 线程1准备用CAS将变量的值由A替换为B,在此之前,线程2将变量的值由A替换为C,又由C替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了,尽管CAS成功,但可能存在潜藏的问题,如果CAS次数过多,会额外损耗CPU性能 解决ABA问题:增加版本号
13.GC的垃圾回收
1.JVM内存分代模型 年轻代和老年代 JVM将堆内存划分为了两个区域,即年轻代和老年代。年轻代主要存放的是创建和使用完即将被回收的对象,老年代存放的是一些长期被使用的对象。 2.确定是否回收
代码语言:javascript复制1、引用计数算法
判断对象的引用数量
通过判断对象的引用数量来决定对象是否可以被回收;
每个对象实例都有一个引用计数器,被引用则 1,完成引用则-1;
任何引用计数为0的对象实例可以被当作垃圾收集。
优点:执行效率高,程序执行受影响较小。
缺点:无发检测出循环引用的情况,导致内存泄露。
代码语言:javascript复制2.可达性分析算法
有一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径被称
为“引用链”。
如果一个对象到GC Roots没有任何引用链相连接时,说明这个对象是不可用的。如果一个对象
到GC Roots有引用链相连接时,说明这个对象是可用的。
也就是说,给定一个集合的引用作为根节点出发,通过引用关系遍历对象图,能够遍历到的对象
就被判定为存活,不能够被遍历到的对象说明对象死亡。
3.常用垃圾算法
代码语言:javascript复制1.复制算法(新生代回收算法)
复制算法主要运用在新生代中,把新生代内存划分为两块内存区域,然后只使用其中一块,待
那块内存快满的时候,就把里面的存活对象一次性转移到另外一块内存区域,然后回收原来那
块的垃圾对象。
整个垃圾回收的流程就是,刚开始创建的对象都是在Eden区域的,如果Eden区域快满了,就会
触发垃圾回收。Eden区把存活的对象都转移到空着的S1区域,接着Eden区就会被请客。然后再
次分配对象到Eden区中直到满了进行下一次垃圾回收,这时会将S1中存活的对象和Eden区存活
的对象转移到空的Survivor区中(S2) 。这就是为什么新生代会被划分为8:1:1的结构了,
这样对内存的利用率会大大提升。
代码语言:javascript复制2.标记清除法
标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,
再扫描整个空间中未被标记的对象,进行回收,如下图所示。标记-清除算法不需要进行对
象的移动,只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标
记-清除算法直接回收不存活的对象,因此会造成内存碎片。
代码语言:javascript复制3.标记整理算法(老年代回收算法)
标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活
的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整
理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存
碎片的问题。
可以作为GC Root的对象 虚拟机栈中引用的对象(栈帧中的本地变量) 方法区中的常量引用的对象 方法区中的类静态属性引用的对象 本地方法中JNI(native方法)的引用对象 活跃线程的引用对象
15.class文件是如何加载到JVM内存的
各个加载器的工作责任:
1)Bootstrap ClassLoader:负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C 实现,不是ClassLoader子类
2)Extension ClassLoader:负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
3)App ClassLoader:负责记载classpath中指定的jar包及目录中class
工作过程:
代码语言:javascript复制1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载
请求委派给父类加载器ExtClassLoader去完成。
2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加
载请求委派给BootStrapClassLoader去完成。
3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class)
,会使用ExtClassLoader来尝试加载;
4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载
5、如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException
这就是所谓的双亲委派模型。简单来说:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。
好处:防止内存中出现多份同样的字节码(安全性角度)
特别说明:类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。
类加载详细过程
代码语言:javascript复制加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象。
连接,连接又包含三块内容:验证、准备、解析。
1)验证,文件格式、元数据、字节码、符号引用验证;
2)准备,为类的静态变量分配内存,并将其初始化为默认值;
3)解析,把类中的符号引用转换为直接引用
初始化,为类的静态变量赋予正确的初始值。
16.JVM内存模型(待完善)
代码语言:javascript复制1.程序计数器(PC寄存器):
程序计数器是一块较小的内存空间,是当前线程正在执行的哪一条字节码指令的地址,若当前
线程正在执行的是一个本地方法
代码语言:javascript复制2.Java虚拟机栈(待完善)
描述Java方法运行过程的内存模型,保存局部变量、基本数据类型以及堆内存中对象的引用变量
随着线程创建而创建,随着线程的结束而销毁
代码语言:javascript复制3.本地方法栈
为JVM提供使用native方法的服务,本地方法栈描述本地方法运行过程的内存模型
也会抛出StackOverFlowError和OutOfMemoryError异常
代码语言:javascript复制4.堆
线程共享、垃圾回收的主要场地,在虚拟机启动的时候就被创建
堆这块区域是JVM中最大的,堆内存的大小是可以调节的
代码语言:javascript复制5.方法区
线程共享、 存储的是类信息 普通常量 静态常量 编译器编译后的代码等,
常量池(Constant Pool)是方法区的一部分
16.1虚拟机栈如何执行方法的
16.2方法区在JDK各个版本的更新
17.SQL语句的优化
sql本身优化,避免全盘扫描
18.聚簇索引和非聚簇索引
主键索引: 其他索引:
19.索引的常见结构
hash:数据量不大,效率远高于BTree
BTree,B Tree:数据量大,更加稳定 树高问题,叶子节点指针
20.ES保存数据后,为什么不能立即查看到
我们经常有这样的需求,在对 Elasticsearch 数据进行操作的时候,要及时返回刚刚操作完毕的数据,或者数据列表。
比如加入存储一条数据后,我马上要返回数据的总条数,这个时候,会出问题,Elasticsearch会返回操作之前的数据,也就是假如开始有500条数据,我Insert了一条进去,按道理来说应该是501条,但是这个时候查询会发现,只有500条数据,再次请求又得到501条数据。
代码语言:javascript复制BulkRequestBuilder bulkRequest = ESTools.client.prepareBulk().setRefresh(true);
这里的setRefresh(true);
就是自动刷新的用处。所以在我们CRUD的时候,如果对数据增删改操作的时候,如果要及时返回最新数据,那么我们就需要加这个方法,及时刷新数据。
21.SpringMVC运行流程
22.SpringBoot自动装配
@SpringBootApplication中有一个@import的注解,这个注解里面加载一个
里面有一个selectImports方法,将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中。
会从META-INF/spring.factories中获取资源,然后通过Properties加载资源:
Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作。以前我们需要自己配置的东西,自动配置类都帮我们完成了。
23.Redis集群架构
(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽;
(2)节点的Fail是通过集群中超过半数的节点检测失效时才生效;
(3)客户端与redis节点直连,不需要中间proxy层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可;
(4)redis-cluster把所有的物理节点映射到[0-16383]slot(插槽)上,cluster 负责维护node<->slot<->value。