《“疫情”结束后的Java求职路-技术篇》

背景概述

"疫情"即将结束, 社会经济也在逐步复苏, 在这期间积累了接近4个月求职大军即将涌入市场,公司企业也陆续逐渐宣布复工,这是一个机遇与竞争并存的时期,势必会影响求职难度,只会简单CRUD就能拿到多个offer的时代已经过去了。不过大家也不用过于担心,像阿里、腾讯、美团、头条、滴滴、蚂蚁金服等等这样的一线企业的用人需求依旧有增无减。而且很多传统行业也发现了自己的行业壁垒,经过这次的“疫情”也会加速各个传统行业的互联网化。5G时代的到来,国家的数字货币政策,会让互联网企业变成一个百花齐放的时代。从信息传输、软件和信息技术服务业,金融业,文化、体育和娱乐业,房地产业及居民服务、维修和其他服务业缺口排名前5名。可见,IT行业是最“缺人”的行业。

那么关于面试如何在众多的求职者中脱颖而出,拿到一个满意的offer呢?
分享内容

  • JVM面试高频及优化
  • MySQL数据库面试高频及优化
  • Java并发编程面试高频
  • 源码分析

1_JVM面试高频及优化

概述

Java中核心中的核心,所有的java代码
框架都必须运行在JVM虚拟机中,面试大厂必问的知识点,对于JVM的面试题基本会从以下几个方面去提问:

JVM的组成及作用 JVM中的类加载机制 JVM中的运行时数据区 JVM中的垃圾回收机制 JVM性能监控与性能调优

1.1面试题 jvm的组成

类加载器(ClassLoader) 运行时数据区(Runtime Data Area) 执行引擎(Execution Engine)
本地库接口(Native Interface)
在这里插入图片描述
首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,
运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),
将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能

1.2面试题 JVM中的类加载机制

1.21JVM中的类加载机制的作用

- 负责将 Class 加载到 JVM 中
- 将 Class 字节码重新解析成 JVM 统一要求的对象格式

1.22JVM中类加载的时机

1.在遇到 new、putstatic、getstatic、invokestatic 字节码指令时,如果类尚未初始化,则需要先
触发初始化。    
	
2.对类进行反射调用时,如果类还没有初始化,则需要先触发初始化。
  	
3.初始化一个类时,如果其父类还没有初始化,则需要先初始化父类。
	
4.虚拟机启动时,用于需要指定一个包含 main() 方法的主类,虚拟机会先初始化这个主类。
	
5.当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 
REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,
并且这个方法句柄所对应的类还没初始化,则需要先触发初始化。

1.23JVM中类加载器的分类

启动类加载器(BootStrap ClassLoader,又称根加载器)

每次执行 java 命令时都会使用该加载器为虚拟机加载核心类。
该加载器是由 native code 实现,而不是 Java 代码,加载类的路径为 <JAVA_HOME>/jre/lib。
特别的 <JAVA_HOME>/jre/lib/rt.jar 中包含了 sun.misc.Launcher 类,
而 sun.misc.Launcher$ExtClassLoader 和 sun.misc.Launcher$AppClassLoader 
都是 sun.misc.Launcher的内部类,所以拓展类加载器和系统类加载器都是由启动类加载器加载的。

扩展类加载器(Extension ClassLoader)

用于加载拓展库中的类。拓展库路径为<JAVA_HOME>/jre/lib/ext/。
实现类为 sun.misc.Launcher$ExtClassLoader。

应用程序类加载器(System ClassLoader)

负责加载用户classpath下的class文件,又叫系统加载器,其父类是Extension。
它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类
,是用户自定义加载器的默认父加载器。
实现类为 sun.misc.Launcher$AppClassLoader

自定义类加载器

用户自定义类加载器,继承自System ClassLoader

1.24JVM中类加载的过程

加载

基本概念:该过程完成查找并加载类的class文件。该class文件可以来自本地磁盘或者网络等。

验证

基本概念:确保类型的正确性,比如class文件的格式是否正确、语义是否符合语法规定、字节码是否
可以被JVM安全执行等
验证总体上分为4个阶段: 文件格式验证、元数据验证、字节码验证、符号引用验证。

准备

基本概念:为类的静态变量分配内存,并赋初值。基本类型的变量赋值为初始值,
比如int类型的赋值为0,引用类型赋值为null。

static  int a = 100

“准备”阶段是正式为类变量(仅仅是类变量,即 static 修饰的变量)分配内存并设置类变量初始值
(除了 final 变量初始值是数据类型的零值,并不是类构造器<clinit> 方法中的初始值)的阶段
,这些变量所使用的内存都将在方法区中进行。

解析

基本概念:将符号引用转为直接引用。比如方法中调用了其他方法,方法名可以理解为符号引用,
而直接引用就是使用指针直接引用方法。

”解析“阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,
主要针对 类或接口、字段、类方法、接口方法、方法类型、方法句柄 和 调用限定符 7类符号引用进行。

初始化

基本概念:初始化,则是为标记为常量值的字段赋值的过程。
换句话说,只对static修饰的变量或语句块进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。

static  int a = 100
static {
    // 只会执行一次
}

1.25JVM中类加载的双亲委托模型

基本概念

如果一个类加载器收到了加载类的请求,它首先将请求交由父类加载器加载;若父类加载器加载失败,当前类加载器才会自己加载类。

在这里插入图片描述
作用

像java.lang.Object这些存放在rt.jar中的类,无论使用哪个类加载器加载,最终都会委


java.lang.Object.class  ext   java.lang.Object.class 对象
                        app   java.lang.Object.class 对象

java.lang.Object.class != java.lang.Object.class

派给最顶端的启动类加载器加载,从而使得不同加载器加载的Object类都是同一个。  

双亲委派模型的代码在java.lang.ClassLoader类中的loadClass函数中实现:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 检查类是否被加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                	//若未加载,则调用父类加载器的loadClass方法
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    	// 没有父类则调用 BootstrapClassLoader
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 若该方法抛出ClassNotFoundException异常,表示父类加载器无法加载
                }
                if (c == null) {
                    // 则当前类加载器调用findClass加载类
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

过程小结

1.首先检查类是否被加载;
2.若未加载,则调用父类加载器的loadClass方法;
3.若该方法抛出ClassNotFoundException异常,表示父类加载器无法加载,则当前类加载器调用findClass加载类;
4.若父类加载器可以加载,则直接返回Class对象。

1.3面试题 JVM的运行时数据区

1.31运行时数据区-程序计数器

线程私有。可看作是**当前线程所执行的字节码的行号指示器**,字节码解释器的工作是通过改变这个计数值
来读取下一条要执行的字节码指令。

多线程是通过线程轮流切换并分时配处理器执行时间来实现的,任何一个时刻,一个内核只能执行
一条线程中的指令。**为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器**。
这就是一开始说的“线程私有”。如果线程正在执行的方法是Java方法,
计数器记录的是虚拟机字节码的指令地址;如果是Native方法,计数器值为空。

1.32运行时数据区-Java虚拟机栈

线程私有,生命周期和线程相同。Java虚拟机栈描述的是Java方法的内存模型:每个方法在执行时都会
创建一个栈帧,存储**局部变量表、操作数栈、动态链接、方法出口信息**,每一个方法从调用到结束,
就对应这一个栈帧在虚拟机栈中的进栈和出栈过程。局部变量表保存了各种基本数据类型
(int、double、char、byte等
)、对象引用(不是对象本身)和returnAddress类型(指向了一条字节码地址)。

* 方法执行的过程:
  方法被调用前:创建栈帧
  方法执行:栈帧入栈
  方法执行后:栈帧出栈

栈的空间大小设置:  -Xss 为jvm启动的每个线程分配的内存大小
  • 线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError;
  • 虚拟机栈扩展时无法申请到足够的内存,抛出OutOfMemoryError。

1.33运行时数据区-本地方法栈

上述虚拟机栈为JVM执行Java方法服务,本地方法则为执行Native服务。其他和虚拟机栈类似,
也会抛出StackOverflowError、OutOfMemoryError。

1.34运行时数据区-Java堆

常说的“栈内存”、“堆内存”,其中前者指的是虚拟机栈,后者说的就是Java堆了。**Java堆是被线程共享的**。
在虚拟机启动时被创建。Java堆是Java虚拟机所管理的内存中最大的一块。Java堆的作用是存放对象实例,
Java堆可以处于物理上不连续的内存空间中,只要求逻辑上连续即可。
	Java堆是垃圾收集器管理的主要区域,因此很多时候也被称作"GC堆",从内存回收的角度看,
	现在收集器都基本采用分代回收的算法 所以Java堆呢还可以细分为:新生代、老年代。  
	在细致一点的有:Eden空间、From Survivor空间、To Survivor空间。   

堆的空间大小设置:   -Xms java堆启动时内存   -Xmx  java堆可扩展的最大内存
内存泄漏:Memory Leak  一个无用的对象,应该被回收,却因为某种原因一直未被回收
内存溢出:Memory Overflow  对象确实都应该活着,这个时候内存不够用了

1.35运行时数据区-方法区

也被称为永久代 1.7 
也被称为元数据空间 1.8

是线程共享的区域。存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据。
方法区无法满足内存分配需求时,抛出OutOfMemoryError。JVM规范被没要求这个区域需要实现垃圾收集,
因为这个区域回收主要针对的是类和常量池的信息回收,回收结果往往难以令人满意。

运行时常量池:是方法区的一部分。Java语言不要求常量只能在编译期产生,
换言之,在运行期间也能将新的常量放入。

方法区空间大小设置: -XX:PermSize -XX:MaxPermSize
1.8之后设置: -XX:MetaspaceSize -XX:MaxMetaspaceSize

1.4面试题 GC垃圾回收机制

1.41GC垃圾回收-垃圾对象的判断

在Java堆中存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还存活着,哪些已经死去(即不可能在被任何途径使用的对象)

如何判断对象是否死亡,主要有两种算法: 引用计数法可达性分析算法

主流的商业虚拟机基本使用的是 可达性分析算法

可达性分析算法

基本思路:通过系列的称为 GC Roots 的对象作为起始点,从这些节点开始向下搜索,
搜索走过的路径成为引用连,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可达的,
会被判断为可回收的对象。
如图: 对象5,6,7 虽然互相有关联,但是他们到GC Roots是不可达的,所以它们会被判定为可回收的对象。

在这里插入图片描述
可作为GC Roots节点的对象**

虚拟机栈中 (栈帧中的本地变量表) 引用的对象 
方法区中类静态属性引用的对象
方法区中常量的引用对象
本地方法栈中引用的对象

1.42GC垃圾回收-垃圾收集算法

标记-清除算法

标记—清除算法是最基础的收集算法,过程分为标记和清除两个阶段,首先标记出需要回收的对象,之后由虚拟机统一回收已标记的对象。这种算法的主要不足有两个:
1、效率问题,标记和清除的效率都不高
2、空间问题,对象被回收之后会产生大量不连续的内存碎片,当需要分配较大对象时,由于找不到合适的空闲内存而不得不再次触发垃圾回收动作

复制算法

为了解决效率问题,复制算法出现了。算法的基本思路是:**将内存划分为大小相等的两部分,每次只使用其中一半,当第一块内存用完了,就把存活的对象复制到另一块内存上,然后清除剩余可回收的对象,这样就解决了内存碎片问题。我们只需要移动堆顶指针,按顺序分配内存即可,简单高效。**但是算法的缺点也很明显:
1、它浪费了一半的内存,这太要命了。
2、如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可忽视。

标记-整理算法

根据老年代的特点,有人提出了另一种改进后的“标记—清除”算法:标记—整理算法。
标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。

分代收集算法

现代商业虚拟机垃圾收集大多采用分代收集算法。主要思路是根据对象存活生命周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,然后根据各个年代的特点采用最合适的收集算法。新生代中,对象的存活率比较低,所以选用复制算法,老年代中对象存活率高且没有额外空间对它进行分配担保,所以使用“标记-清除”或“标记-整理”算法进行回收
在这里插入图片描述

分代收集算法垃圾回收小结

1.对象是在Eden区进行分配,如果Eden区没有足够空间时触发一次 Minor GC
JVM提供 -XX:+PrintGCDetails这个收集器日志参数
2.占用内存较大的对象,对于虚拟机内存分配是一个坏消息,虚拟机提供了一个 -XX:PretenureSizeThreshold
让大于这个设置的对象直接存入老年代。
3.长期存入的对象会存入老年代。
	虚拟机给每个对象定义了一个Age年龄计数器,对象在Eden中出生并经过第一次Minor GC后仍然存活,对象年龄+1,此后每熬过一次Minor GC则对象年龄+1,当年龄增加到一定程度默认15岁,就会晋升到老年代。 、
	可通过参数设置晋升年龄 -XX:MaxTenuringThreshold 
 Minor GC 和 Full GC的区别
 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,Minor GC非常频繁,一般回收速度也很快
 老年代GC(Full GC/Major GC):指发生在老年代的GC,出现Full GC 一般会伴随一次 Minor GC,Full GC的速度要慢很多,一般要比Minor GC慢10倍

1.43GC垃圾回收-垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图是HotSpot虚拟机所包含的所有垃圾收集器:在这里插入图片描述

1.5面试题 JVM监控及故障分析

上面的理论知识,除了面试官会问之外,我们的实际工作中也可能会用到

在JDK的安装包中给我们提供了很多工具,工具配合理论知识可以帮助我们解决很多生产环境的问题。 在面试中一些特别有心的面试官也会侧重考量我们这方面的问题:
如: 面试官问 - 你有没有解决过一些生产环境下的bug问题啊?

如果回答没有,那显然在面试官心理对我们的评价会大打折扣 

回答有, 从tomcat下的log目录使用tail -f xxx.log查看是什么问题,根据问题排查。 这样虽然能勉强过关,

但是不好的点:
	1.什么问题都没说清
	
	2.直接查tomcat日志没体现出技术水平
	
	3.如果你用的微服务,基本上都有日志的统一处理方案

在这里呢,我们给大家提供3种常见的问题,并配合JDK工具进行排查和分析

  1. 内存溢出问题
    JDK工具: jconsole 、 jmap 、 jprofiler
  2. CPU处理器持续100%使用率问题
    JDK工具: jconsole 、 jstack
  3. 死锁问题
    JDK工具: jstack

1.6面试题 JVM调优分析

所谓的JVM调优,实际上就是对JVM各参数指标的设置,
根据不同公司 不同的项目 不同的硬件环境都有不同的方案。

2_MySQL面试高频及优化

2.1概述:

企业级的项目都需要使用数据库,而数据库性能的好坏也直接体现出项目的好坏,试想一下不管你代码写的多么好,如果一个查询语句执行了几分钟那么用户的体验也是极不好的。所以不管是学习中、工作中、面试中 数据库相关的面试题都是必不可少的。

常见高频面试点:


SQL相关(分组 排序 连接 书写顺序 执行顺序) 主要出现在笔试中

事务相关
	ACID
	隔离性
	并发问题
	事务如何配置
		spring
MySQL中的锁相关(读写锁   行 页 表     乐观锁悲观锁)
	读写锁
	行 页 表 
	乐观锁悲观锁
索引相关
	索引有什么作用
	为什么能够提升查询效率
	mysql数据库索引是基于哪个数据结构  B+Tree  Hash
	为什么要选择B+Tree
	
	索引的选择  
	
	索引的优点和缺点 
	
	索引 最左匹配原则 
	
MySQL优化相关
	


我们主要讨论的是索引和MySQL优化方面:

在这里插入图片描述

索引优化: 添加适当索引(index)(重点)

Sql优化: 写出高质量的sql,避免索引失效 (重点)

设计优化: 表的设计合理化(符合3NF,有时候要进行反三范式操作)

配置优化: 对mysql配置优化 [配置最大并发数my.ini, 调整缓存大小 ]

硬件优化: 服务器的硬件优化

架构优化:读写分离、分库分表

2.2面试题 MySQL索引的实现原理

2.21MySQL索引实现-磁盘IO的介绍

索引是解决SQL性能问题的重要手段之一,使用索引可以帮助用户解决大多数的SQL性能问题。索引就是数据结构,通过这种数据结构可以大大提高mysql的查询效率

磁盘存取示意图

每次从磁盘中查找数据称为磁盘I/O, 而磁盘IO 至少要经历磁盘寻道、磁盘旋转、数据读取等等操作,非常影响性能,所以对于读取数据,最大的优化就是减少磁盘I/O
在这里插入图片描述
在这里插入图片描述

2.22MySQL索引实现-数据结构

MySQL的索引主要是基于,B+Tree和Hash实现的,有那么多的数据结构,
为什么选择这两种数据结构呢,我们来依次分析一下

二叉树
在这里插入图片描述
为了加快数据的查找,可以维护二叉查找树, 每个节点分别包含索引键值和一个指向对应数据记录的物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取相应的数据,从而快速的检索出符合条件 的记录

左子树的键值小于根的键值

右子树的键值大于根的键值

从二叉树的查找过程了来看,最坏的情况下磁盘IO的次数由树的高度来决定。从前面分析情况来看,减少磁盘IO的次数就必须要压缩树的高度,让瘦高的树尽量变成矮胖的树,所以引出B-Tree强势登场

B-Tree

平衡多叉查找树
度(Degree) 节点的数据存储个数
叶节点具有相同的深度
节点中数据key从左到右递增排列
叶节点的指针为空
在节点中直接存储了数据 data


在这里插入图片描述
疑问:
二叉树的节点只存了一个数据? 而B-Tree的节点因为有度的概念存了多个数据?
那么二叉树的节点数据量小是不是在读取的时候效率更高呢?
而且读到内存中的遍历速度是不是更快些呢?

预读:磁盘一般会顺序向后读取一定长度的数据(页的整数倍)放入内存
局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用
存储器读取数据按 磁盘块读取
每个磁盘块的大小为 扇区(页)的2的N次方
每个扇区的最小单位 512B 或 4096B 不同的生产厂家不同

为了提升度的长度,还需要对这种数据结构进行优化,所以它的升华版B+Tree诞生了

B+Tree

B+树是B树的变体,基本与B-Tree相同

特点

非叶子节点不存储data,只存储key,可以增大度
叶子节点不存储指针
顺序访问指针,提高区间访问能力

在这里插入图片描述
B+Tree索引的性能分析

一般使用磁盘I/O次数评价索引结构的优劣


B+Tree的度一般会超过100,因此h非常小 (一般为3到5之间),
性能就会非常稳定
B+Tree叶子节点有顺序指针,更容易做范围查询

Hash

使用hash结构存储索引,查找单行数据很快,但缺点也很明显。在这里插入图片描述
1.无法用于排序
2.只支持等值查找
3.存在Hash冲突

Hash索引只适用于某些特定场景,我们使用不多

什么是索引?

索引为什么能够提升查询效率?

mysql的索引是基于什么数据结构实现的? B+Tree hash 为什么选择这种数据结构?

聚簇索引和非聚簇索引有什么区别 组合索引的最左原则

2.23MySQL索引实现-存储引擎

MyISAM 和 innoDB的对比

MyISAMinnoDB
事务不支持支持
外键不支持支持
表锁行锁
缓存缓存索引缓存索引和数据
全文索引支持不支持
索引实现非聚簇索引聚簇索引
 总的来说:
 	需要事务: 那肯定用innoDB
 	不需要事务:
 		myisam的查询效率高,内存要求低, 但因为采用表锁, 不适合并发写操作, 读多写少选它
 		innoDB采用行锁,适合处理并发写操作, 写多读少选它

MyISAM索引实现

索引特点:
采用B+Tree 和 Hash作为数据结构
MyISAM 索引文件和数据文件是分离的(非聚簇)
叶子节点存储的是数据的磁盘地址
非主键索引和主键索引类似
在这里插入图片描述
InnoDB索引实现
索引特点:
采用B+Tree 和 Hash作为数据结构
数据文件本身就是索引文件 (聚簇索引)
表数据文件本身就是按照B+Tree组织的一个索引结构文件
聚集索引-叶节点包含了完整的数据记录
非主键索引 的叶子节点指向主键
在这里插入图片描述

2.3面试题-MySQL索引的高频面试题

2.31联合索引 (最左匹配原则)

联合索引:
当我们的where条件中 经常存在多个条件查询的时候,我们可以为这多个列创建组合索引

如:一张员工表,我们经常会用 工号、名称、入职日期 作为条件查询
select * from 员工表 where 工号=10003 and 名称=Staff and 入职日期='2001-09-03'

那么我们可以考虑 将(工号、名称、入职日期)创建为一个组合索引

疑问: 那为什么我们不把 这三个字段都单独列一个索引呢?
答: 主要是效率问题,对工号、名称、入职日期三列分别创建索引,MySQL只会选择辨识度高的一列作为索引。假设有100w的数据,一个索引筛选出10%的数据,那么可以筛选出10w的数据;

对于组合索引而言: 如果将(工号、名称、入职日期)创建为一个组合索引,那么三个字段的筛选将都会使用上,
先按工号排查、工号匹配完在按名称筛选、名称筛选完再按日期筛选,那么筛选的数据就是 100w*10%*10%*10%筛选出1000条数据。


最左原则:
	(工号、名称、入职日期) 作为一个组合索引,将会生成下图的索引目录结构。
	
	由接口可以看出,  工号是最先需要判断的字段,所以工号这个查询条件必须存在
	
	工号判断完,才会判断名称
	
	名称判断完才会判断入职日期

也就是说,组合索引查询条件必须得带有最左边的列:
	对于我们的索引:
	条件为: (工号)   (工号,名称)   (工号,名称,入职日期) 这几种情况都是生效的
	
	条件为: (名称)不生效    (名称,入职日期)不生效  (工号,入职日期)部分生效


在这里插入图片描述

2.32索引的优劣势

优势

1.可以通过建立唯一索引或者主键索引,保证数据库表中每一行数据的唯一性.
2.建立索引可以大大提高检索的数据,以及减少表的检索行数
3.在表连接的连接条件 可以加速表与表直接的相连
4.在分组和排序字句进行数据检索,可以减少查询时间中 分组 和 排序时所消耗的时间(数据库的记录会重新排序)
5.建立索引,在查询中使用索引 可以提高性能

劣势

1.在创建索引和维护索引 会耗费时间,随着数据量的增加而增加

2.索引文件会占用物理空间,除了数据表需要占用物理空间之外,每一个索引还会占用一定的物理空间

3.当对表的数据进行INSERT,UPDATE,DELETE 的时候,索引也要动态的维护,这样就会降低数据的维护速度

2.33索引的选择

适合建立索引

	1.主键自动建立唯一索引:primary    
	2.频繁作为查询条件的字段应该创建索引 
	3.查询中与其它表关联的字段,外键关系建立索引
	4.查询中排序的字段,排序的字段若通过索引去访问将大大提升排序速度	 
	5.查询中统计或分组的字段

不适合建立索引

	1.记录比较少
	2.where条件里用不到的字段不建立索引
	3.经常增删改的表
	索引提高了查询的速度,同时却会降低更新表的速度,因为建立索引后, 如果对表进行INSERT,UPDATE和DELETE, MYSQL不仅要保存数据,还要保存一下索引文件
	4.数据重复的表字段
	如果某个数据列包含了许多重复的内容,为它建立索引  就没有太大在的实际效果,比如表中的某一个字段为国籍,性别,数据的差异率不高,这种建立索引就没有太多意义。

2.4面试题 回答SQL性能优化的思路

2.41慢查询日志

mysql的慢查询日志是mysql提供的一种日志记录,它用来记录在mysql中响应时间超过阀值的语句,mysql 的日志是跟踪mysql性能瓶颈的最快和最直接的方式了,系统性能出现瓶颈的时候,首先要打开慢查询日志,进行跟踪,尽快的分析和排查出执行效率较慢的SQL ,及时解决避免造成不好的影响。

**作用**: 记录具体执行效率较低的SQL语句的日志信息。

注意:

在默认情况下mysql的慢查询日志记录是关闭的。

同时慢查询日志默认不记录管理语句和不使用索引进行查询的语句

查看是否开启慢查询日志

show variables like '%slow_query_log%'
开启
set global slow_query_log=1;
只对当前数据库生效,如果重启后,则会失效

如果想永久生效,必须修改配置文件
slow_query_log=1
slow_query_log_file=地址

设置慢查询的阀值

show variables like 'long_query_time'
set global long_query_time=4;

要断开连接后, 才能生效

show global variables like 'long_query_time';
select sleep(4)
show global status like '%slow_queries%';

慢查询日志分析

主要功能是, 统计不同慢sql的
出现次数(Count), 
执行最长时间(Time), 
累计总耗费时间(Time), 
等待锁的时间(Lock), 
发送给客户端的行总数(Rows), 
扫描的行总数(Rows), 
用户以及sql语句本身(抽象了一下格式, 比如 limit 1, 20 用 limit N,N 表示).

第三方的慢查询日志分析工具:mysqlsla,myprofi,pt-query-diges等等
在这里插入图片描述
在这里插入图片描述

2.42Explain执行计划分析

概念及作用

使用explain关键字,可以模拟优化器执行的SQL语句
从而知道MYSQL是如何处理sql语句的
通过Explain可以分析查询语句或表结构的性能瓶颈

具体作用:
	查看表的读取顺序
	数据读取操作的操作类型
	查看哪些索引可以使用
	查看哪些索引被实际使用
	查看表之间的引用
	查看每张表有多少行被优化器执行

使用方法

使用Explain关键字 放到sql语句前
explain select cus_id from testemployee where cus_id > 10

在这里插入图片描述
结果查看

type 访问类型 (重要)

访问类型排列	                              
结果值:(最好到最差) const > eq_ref > range > index > ALL

key 使用到的索引

Extra 额外信息

using filesort 触发文件排序

using tempory 触发临时表

2.43Sql的常见优化思路

SELECT语句务必指明字段名称(避免直接使用select *  )
SQL语句要避免造成索引失效的写法
SQL语句中IN包含的值不应过多
当只需要一条数据的时候,使用limit 1
如果排序字段没有用到索引,就尽量少排序
如果限制条件中其他字段没有索引,尽量少用or
尽量用union all代替union
避免在where子句中对字段进行null值判断
不建议使用%前缀模糊查询
避免在where子句中对字段进行表达式操作
Join优化 能用innerjoin 就不用left join right join,如必须使用 一定要已小表为驱动

2.44面试题 回答MySQL性能优化的思路

1.表的设计优化
选择表合适存储引擎:
myisam: 应用时以读和插入操作为主,只有少量的更新和删除,并且对事务的完整性,并发性要求不是很高的。
Innodb: 事务处理,以及并发条件下要求数据的一致性。除了插入和查询外,包括很多的更新和删除。
尽量 设计 所有字段都得有默认值,尽量避免null。
数据库表设计时候更小的占磁盘空间尽可能使用更小的整数类型.
但是一般说来,数据库中的表越小,在它上面执行的查询也就会越快。
因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽可能小。例如,
在定义邮政编码这个字段时,如果将其设置为CHAR(255),显然给数据库增加了不必要的空间,
CHAR (255) VARCHAR (255) 10
100101
甚至使用VARCHAR这种类型也是多余的,因为CHAR(6)就可以很好的完成任务了。同样的,如果可以的话,
我们应该使用TINYINT而不是BIGINT来定义整型字段。
应该尽量把字段设置为NOT NULL,这样在将来执行查询的时候,数据库不用去比较NULL值。
对于某些文本字段,例如“省份”或者“性别”,我们可以将它们定义为ENUM类型。因为在MySQL中,ENUM类型被当作数值型数据来处理, 而数值型数据被处理起来的速度要比文本类型快得多。这样,我们又可以提高数据库的性能。

2.索引优化
表的主键、外键必须有索引;
数据量大的表应该有索引;
经常与其他表进行连接的表,在连接字段上应该建立索引;
经常出现在Where子句中的字段,特别是大表的字段,应该建立索引;
索引应该建在选择性高的字段上; (sex 性别这种就不适合)
索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;
频繁进行数据操作的表,不要建立太多的索引;
删除无用的索引,避免对执行计划造成负面影响;
表上建立的每个索引都会增加存储开销,索引对于插入、删除、更新操作也会增加处理上的开销。另外,过多的复合索引,在有单字段索引的情况下,一般都是没有存在价值的;相反,还会降低数据增加删除时的性能,特别是对频繁更新的表来说,负面影响更大。

3.sql语句优化
SELECT语句务必指明字段名称(避免直接使用select * )
SQL语句要避免造成索引失效的写法
SQL语句中IN包含的值不应过多
当只需要一条数据的时候,使用limit 1
如果排序字段没有用到索引,就尽量少排序
如果限制条件中其他字段没有索引,尽量少用or
尽量用union all代替union
避免在where子句中对字段进行null值判断
不建议使用%前缀模糊查询
避免在where子句中对字段进行表达式操作
Join优化 能用innerjoin 就不用left join right join,如必须使用 一定要已小表为驱动

4.缓存优化 (数据库自身缓存 redis缓存 等等 )
为了提高查询速度,我们可以通过不同的方式去缓存我们的结果从而提高响应效率。

数据库本身也是支持缓存的 --> 查询缓存query_cache , 默认查询缓存是关闭的
需要我们在mysql.ini 配置文件中开启:
开启方法:
query_cache_type=0 #关闭查询缓存
query_cache_type=1 #开启查询缓存,mysql自动帮我们缓存满足条件的查询数据
query_cache_type=2 #开启查询缓存,需要在参数中手动指定要缓存的查询

不过因为我们的课程体系主要讲解的是redis,所以在这里可以引入redis的知识点。

5.主从复制、读写分离
如果数据库的使用场景读的操作比较的时候,为了避免写的操作所造成的性能影响 可以采用读写分离的架构,读写分离,解决的是,数据库的写入,影响了查询的效率。读写分离的基本原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。 数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。
参考: https://www.jianshu.com/p/faf0127f1cb2

6.mysql的分库分表
数据量越来越大时,单体数据库无法满足要求,可以考虑分库分表
两种拆分方案:
垂直拆分:(分库)
业务表太多? 将业务细化 不同的小业务专门用一个库来维护
水平拆分:(分表)
单个表存的数据太多,装不下了? 将该表查分成多个
分库分表经常使用的数据库中间件:
MyCat
https://www.cnblogs.com/chongaizhen/p/11083226.html
Sharding-JDBC
https://blog.csdn.net/forezp/article/details/94343671

3_并发编程面试高频及优化

3.1概述

并发编程,可以让我们的代码由串行变为并行, 在工作中处理大量的任务或需要异步处理时, 
可以用来并发编程 提高我们程序运行的效率, 并发编程相关的面试题也是面试官特别爱问的技术点. 
下面我们总结一下 多线程中常见的面试题

3.2面试题 并发编程之多线程基础面试题

* 线程的创建方式
	
用户线程和守护线程

线程的优先级
	
* 线程的生命周期
线程的常用API(join sleep 如何停止正在运行的线程)
	join
	如何停止运行线程

3.3面试题 并发编程之线程安全面试题

什么是线程安全问题
	
JMM内存模型
	Java Memory Model 
	
并发编程的三大特性
	原子性
	可见性
	有序性
	
关键字 volatile
	可见性
	有序性
	
	
关键字 synchronized 

API Lock 

ThreadLocal 

CAS  

3.4面试题 并发编程之线程池

什么是线程池?
	
线程池的作用?
	
java线程池框架的体系?

构建线程池的核心参数介绍?
new ThreadPoolExecutor(核心参数);


线程池的工作原理?



3.5面试题 并发编程之线程间的通信

等待唤醒机制  wait()   notify();

1~100   A线程专门奇数    B线程打印偶数  依次打印

                锁     2    B
	       1                  
	       2   wait           2
	       A                  3    notify wait
生产消费模式
	A                盒子         B
	生产苹果                      购买苹果


04_源码分析面试高频

4.1概述

通俗点来说:
1. 你看过源码,到一个新公司,公司所采用的架构也是源码你能够快速上手
2. 看过框架源码更容易理解和记忆框架的特点及工作原理,可以避免一些有可能会遇到的坑。
3. 看过源码代表了你的学习能力,可以让你在水平相当的面试者当中 脱颖而出.
4. 看过源码可以学习一些高大上的写法,当研究某一个优秀框架源码时,会发现里面使用到了很多设计模式,这样可以提高自己在实际的开发中如何使用设计模式能力,让代码结构质量等有一个质的提高。
5. 面试官爱问源码也能体现出面试官的  * 格  ^_^

但是我们的JAVA技术体系中,有非常多的API非常多的框架、而且源码又很晦涩难懂,实际上如果单纯的就面试而言,我们可以只记忆理解源码中的精华。哪些源码是面试中常出现的呢?

4.2面试题 JDK相关API源码面试题

String 和 StringBuffer、StringBuilder 的区别是什么 String 为什么是不可变的

可变性
简单的来说:String 类中使用 final 关键字字符数组保存字符串,
private final char value[],所以 String 对象是不可变的。

而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 
中也是使用字符数组保存字符串char[] value 但是没有用 final 关键字修饰,所以这两种对象都是可变的。


线程安全性
String 中的对象是不可变的,也就可以理解为常量,线程安全。
AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等
公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的
。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

性能
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。
相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升
,但却要冒多线程不安全的风险。

对于三者使用的总结:
操作少量的数据 = String
单线程操作字符串缓冲区下操作大量数据 = StringBuilder
多线程操作字符串缓冲区下操作大量数据 = StringBuffer

java集合接口及实现类介绍

Collection 接口的接口 对象的集合(单列集合)
├——-List 接口:元素按进入先后有序保存,可重复
│—————-├ LinkedList 接口实现类, 链表, 插入删除, 没有同步, 线程不安全
│—————-├ ArrayList 接口实现类, 数组, 随机访问, 没有同步, 线程不安全
│—————-└ Vector 接口实现类 数组, 同步, 线程安全
│ ———————-└ Stack 是Vector类的实现类
└——-Set 接口: 仅接收一次,不可重复,并做内部排序
├—————-└HashSet 使用hash表(数组)存储元素
│————————└ LinkedHashSet 链表维护元素的插入次序
└ —————-TreeSet 底层实现为二叉树,元素排好序
Map 接口 键值对的集合 (双列集合)
├———Hashtable 接口实现类, 同步, 线程安全
├———HashMap 接口实现类 ,没有同步, 线程不安全-
│—————–├ LinkedHashMap 双向链表和哈希表实现
├ ——–TreeMap 红黑树对所有的key进行排序

集合常用工具类:
Collections:

ArrayList源码分析

## 概述

ArrayList是一个动态数组,他是基于数组实现的集合类。在它内部有几个重要的属性。
初始化的长度、存储数据数组 及一个size属性, 我们执行的add方法将会把元素存入到该数组中,
数组的初始化长度为10,如果长度不满足要求 会 触发数组的扩容,size属性用来记录数组中元素的个数。

因为arrayList是基于数组的 , 所以它的查询的速度会非常快。 但指定位置插入数据 或删除指定位置数据,会引起其他数据的变化, 如果需要频繁的 随机插入 随机删除 推荐使用linkedList

但是 ArrayList 不是线程安全的 如果是多线程的环境, 建议使用 Vector 或 CopyOnWriteArrayList

## 扩容

当调用 add方法时, 会检查 size + 1位是否有效, 如果没有size + 1位 会触发grow扩容方法,
会进行1.5倍扩容。

当ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:新的容量=原始容量 + (原始容量 >> 1);

LinkedList源码分析

LinkedList 本质上是基于双向链表实现的, 它内部 主要维护了 一个first节点 和 一个last节点( Node节点分为 数据本身 和 上一个节点的变量 还有下一个节点的变量 )

它实现了 list 可以作为一个集合来使用
它也实现 Deque 可以作为一个双端队列来使用
linkedList 插入数据 和 删除数据非常快

也不是线程安全的。

HashMap 源码分析(1.7)

## 概念

HashMap是基于hash表的map实现类,它可以接收null的键值,是非线程安全的,底层基于数组加链表实现的,
当我们new 一个hashmap的时候,会默认初始化一个长度为16的Entry数组(长度是可以指定),我们使用hashmap存储数据的时候 会根据 key计算出hash值 根据hash值及数组的长度能够计算出 要存入数组中的位置,数据里面的每个位置都称作桶
,一个桶有可能会存放多个Entry (hash碰撞) ,多个Entry会已单向链表形式存放。 如果桶的使用达到一定数量会触发扩容,这个数量是根据负载因子 和 数据长度决定的 (数组长度 *负载因子 ),默认的负载因子为0.75 ,默认数组长度为16 16*0.75=12,桶的使用超过12就会触发扩容 ,扩容会创建一个新的数组 长度为旧数组的2倍 ,并将旧数组中的数据迁入到新数组中。
hashmap不是线程安全的,
如果多线程考虑使用hashTable 或 ConcurrentHashMap 或 Map m = Collections.synchronizeMap(hashMap);

## put方法源码详解

如果存入的key为null 那这个Entry会存入到0位数组中
不为null 会计算key的hash值
根据hash 及 数组长度会计算出桶的下标
查看下标下是否有对应Entry链表,如果有遍历该链表
对链表中Entry的key进行equals对比,如果结果为true替换
没有对比到对应的key
则会将新的Entry插入到链表的表头中

## 什么是哈希碰撞(hash碰撞)

指的是两个不同的key计算出相同的hashcode 称为hash碰撞
发生hash碰撞后,他们会存入相同的桶中

## 为什么负载因子要默认为0.75  
HashMap负载因子为0.75是 空间和时间 成本的一种折中,
负载因子过小,扩容频率变高,空间使用率变低
负载因子过高,空间使用率变高,但hash碰撞增加,造成链表长度增加影响查询性能
使用时可根据需求更改负载因子

## get方法详解

根据key的hash方法及数组的长度,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。因此,设计HashMap的key类型时,如果使用不可变的、声明作final的对象,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生,提高效率。不可变性能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择

## 扩容详解

插入新的Entry对象时 需要判断size是否大于等于 负载因子*数组长度,如果大于需要
先对数组进行扩容,扩容就是用一个新的大数组替换原来的小数组,并将原来数组中的值迁移到新的数组中

## 什么是哈希表?(hash表、散列表)

哈希表(HashTable)又叫做散列表,是根据关键码值(即键值对)而直接访问的数据结构。也就是说,它通过把关键码映射到表中一个位置来访问记录,以加快查找速度。这个映射函数就叫做散列(哈希)函数,存放记录的数组叫做散列表。在数据结构中,我们对两种数据结构应该会非常熟悉:数组与链表。数组的特点就是查找容易,插入删除困难;而链表的特点就是查找困难,但是插入删除容易。既然两者各有优缺点,那么我们就将两者的有点结合起来,让它查找容易,插入删除也会快起来。哈希表就是讲两者结合起来的产物。

## 为什么String、Integer这样的类适合作为key

因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能

## 如果使用自定义的对象作为key要注意什么

一定要重写equals 及 hashcode的方法

## jdk1.8对于hashmap的优化

1. 单个链表长度超过8之后采用红黑树结构,优化查询速度

2. 数组扩容时旧数据的迁移采用位运算 得到的值 为0或1 0位置不变 1位置为当前位置加原数组长度,避免重新hash的性能开销

## hashmap的线程安全问题

3. 当put数据时,多线程操作时可能会出现数据不一致
在多线程操作hashmap时,如果多条线程同时发现hashmap达到扩容要求,会同时进行resize 方法,在数据迁移的过程中可能会造成环形链表,当调用Get查找一个不存在的Key,而这个Key的Hash结果恰好等于3的时候,由于位置3带有环形链表,所以程序将会进入死循环!(1.8不会出现这个问题)

HashTable 源码分析

HashTable 也是基于数组加链表, 和hashmap一样, 不同的是 在很多有可能出现线程安全的方法上加了synchronized 锁,这样保证了线程安全。但性能也会跟着下降。 hashTable不能够存null值。 所以现在用的不多 ,都会考虑使用ConcurrentHashMap

ConcurrentHashMap 源码分析(1.7)

## 概念

ConcurrentHashMap 和 HashMap 思路是差不多的,不过支持了并发安全。
整个 ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承Lock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
每一个Segment元素中数据的存储还是由数组+链表组成(相当于每个Segment中存储一个hash表)

## put数据的过程

当我们put一个key value时
会根据key计算和Segment的长度计算出 要存入的Segment 中的位置
找到位置后 对该Segment上锁
上锁成功后 , 同hashmap的put操作很像,
想看该Segment中 有没有数组 如果有数组 计算应存储的数组下标
如果没数组 初始化数组。
然后 往数组中存入元素 发生hash碰撞 则已链表形式存储

## 1.8实现

1.8源码改动量很大,不在使用分段锁 而是采用1.8 hashmap的实现思路,引入红黑树, 在put 和 resize方法中使用CAS+synchronized控制线程安全

HashSet 源码分析

对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成

ArrayList、LinkedList、Vector对比

ArrayList 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。
LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率高。
Vector 和ArrayList一样,它也是一个动态数组,由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。

HashMap、HashTable、ConcurrentHashMap

hashmap 允许一个NULL键和多个NULL值,基于数组+链表+红黑树 效率比另外两个高,但不是线程安全的
hashtable 也是基于数组 + 链表, 在关键操作的方法上 加了同步锁 Sychonized , 所以它是线程安全的
但hashTable 效率不高
ConcurrentHashMap 在1.7中采用分段锁的方式实现,每一段锁都有同步手段修饰,所以它既能保证多条线程同时操作
又能够保证数据线程安全

HashMap 、 TreeMap

HashMap:适用于在Map中插入、删除和定位元素。
Treemap:适用于按自然顺序或自定义顺序遍历键(key)。
HashMap通常比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,
在需要排序的Map时候才用TreeMap。

4.3面试题 开源框架源码相关

开源框架一般代码非常多,对于源码的研究是一个漫长的持续学习的过程。 如果针对面试来讲,可详细准备一个。如果面试官问有没有研究过开源框架源码时,可以聊一聊。

一些经典的开源框架:
SpringIOC (推荐)
	IOC的底层实现
	IOC的初始化流程
	IOC的getBean
SpringAOP
	AOP的介绍
	AOP的使用
	动态代理
SpringMVC
	运行原理
SpringBoot
	自动装配的原理
Mybatis 
	运行原理的源码

附录(面试题小结):

JVM相关面试题汇总

说一下 JVM 的主要组成部分?及其作用?

类加载器(ClassLoader)
运行时数据区(Runtime Data Area)
执行引擎(Execution Engine)
本地库接口(Native Interface)
组件的作用: 首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,
运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,
并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),
将字节码翻译成底层系统指令,再交由 CPU 去执行,
而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能

说一下 JVM 运行时数据区?详细介绍下每个区域的作用?

程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,
字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、
异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;
Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,
而本地方法栈是为虚拟机调用 Native 方法服务的;
Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,
几乎所有的对象实例都在这里分配内存;
方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

java中都有哪些类加载器

启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,
或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;
扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库;
应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,
我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

自定义类加载器:通过继承ClassLoader抽象类,实现loadClass方法

哪些情况会触发类加载机制

见类加载的时机

什么是双亲委派模型?

如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,
而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,
这样所有的加载请求都会被传送到顶层的启动类加载器中,
只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。

说一下类装载的执行过程?

类装载分为以下 5 个步骤:

加载:根据查找路径找到相应的 class 文件然后导入;
检查:检查加载的 class 文件的正确性;
准备:给类中的静态变量分配内存空间;
解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,
而在直接引用直接指向内存中的地址;
初始化:对静态变量和静态代码块执行初始化工作。

怎么判断对象是否可以被回收?

一般有两种方法来判断:

引用计数器:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,
当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
可达性分析:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。
当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。


哪些变量可以作为GC Roots

见可作为GC Roots节点的对象

Java 中都有哪些引用类型?

强引用:发生 gc 的时候不会被回收。
软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
弱引用:有用但不是必须的对象,在下一次GC时会被回收。
虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,
虚引用的用途是在 gc 时返回一个通知。

说一下 JVM 有哪些垃圾回收算法?

标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。
标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。
复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,
然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。
分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,
老年代采用标记整理或标记清楚算法。

说一下 JVM 有哪些垃圾回收器?

Serial:最早的单线程串行垃圾回收器。
Serial Old:Serial 垃圾回收器的老年版本,同样也是单线程的,可以作为 CMS 垃圾回收器的备选预案。
ParNew:是 Serial 的多线程版本。
Parallel 和 ParNew 收集器类似是多线程的,但 Parallel 是吞吐量优先的收集器,
可以牺牲等待时间换取系统的吞吐量。
Parallel Old 是 Parallel 老生代版本,Parallel 使用的是复制的内存回收算法,
Parallel Old 使用的是标记-整理的内存回收算法。
CMS:一种以获得最短停顿时间为目标的收集器,非常适用 B/S 系统。
G1:一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项。

新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?

- 新生代回收器:Serial、ParNew、Parallel Scavenge
- 老年代回收器:Serial Old、Parallel Old、CMS
- 整堆回收器:G1

新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,
缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。

简述分代垃圾回收器是怎么工作的?

分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。

新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,
它们的默认占比是 8:1:1,它的执行流程如下:

把 Eden + From Survivor 存活的对象放入 To Survivor 区;
清空 Eden 和 From Survivor 分区;
From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,
当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。

老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。
以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。

Minor GC与Full GC分别在什么时候发生?

新生代内存不够用时候发生MGC也叫YGC,JVM堆内存不够的时候发生FGC在 

有没有在生产环境下排过错,说说过程?

可以说案例分析里面的案例,也可以说内存飙高的排查

说一下 你知道的JVM 性能监控的工具?

JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 
这两款视图监控工具。
jps  jinfo jstat jmap jstack 

jconsole:用于对 JVM 中的内存、线程和类等进行监控;
jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、
gc 变化等

idea + jprofiler内存分析

常用的 JVM 调优的参数都有哪些?

-Xms2g:初始化推大小为 2g;
-Xmx2g:堆最大内存为 2g;
-Xmn500M 设置年轻代为500m;
-XX:PermSize=500M ;  1.8之后采用 MetaspaceSize
-XX:MaxPermSize=500M ;  1.8之后采用 MaxMetaspaceSize
-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
-XX:+PrintGC:开启打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 详细信息。
-XX:MaxDirectMemorySize : 设置直接内存的大小

MySQL相关面试题汇总

以下情况中索引的使用情况

建立复合索引(a,b,c),请说出下列条件关于索引的使用情况
1.select * from table where a=4  
2.select * from table where a=4 and b=6
3.select * from table where a=4 and c=5 and b=6
4.select * from table where b=4 or b=5   
5.select * from table where a=4 and c=6
6.select * from table where a=4 and b>5 and c=6
7.select * from table where a=4 and b like 'test%' and c=6
8.select * from table where a=4 order by b,c  
9.select * from table where b=5 order a
10.select * from table where b=5 order c
11.select * from table where a=5 group by c,b

什么是索引

数据库索引的本质是数据结构,这种数据结构能够帮助我们快速的获取数据库中的数据。

索引的作用

当表中的数据量越来越大时,索引对于性能的影响愈发重要。索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高好几个数量级。有了索引相当于我们给数据库的数据加了目录一样,可以快速的找到数据,如果不适用索引则需要一点一点去查找数据
简单来说
提高数据查询的效率。

索引的分类

- 1.普通索引index :加速查找
- 2.唯一索引
- 3.联合索引(组合索引)
- 4.全文索引fulltext :用于搜索很长一篇文章的时候,效果最好。

索引原理

索引的实现本质上是为了让数据库能够快速查找数据,而单独维护的数据结构,
mysql实现索引主要使用的两种数据结构:hash和B+树: 我们比较常用的 MyIsam 和 innoDB引擎都是基于B+树的。

hash:(hash索引在mysql比较少用)他以把数据的索引以hash形式组织起来,因此当查找某一条记录的时候,
速度非常快.当时因为是hash结构,每个键只对应一个值,而且是散列的方式分布
.所以他并不支持范围查找和排序等功能.

B+树:b+tree是(mysql使用最频繁的一个索引数据结构)数据结构以平衡树的形式来组织,因为是树型结构,
所以更适合用来处理排序,范围查找等功能.相对hash索引,B+树在查找单条记录的速度虽然比不上hash索引,
但是因为更适合排序等操作,所以他更受用户的欢迎.毕竟不可能只对数据库进行单条记录的操作.

索引的优点

1.可以通过建立唯一索引或者主键索引,保证数据库表中每一行数据的唯一性.
2.建立索引可以大大提高检索的数据,以及减少表的检索行数
3.在表连接的连接条件 可以加速表与表直接的相连
4.在分组和排序字句进行数据检索,可以减少查询时间中 分组 和 排序时所消耗的时间
(数据库的记录会重新排序)
5.建立索引,在查询中使用索引 可以提高性能

索引的缺点

1.在创建索引和维护索引 会耗费时间,随着数据量的增加而增加
2.索引文件会占用物理空间,除了数据表需要占用物理空间之外,每一个索引还会占用一定的物理空间
3.当对表的数据进行INSERT,UPDATE,DELETE 的时候,索引也要动态的维护,
这样就会降低数据的维护速度,(建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,
但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快)。

索引操作

对索引的简单增删改查语句要记得
查看表中索引
show index from tableName;
创建索引
CREATE INDEX 索引名 ON 表名 列名;
删除索引
DORP INDEX IndexName ON TableName
分析索引使用情况
explain select 语句

分析索引使用情况

explain显示了MySQL如何使用索引来处理select语句以及连接表。
可以帮助选择更好的索引和写出更优化的查询语句。简单讲,它的作用就是分析查询性能。
explain关键字的使用方法很简单,就是把它放在select查询语句的前面。mysql查看是否使用索引,
简单的看type类型就可以。如果它是all,那说明这条查询语句遍历了所有的行,并没有使用到索引。 
(最简单的说法,希望能说详细些)

哪些字段适合加索引

1.在经常需要搜索的列上,可以加快索引的速度
2.主键列上可以确保列的唯一性
3.在表与表的而连接条件上加上索引,可以加快连接查询的速度
4.在经常需要排序(order by),分组(group by)和的distinct 列上加索引 可以加快排序查询的时间,

哪些字段不适合加索引

1.查询中很少使用到的列 不应该创建索引,如果建立了索引然而还会降低mysql的性能和增大了空间需求.
2.很少数据的列也不应该建立索引,比如 一个性别字段 0或者1,
在查询中,结果集的数据占了表中数据行的比例比较大,mysql需要扫描的行数很多,增加索引,并不能提高效率
3.定义为text和image和bit数据类型的列不应该增加索引,
4.当表的修改(UPDATE,INSERT,DELETE)操作远远大于检索(SELECT)操作时不应该创建索引,
这两个操作是互斥的关系。

哪些情况会造成索引失效

1.如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)
2.索引字段的值不能有null值,有null值会使该列索引失效
3.对于多列索引,不是使用的第一部分,则不会使用索引(最左原则)
4.like查询以%开头
5.如果列类型是字符串,那一定要在条件中将数据使用单引号引用起来,否则不使用索引
6.在索引的列上使用表达式或者函数会使索引失效

联合索引最左原则

在mysql建立联合索引时会遵循最左前缀匹配的原则,即最左优先,
在检索数据时从联合索引的最左边开始匹配,组合索引的第一个字段必须出现在查询组句中,这个索引才会被用到
如创建组合索引 a,b,c     那么查询条件中单纯的使用  b 和 c是使用不到索引的

聚簇索引和非聚簇索引

MyISAM——非聚簇索引
MyISAM存储引擎采用的是非聚簇索引,非聚簇索引的主索引和辅助索引几乎是一样的,
只是主索引不允许重复,不允许空值,他们的叶子结点的key都存储指向键值对应的数据的物理地址。
非聚簇索引的数据表和索引表是分开存储的。


InnoDB——聚簇索引
聚簇索引的主索引的叶子结点存储的是键值对应的数据本身,
辅助索引的叶子结点存储的是键值对应的数据的主键键值。因此主键的值长度越小越好,类型越简单越好。
聚簇索引的数据和主键索引存储在一起。

事务的基本要素(ACID)

(1)原子性:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。
事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没发生过一样。
例如:A账户中有1000元,B账户中有1000元。A要给B转账500元。A扣款和B加款这两条要么同时执行,
要么同时不执行。如果在A扣款后B加款之前,系统发生故障,会回滚到A扣款之前的状态。
(2)一致性:事务开始之前和事务结束后,数据库的完整性约束没有被破坏。
例如:不论汇款成败,A账户B账户总额是2000元。
(3)隔离性:事务的执行互不干扰。
(4)持久性:事务执行成功后,该事务对数据库的更改是持久保存在数据库中的,不会被回滚。
可以使用日志记录或影子副本来实现。

什么是事务?

事务就是被绑定在一起作为一个逻辑工作单元的SQL语句分组
如果任何一个语句操作失败那么整个操作就被失败,以后操作就会回滚到操作前状态,或者是上有个节点。
为了确保要么执行,要么不执行,就可以使用事务。
要将有组语句作为事务考虑,就需要通过ACID测试:
即原子性,一致性,隔离性和持久性。

- 锁:锁是实现事务的关键,锁可以保证事务的完整性和并发性。 与现实生活中锁一样,
- 它可以使某些数据的拥有者,在某段时间内不能使用某些数据或数据结构。
老李 给 老王汇钱
老李把钱 ==》 银行 老李账户扣钱
银行 ==》 老王 成功 老王账户加钱
不成功 老账户补钱(银行将钱返给老李)

事务的并发问题

1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,
导致事务A多次读取同一数据时,结果 不一致。
3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,
但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,
就好像发生了幻觉一样,这就叫幻读。

事务隔离性的作用

就是保证数据的一致性、完整性。
事务隔离级别越高,在并发下会产生的问题就越少,
但同时付出的性能消耗也将越大,因此很多时候必须在并发性和性能之间做一个权衡。
所以设立了几种事务隔离级别,以便让不同的项目可以根据自己项目的并发情况选择合适的事务隔离级别,
对于在事务隔离级别之外会产生的并发问题,在代码中做补偿。

事务的隔离级别4个

事务隔离级别读未提交读已提交可重复读串行化
脏读
------------------------
不可重复读
------------------------
幻读

mysql中锁的分类

按操作分
	读锁(共享锁)
		加了读锁,   其他的进程也可以进行读操作,但写操作会阻塞,所以称为共享锁
	写锁(排它锁)
		加了写锁,  其他的进程读操作和写操作都会进入阻塞状态	
按粒度分
	表锁
		加锁特点:开销小、加锁快,不会出现死锁;锁粒度大,锁冲突高,并发低
		加锁方式:
			lock table tableName read; //读锁
			lock table tableName write; //写锁
		解锁方式:
			unlock tables;//释放全部锁
	行锁
		开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 
		加锁方式:
			select * from table where id=1 lock in share mode; //读锁
			select * from table where id=1 for update; //写锁
		解锁方式:
        	commit; //提交事务即解锁
	页锁
		介于上面两个之间,不用特意阐述
从思想的层面:
	悲观锁:
	看待事情比较悲观, 认为别人会修改它的数据,需要上锁来保证数据的安全
	select * from employee where id = 1 for update
	update -- 
	乐观锁:
	看待事情比较乐观, 
	
	id   name  salary version 
	1     老王   500     2
	
   客户端1   
   select * from employee where id = 1    版本 = 1
   update employee set salary=1000,version=version+1 where id=1 and version = 1
   
   客户端2   
   select * from employee where id = 1    版本 = 1
   update employee set salary=1000,version=version+1 where id=1 and version = 1
   
   
   读操作多 选乐观锁
   写操作多 选悲观锁

mysql中的几种连接查询

内连接:只有两个元素表相匹配的才能在结果集中显示。
	inner join
外连接:
    左外连接:左边为驱动表,驱动表的数据全部显示,匹配表的不匹配的不会显示。
    	left join
	右外连接:右边为驱动表,驱动表的数据全部显示,匹配表的不匹配的不会显示。
		right join
	全外连接:连接的表中不匹配的数据全部会显示出来。
	 	full join

sql的书写顺序和执行顺序

-- 编写顺序
select  distinct  查询字段
from  表名
JOIN 表名
ON  连接条件
where 查询条件
group by 分组字段
having 分组后条件
order by  排序条件
limit 查询起始位置, 查询条数

-- 执行顺序
from  表名
ON  连接条件
JOIN 表名
where 查询条件
group by 分组字段
having 分组后条件
select  distinct  查询字段
order by  排序条件
limit 查询起始位置, 查询条数

mysql优化综合性

1.表的设计优化
选择表合适存储引擎:
myisam: 应用时以读和插入操作为主,只有少量的更新和删除,并且对事务的完整性,并发性要求不是很高的。
Innodb: 事务处理,以及并发条件下要求数据的一致性。除了插入和查询外,包括很多的更新和删除。
尽量 设计 所有字段都得有默认值,尽量避免null。
数据库表设计时候更小的占磁盘空间尽可能使用更小的整数类型. 
但是一般说来,数据库中的表越小,在它上面执行的查询也就会越快。
1 ~ 10  
tinyint   int   bigint

因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽可能小。例如,
在定义邮政编码这个字段时,如果将其设置为CHAR(255),显然给数据库增加了不必要的空间,
 CHAR (255)            VARCHAR (255)    10       
100101
甚至使用VARCHAR这种类型也是多余的,因为CHAR(6)就可以很好的完成任务了。同样的,如果可以的话,
我们应该使用TINYINT而不是BIGINT来定义整型字段。
应该尽量把字段设置为NOT NULL,这样在将来执行查询的时候,数据库不用去比较NULL值。
对于某些文本字段,例如“省份”或者“性别”,我们可以将它们定义为ENUM类型。因为在MySQL中,ENUM类型被当作数值型数据来处理, 而数值型数据被处理起来的速度要比文本类型快得多。这样,我们又可以提高数据库的性能。



2.索引优化
表的主键、外键必须有索引;
数据量大的表应该有索引;
经常与其他表进行连接的表,在连接字段上应该建立索引;
经常出现在Where子句中的字段,特别是大表的字段,应该建立索引;
索引应该建在选择性高的字段上; (sex 性别这种就不适合)
索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;
频繁进行数据操作的表,不要建立太多的索引;
删除无用的索引,避免对执行计划造成负面影响;
表上建立的每个索引都会增加存储开销,索引对于插入、删除、更新操作也会增加处理上的开销。
另外,过多的复合索引,在有单字段索引的情况下,一般都是没有存在价值的;
相反,还会降低数据增加删除时的性能,特别是对频繁更新的表来说,负面影响更大。


3.sql语句优化
SELECT语句务必指明字段名称(避免直接使用select * )
SQL语句要避免造成索引失效的写法
SQL语句中IN包含的值不应过多
当只需要一条数据的时候,使用limit 1
如果排序字段没有用到索引,就尽量少排序
如果限制条件中其他字段没有索引,尽量少用or
尽量用union all代替union
避免在where子句中对字段进行null值判断
不建议使用%前缀模糊查询
避免在where子句中对字段进行表达式操作
Join优化 能用innerjoin 就不用left join right join,如必须使用 一定要已小表为驱动



4.缓存优化 (数据库自身缓存  redis缓存 等等 )
为了提高查询速度,我们可以通过不同的方式去缓存我们的结果从而提高响应效率。

数据库本身也是支持缓存的 --> 查询缓存query_cache , 默认查询缓存是关闭的
需要我们在mysql.ini 配置文件中开启:
开启方法: 
query_cache_type=0   #关闭查询缓存
query_cache_type=1   #开启查询缓存,mysql自动帮我们缓存满足条件的查询数据
query_cache_type=2   #开启查询缓存,需要在参数中手动指定要缓存的查询

不过因为我们的课程体系主要讲解的是redis,所以在这里可以引入redis的知识点。



5.主从复制、读写分离
如果数据库的使用场景读的操作比较的时候,为了避免写的操作所造成的性能影响 可以采用读写分离的架构,
读写分离,解决的是,数据库的写入,影响了查询的效率。读写分离的基本原理是让主数据库处理事务性
增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。 
数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。
参考: https://www.jianshu.com/p/faf0127f1cb2


6.mysql的分库分表
数据量越来越大时,单体数据库无法满足要求,可以考虑分库分表
两种拆分方案:
垂直拆分:(分库)
业务表太多? 将业务细化 不同的小业务专门用一个库来维护
水平拆分:(分表)
单个表存的数据太多,装不下了? 将该表查分成多个

分库分表经常使用的数据库中间件:
MyCat
https://www.cnblogs.com/chongaizhen/p/11083226.html



Sharding-JDBC
https://blog.csdn.net/forezp/article/details/94343671

并发编程相关面试题汇总

什么是线程?

同一个进程内可以执行多个任务,而这每一个任务就可以看做一个线程。
线程 : 是程序的执行单元 也叫执行路径。是程序使用cpu的基本单位。
单线程:程序只有一条执行路径。
多线程:程序有多条执行路径。

线程和进程有什么区别?

线程是进程的子集,
一个进程可以有很多线程,
每条线程并行执行不同的任务。
不同的进程使用不同的内 存空 间,
而所有的线程共享一片相同的内存空间。
每个线程都拥有单独的栈内存用来存储本地数据。

创建线程的方式

## 继承Thread类

A:自定义类MyThread继承Thread类
B:在MyThread类中重写run()
C:创建MyThread类的对象
D:启动线程对象

## 实现Runnable接口

A:自定义类MyRunnable实现Runnable接口
B:在MyRunnable里面重写run()
C:创建MyRunnable类的对象
D:创建Thread类的对象,并把C步骤的对象作为构造参数传递


## 实现Callable接口
    带返回值的线程任务
A:自定义类MyCallable实现Callable接口(要求带泛型,泛型即是返回值)
B:在MyCallable中实现call方法,并返回执行结果
C:创建FutureTask对象 并传入MyCallable对象
D:创建Thread类对象,并把FutureTask对象作为构造参数传递
E:通过FutureTask对象获取该任务的执行结果

可以直接调用Thread类的run()方法么

当然可以,但是如果我们调用了Thread的run()方法,
它的行为就会和普通的方法一样,为了在新的线程中执行我们的代码,
必须使用Thread.start()方法。

线程的生命周期(几种状态)

线程的状态以及状态之间的相互转换:  
**1、新建状态(New):**新创建了一个线程对象。   
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。   
**3、运行状态(Running):**就绪状态的线程获取了CPU,执行程序代码。   
**4、阻塞状态(Blocked):**阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
**5、死亡状态(Dead):**线程执行完了或者因异常退出了run()方法,该线程结束生命周期。等待被销毁。

线程中run和start方法有什么区别?

Thread的start才是正在开启线程,使线程处于一个就绪状态。

Run只是调用了一个普通方法,并没有启动另一个线程,程序还是会按照顺序执行相应的代码 Start则表示,
重新开启一个线程,不必等待其他线程运行完,只要得到cpu就可以运行该线程

wait和sleep的区别

1、sleep()方法是属于Thread类中的,而wait()方法,则是属于Object类中的。
2、sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持着,
当指定的时间到了又会自动恢复运行状态。所以在调用sleep()方法的过程中,线程不会释放对象锁。
3、调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,
只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

java线程池概述

   java线程池的工作原理和数据库连接池的差不多,因为每次重新创建线程 都是很耗资源的操作,
   所以我们可以建立一个线程池,这样当需要用到线程 进行某些操作时,就可以直接去线程池里面找到空闲的线程,
   这样就可以直接 使用,而不用等到用到的时候再去创建,用完之后可以把该线程重新放入线程池
    供其他请求使用从而提高应用程序的性能。  

用户线程和守护线程有什么区别

当我们在Java程序中创建一个线程,
它就被称为用户线程。
一个守护线程是在后台执行并且不会阻止JVM终止的线程。
当没有用户线程在运行的时候,JVM关闭程序并且退出。
一个守护线程创建的子线程依然是守护线程
比如说我们java中的GC回收 就是一个守护线程

线程的优先级

每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,
这个实现是和操作系统相关的(OS dependent)。我们可以定义线程的优先级,
但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个int变量(从1-10),
1代表最低优先级,10代表最高优先级

线程池的优点

重用线程池中的线程,减少因对象创建,销毁所带来的性能开销;
能有效的控制线程的最大并发数,提高系统资源利用率,同时避免过多的资源竞争,避免堵塞;
能够多线程进行简单的管理,使线程的使用简单、高效。

线程池框架Executor

java中的线程池是通过Executor框架实现的,
Executor 框架包括类:
Executor,Executors,ExecutorService,
ThreadPoolExecutor ,
Callable和Future、FutureTask的使用等
Executor: 所有线程池的接口,只有一个方法。
ExecutorService: 增加Executor的行为,是Executor实现类的最直接接口。
Executors: 提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService 接口。
ThreadPoolExecutor:线程池的具体实现类,一般用的各种线程池都是基于这个类实现的
corePoolSize:线程池的核心线程数,线程池中运行的线程数也永远不会超过 corePoolSize 个,
默认情况下可以一直存活。可以通过设置allowCoreThreadTimeOut为True,此时 核心线程数就是0,
此时keepAliveTime控制所有线程的超时时间。
maximumPoolSize:线程池允许的最大线程数;
keepAliveTime: 指的是空闲线程结束的超时时间;
unit :是一个枚举,表示 keepAliveTime 的单位;
workQueue:表示存放任务的BlockingQueue Runnable队列。
BlockingQueue:阻塞队列(BlockingQueue)是java.util.concurrent下的主要用来控制线程同步的工具。

Java关键字volatile与synchronized作用与区别?

1,volatile 它所修饰的变量不保留拷贝,直接访问主内存中的。 在Java内存模型中,有main memory,
每个线程也有自己的memory (例如寄存器)。为 了性 能,一个线程会在自己的memory中保持要访问的变量的副本
。这样就会出现同一个变量在某个瞬间, 在一个线 程的memory中的值可能与另外一个线程memory中的值,
或者main memory中的值不一致的情况。一 个变量 声明为volatile,就意味着这个变量是随时会被其他线程修改的,
因此不能将它cache在线程memory 中。
2, synchronized 当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多
只有一个线程执行该段代 码。 -、当两个并发线程访问同一个对象object中的这个synchronized( this)同步代码块时 
,一个 时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块 
二、 然而,当一个线程访问object的一个synchronized(this)同步代码块时,
另一个线 程仍然 可以访问该object中的非synchronized (this)同步代码块。
3、 尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时, 
其他线 程对object中所有其它synchronized (this)同步代码块的访问将被阻塞。
4、当一个线程访问object的一个synchronized (this)同步代码块时,它就获得了这个 object的对象锁。
结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
5、以上规则对其它对象锁同样适用.

同步方法和同步块,哪个是更好的选择?

同步块是更好的选择,因为它不会锁住整个对象
(当然你也可以让它锁住整个对象)。
同步方法会 锁住整 个对象,哪怕这个类中有多个不相关联的同步块,
这通常会导致他们停止执行并需要等待获得这个对象上

并发线程局部变量-ThreadLocal

ThreadLocal是一个线程级别的局部变量,并非“本地线程”。
ThreadLocal为每个使用该变量的线程提供了一个独立的变量副本,
每个线程修改副本时不影响其他线程对象的副本 线程局部变量(ThreadLocal variables)的关键点:
一个线程局部变量为每个线程方便的提供了一个单独的变量。
Threadlocal实例通常作为静态的私有的字段出现在一个类中,这个类用来关联一个线程。
当多个线程访问ThreadLocal实例时,每个线程维护ThreadLocal提供的独立的变量副本。
常用的使用可在Dao模式中见到,当Dao类作为一个单例类时,数据库连接被每一个线程独立的维护,互不影响

Lock和synchronized

1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;
而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,
等待的线程会一直等待下去,不能够响应中断;

4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

5)Lock可以提高多个线程进行读操作的效率。

  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,
  而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
  所以说,在具体使用时要根据适当情况选择。

并发安全之volatile

当我们使用volatile关键字去修饰变量的时候,

所以线程都会直接读取该变量并且不缓存它。

这就确保了线程读取到的变量是同内存中是一致的。

并发安全之synchronized

在多线程并发访问资源的时候,由于不是原子操作,所以会导致数据不一致的情况。
为了避免这种情况,需要使用同步机制,同步机制能够保证多线程并发访问数据的时候不会出现数据不一致的情况。

一种同步机制是使用synchronized关键字,这种机制也称为互斥锁机制,
这就意味着同一时刻只能有一个线程能够获取到锁,获得的锁也被称为互斥锁。
其他需要获取该互斥锁的线程只能被阻塞,直到获取到该锁的线程释放锁。在Java中,每个类都有一个内置锁,
之所以如此,是因为Java并发专家认为这样可以避免显式创建锁。

并发安全之Lock

Lock是java在1.5以后提供的线程安全接口,里面定义了几个方法:

如:

lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的。
unLock()方法是用来释放锁的
Lock lock = ...;
if(lock.tryLock()) { //获取锁
try{
//处理任务
}catch(Exception ex){

​```
 }finally{
     lock.unlock();   //释放锁
 } 

​```

}else {
//如果不能获取锁,则直接做其他事情
}
使用Lock必须要求我们手动释放锁。 (lock.unlock()方法)

不然可能造成死锁。

不可变对象对多线程有什么帮助

前面有提到过的一个问题,

不可变对象保证了对象的内存可见性,

对不可变对象的读取不需要进行额外的同步手段,

提升了代码执行效率。 前面有提到过的一个问题,

不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步手段,

提升了代码执行效率。 前面有提到过的一个问题,不可变对象保证了对象的内存可见性,

对不可变对象的读取不需要进行额外的同步手段,提升了代码执行效率。

线程之间是如何通信的?

当线程间是可以共享资源时,

线程间通信是协调它们的重要的手段。

Object类中wait()\notify()\notifyAll()方法

可以用于线程间通信关于资源的锁的状态

notify和notifyAll区别

他们的作用都是通知处于等待该对象的线程。 
1、notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,
一旦该对象被解锁,他们就会去竞争。 
2、notify是通知其中一个线程,不会通知所有的线程。

volatile关键字在Java中有什么作用?

当我们使用volatile关键字去修饰变量的时候,

所以线程都会直接读取该变量并且不缓存它。

这就确保了线程读取到的变量是同内存中是一致的。

常见线程名词解释

主线程:JVM调用程序main()所产生的线程。
当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。
后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。
用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束
前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系
。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。
可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。

线程类的一些常用方法:

sleep(): 强迫一个线程睡眠N毫秒。   

isAlive(): 判断一个线程是否存活。   

join(): 等待线程终止。   

activeCount(): 程序中活跃的线程数。   

enumerate(): 枚举程序中的线程。

currentThread(): 得到当前线程。   

isDaemon(): 一个线程是否为守护线程。   

setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,
是否等待主线程依赖于主线程结束而结束)   

setName(): 为线程设置一个名称。   

wait(): 强迫一个线程等待。   

notify(): 通知一个线程继续运行。   

setPriority(): 设置一个线程的优先级。

什么是死锁

死锁就是两个或两个以上的线程被无限的阻塞,线程之间相互等待所需资源。
这种情况可能发生在当两个线程尝试获取其它资源的锁,而每个线程又陷入无限等待其它资源锁的释放,
除非一个用户进程被终止。就 JavaAPI 而言,线程死锁可能发生在一下情况。 
当两个线程相互调用 Thread.join () 当两个线程使用嵌套的同步块,一个线程占用了另外一个线程必需的锁,
互相等待时被阻塞就有可能出现死锁

Semaphore有什么作用

Semaphore就是一个信号量,它的作用是限制某段代码块的并发数。
Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,
如果超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。
由此可以看出如果Semaphore构造函数中传入的int型整数n=1,相当于变成了一个synchronized了。

CyclicBarrier和CountDownLatch的区别

两个看上去有点像的类,都在java.util.concurrent下,都可以用来表示代码运行到某个点上,二者的区别在于:

(1)CyclicBarrier的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,
所有线程才重新运行;CountDownLatch则不是,某线程运行到某个点上之后,只是给某个数值-1而已
,该线程继续运行

(2)CyclicBarrier只能唤起一个任务,CountDownLatch可以唤起多个任务

(3)CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了

线程调度器(Thread Scheduler)和时间分片(Time Slicing)

线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。
一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。 
时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。
分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,
所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。

参考面试宝典:

http://chenjin.net.cn

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐