1、讲一下Java的集合框架
说到集合框架我们可以把集合框架分为两个部分,单列集合collection和双列集合顶层接口 map。其中map的存储是key-value形式,collection下面有set接口特点是存储无序不可重复,list接口存储有序可重复。
列表list(集合有序可重复,可有null元素) | 集set(元素是无序不可重复的) | map(顶层接口,存储的是键值对,键是唯一的。) |
---|---|---|
Vector:底层数据结构是数组数据结构.特点是查询和增删速度都很慢。 集合长度。线程安全。 | HashSet:底层数据结构是哈希表、存取速度快、元素唯一、线程不安全。 | HashMap:底层是哈希表数据结构;允许使用null键和null值;线程不安全,效率高; |
ArrayList:底层的数据结构是数组数据结构,特点是查询速度快(因为带下标),但是增删速度稍慢,因为当元素多时,增删一个元素则所有元素的下标都得改变,线程不安全。默认长度是10,当超过长度时,按1.5倍延长集合长度。 | TreeSet:底层数据结构式二叉树。可以对Set集合中的元素进行排序。元素有序、线程不安全。 | HashTable: 底层是哈希表数据结构;不可以使用null键和null值线程安全效率低,因为它里面的方法都是用了 synchronized关键字修饰。 |
LinkedList:底层数据结构式双向链表数据结构(即后面一个元素记录前一个),特点:查询速度慢,因为每个元素只知道前面一个元素,但增删速度快,因为元素再多,增删一个只要让其前后 的元素 重新相连即可,线程不安全。 | ①特点:有序的,保证元素不可重复的前提下,维护了一层添加顺序,判断是否重复的依据:hashCode、equals | TreeMap:底层是二叉树结构;允许使用null键和null值;线程不安全; |
2、定义线程安全的map,有哪些方法,ConcurrentHashMap原理
①定义线程安全的Map可以通过在每个方法上添加synchronized关键字实现或者使用Collections.synchronizedMap方法来保证线程安全。但是这样效率比较低。
②还可使用ConcurrentHashMap。原理是采用分段锁的机制,将整个Map分成多个Segment,在每个Segment上都加锁,不同的线程可以同时访问不同的Segment,这样兼顾了线程安全和运行效率。
3、equals与==
①==:如果比较的对象是基本数据类型,则比较的是数值;如果比较的是引用数据类型,则比较的是对象的地址值。
②equals():用来比较两个对象的内容是否相等。equals方法不能用于基本数据类型的变量,如果没有对equals方法进行重写,则比较的是对象的地址值。
4、hashtable和hashmap的区别
hashmap底层是哈希表存储是无序的它和HashTable最大的区别是hashmap线程不安全并且key值允许为null,而HashTable线程安全并且key值不允许为null,由于Hashtable直接在put和get方法上面加synchronized关键字来实现线程安全所有操作都需要竞争同一把锁所以效率很低。
区别 | 存储 | 底层 | 如何选择 | key是否允许null | 是否线程同步 |
---|---|---|---|---|---|
HashMap | 存储无序 | 哈希表 | 不需要排序 | 允许 | 非线程安全 |
HashTable | 存储无序 | 哈希表 | 需要线程安全 | 不允许 | 线程安全 |
TreeMap | 存储有序 | 红黑树 | 需要排序 | 不允许 | 非线程安全 |
LinkedHashMap | 存储有序 | 链表和哈希表 | 需要存储有序 | 允许 | 非线程安全 |
<span style="color:#000000"><span style="background-color:#1d1f21"><code class="language-java"><span style="color:#b294bb">public</span> <span style="color:#b294bb">class</span> Test1<span style="color:#999999">{</span>
<span style="color:#969896">//1、私有化本类所有的构造方法</span>
<span style="color:#b294bb">private</span> Test1<span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">{</span><span style="color:#999999">}</span>
<span style="color:#969896">//2、直接在本类中创建唯一对象</span>
<span style="color:#b294bb">private</span> <span style="color:#b294bb">static</span> Test1 t1 <span style="color:#a67f59">=</span> <span style="color:#b294bb">new</span> Test1<span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#969896">//3、提供外界获取唯一对象的方法(公共的、静态的)</span>
<span style="color:#b294bb">public</span> <span style="color:#b294bb">static</span> Test1 <span style="color:#81a2be">getInstance</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">{</span>
<span style="color:#b294bb">return</span> t1<span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#999999">}</span>
</code></span></span>
懒汉式单例设计模式的特点
①懒汉式模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance (获取实例)方法 时才去创建这个单例。(比较懒别人喊一次才动一次?)。
②好处:不存在浪费内存的问题,弊端:在多线程环境下,可能不能保证对象是唯一的
<span style="color:#000000"><span style="background-color:#1d1f21"><code class="language-java"><span style="color:#b294bb">public</span> <span style="color:#b294bb">class</span> Test2<span style="color:#999999">{</span>
<span style="color:#969896">//1、私有化本类所有的构造方法</span>
<span style="color:#b294bb">private</span> Test2<span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">{</span> <span style="color:#999999">}</span>
<span style="color:#969896">//private和static修饰的成员变量</span>
<span style="color:#b294bb">private</span> <span style="color:#b294bb">static</span> Test2 t2<span style="color:#999999">;</span>
<span style="color:#969896">//3、提供外界获取唯一对象的方法(公共的、静态的)</span>
<span style="color:#b294bb">public</span> <span style="color:#b294bb">static</span> Test2 <span style="color:#81a2be">getInstance</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">{</span>
<span style="color:#b294bb">if</span><span style="color:#999999">(</span>t2 <span style="color:#a67f59">==</span> <span style="color:#b294bb">null</span><span style="color:#999999">)</span><span style="color:#999999">{</span>
<span style="color:#969896">//2、在本类中创建唯一对象</span>
t2 <span style="color:#a67f59">=</span> <span style="color:#b294bb">new</span> Test2<span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#b294bb">return</span> t2<span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#999999">}</span>
</code></span></span>
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
5、什么是哈希表
哈希表又叫散列表是一种数据结构,特点是查找增删都很快。哈希表里面每个数据都有唯一的键(key),把这个关键码值(key)通过映射函数映射到哈希表中一个位置来访问。
这个映射函数叫做哈希函数(散列函数),可以把任意长度的key值变换输出成固定长度的哈希值(Hash value)。一个好的哈希函数能够将键均匀地映射到哈希表中,以减少冲突和查找时间。
Hash code是一种编码方式,在Java中,每个对象都会有一个hashcode。Java可以通过这个hashcode来识别一个对象。
6、什么是哈希冲突,怎么解决
哈希函数把key变换输出为哈希码(哈希值),当不同的key值产生的哈希值H(key)是一样的,就产生了哈希冲突。
解决哈希冲突的方法
(1) 再哈希法
当发生冲突时,用不同的哈希函数计算地址,直到无冲突。虽然不易发生聚集,但是增加了计算时间。
(2) 链地址法
通过将具有相同哈希码的数据元素存储在一个链表中,来避免冲突的发生。
在查询、插入或删除数据元素时,首先根据哈希码找到对应的链表,然后在链表中搜索或修改相应的数据元素。
(3)建立公共溢出区
将哈希表分为基本表和溢出表两部分,将冲突的元素存储在另一个溢出表中
7、final关键字可以修饰哪些对象
final类:不可被继承,如java.lang.Math就是一个 final类,不可被继承。
final方法:不可被重写
final变量:final修饰的变量是一个常量一般和static联用。只能被赋值一次在初始化后不可改变变量值。如果final变量是引用变量,则不可以改变它的引用对象,但可以改变对象的属性。
public static final double pi=3.14;
8、lise集合便利查找其中的一项怎么处理比较快。
9、固定的不可变的一些对象,放到哪里让全局都可以使用?
放在常量的类里,配置到数据库里,放在配置文件里,放到缓存里面,第三方配置中心
1.2)java8新特性,xxx原理。反射等高级问题
1、Java8有哪些新特性
Java8出现了很多新特性我说几个比较大的改变,第一个是接口可以写默认方法和静态方法,默认方法用default修饰符标记,不强制实现类实现默认方法。第二个是stream流,提供了便捷快速的操作数据的方式。第三个是Lambda表达式和函数式接口,函数式接口仅有一个抽象方法的接口,Lambda 表达式本质上是一个匿名方法,让我们的代码更简洁直观。第四个Java 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。
2、单例模式
1)饿汉式单例设计模式的特点
①饿汉式模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance (获取实例)方法之前单例就已经存在了。(比较饿,没喊就迫不及待的创建了)。
②好处:就算在多线程环境下,也一定可以保证对象是唯一的。弊端:创建比较早,有浪费内存的现象。
2)懒汉式单例设计模式的特点
在第一次使用时才创建实例,但需要考虑线程安全问题。
第二章、深入技术栈
2.1)JVM 运行机制等问题
1、JVM是什么 包含哪些模块
①JVM是Java虚拟机,是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java语言非常重要的特点跨平台性就是通过jvm实现,Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。
整个JVM 框架由加载器加载文件,然后执行器在内存中处理数据,通过本地接口与异构系统交互
②JVM包括
1、类加载器(Class Loader )类加载器的作用是加载类文件到内存
2、执行器(Execution Engine) 执行引擎执行引擎也叫做解释器,负责解释命令,提交操作系统执行。
3、本地接口(Native Interface )作用是融合不同的编程语言为Java 所用
4、运行时数据区(Runtime data area) 我们所有写的程序都被加载到这里之后才开始运行。包括:
(1)线程共享的堆(含有字符串常量池),元空间(元空间在本地内存中,包含了加载的类信息和运行时常量池)
(2)线程私有的虚拟机栈、本地方法栈以及程序计数器
2、JVM 内存区域(运行时数据区)
①栈是运行时的单位 , 是线程私有的,它的生命周期和线程相同,随着线程的创建而创建,随着线程的死亡而死亡。所有的 Java 方法调用都是通过栈来实现的,方法调用的数据需要通过栈进行传递,每一次方法调用都会有一个对应的栈帧被压入栈中,每一个方法调用结束后,都会有一个栈帧被弹出。
②Java 堆是所有线程共享的最大的一块内存区域,唯一目的就是存放对象实例。里面包含字符串常量池(针对字符串专门开辟的一块区域,主要目的是为了避免字符串的重复创建。)。因为Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆。通过垃圾回收算法 Java 堆还可以细分为:新生代和老年代。
③方法区(元空间)
方法区是被所有线程共享,方法区会存储已被虚拟机加载的 类信息、字段信息等。JDK1.8 最大的变化就是方法区的位置转移到了本地内存中被称为元空间。元空间中还包含运行时常量池:各种字面量和符号引用的常量池表 。
④程序计数器
线程私有的,是一块很小的内存空间,每个线程都有一个程序计数器,代码的流程控制,如分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
⑤本地方法栈
本地方法栈为JVM调用native方法时服务,一个Native方法就是一个java调用非java代码的接口。
PS:直接内存在本地内存中,不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现
3、垃圾收集器之G1 收集器
①垃圾回收器通常是作为一个单独的低级别的线程运行,通过垃圾回收算法对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
②JDK9 开始,G1 垃圾收集器成为了默认的垃圾收集器。使用标记整理算法主要有初始标记,并发标记,最终标记,筛选回收四个过程。G1将整个堆分为大小相等的多个Region(区域),G1跟踪每个区域的垃圾大小,在后台维护一个优先级列表,每次根据允许的收集时间,优先回收价值最大的区域,已达到在有限时间内获取尽可能高的回收效率。
4.1、内存中死亡对象判断方法
①引用计数法
每当有一个地方引用它,计数器就加 1;
当引用失效,计数器就减 1;
任何时候计数器为 0 的对象就是不可能再被使用的。
②可达性分析算法
以 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。
引用的概念:
引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)
1、强引用:最普遍的引用如String s = new String(“aaa”)。如果一个对象具有强引用,垃圾回收器绝不会回收它
2、软引用:如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。
3、弱引用:的对象拥有更短暂的生命周期。一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
4、虚引用:虚引用并不会决定对象的生命周期。是一种形同虚设的引用,随时会被回收。
4.2、新老年代的含义和晋升逻辑
①长期存活的对象将进入老年代
对象首先在 Eden 区域分配。如果对象在 Eden 出生并经过第一次 Minor GC (新生代GC)后仍然能够存活年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。
②大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
③新生代 GC(Minor GC):指发生新生代的的垃圾收集动作,Minor GC 非常频繁,也比较快。
老年代 GC(Major GC/Full GC):指发生在老年代的 GC,Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。
4.3、垃圾回收算法
①标记清除算法
标记阶段,标记所有的可访问对象。
收集阶段,垃圾收集算法扫描堆并回收所有的未标记对象。
② 标记整理算法
标记-整理(Mark-and-Compact)算法是根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
③复制算法
将内存按照容量大小分为大小相等的两块,每次只使用一块,当一块使用完了,就将还存活的对象移到另一块上,然后在把使用过的内存空间移除。特点:不会产生空间碎片;内存使用率极低
④分代收集算法
不同的对象的生命周期是不一样的。分代回收把不同生命周期的对象 放在不同代上,在新生代中,有大量对象死去和少量对象存活,所以采用复制算法,老年代中因为对象的存活率极高,所以采用标记清理或者标记整理算法进行回收。
5、常用 JVM 调优手段
1、内存分配:根据应用程序的需求调整JVM的内存分配。如果应用程序需要处理大量数据,则可以增加JVM的堆内存和非堆内存。
2、垃圾回收:JVM的垃圾回收是一项重要的任务,它可以释放无用的对象并回收内存。通过选择不同的垃圾回收器、调整垃圾回收器的参数和设置合适的内存阈值等,可以提高垃圾回收的效率。
3、线程管理:JVM中的线程是Java应用程序的核心组成部分。通过调整线程的数量、设置线程的优先级和使用线程池等方式,可以提高Java应用程序的并发性能。
4、类加载:Java应用程序需要在运行时动态加载类。通过优化类的加载过程,可以提高应用程序的启动速度和响应性能。
5、编译优化:JIT编译器可以将Java代码编译为本机代码,从而提高应用程序的执行速度。通过设置JIT编译器的参数和选择适当的编译器,可以提高编译器的性能和效率。
6、I/O优化:I/O操作是Java应用程序中常见的性能瓶颈之一。通过使用缓冲区、选择合适的I/O库、减少I/O操作次数等方式,可以提高I/O操作的效率。
6、类的加载
JVM中类的装载是由类加载器和它的子类来实现的,类加载器负责在运行时查找和装入类文件中的类。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。
1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
2)如果类中存在初始化语句,就依次执行这些初始化语句。
7、主内存和本地内存
①主内存 :所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量)
②本地内存 :每个线程都有一个私有的本地内存来存储共享变量的副本,并且,每个线程只能访问自己的本地内存,无法访问其他线程的本地内存。本地内存是 JMM 抽象出来的一个概念,存储了主内存中的共享变量副本。
8、类什么时候被初始化?
1)创建类的实例,也就是 new 一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4) 反 射 (Class.forName(“com.lyj.load”))
5)初始化一个类的子类(会首先初始化子类的父类)
6)JVM 启动时标明的启动类,即文件名和类名相同的那个类只有这 6 中情况才会导致类的类的初始化。
类的初始化步骤:
1)如果这个类还没有被加载和链接,那先进行加载和链接
2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
3)加入类中存在初始化语句(如 static 变量和 static 块),那就依次执行这些初始化语句。
9、内存溢出内存泄露的区别
10、说一下双亲委派机制
2.2)数据库事务、索引, 优化 SQL 等
1.1、隔离级别
①读未提交 : 可以看到其他事务没有提交的结果。等于没有任何隔离性。
②读已提交 :只能看到其他事务已经提交的改变。这种隔离级别会引起不可重复读。
③可重复读: 它保证了可重复读取同样的数据,即同一个事务多次读取操作数据会看到同样的数据。解决了不可重复读但可能造成幻读。
④可串行化 : 通过强制事务排序,解决了幻读的问题。它在每个读的数据行上面加上共享锁。(实际中基本不使用)
不同隔离级别可能导致的问题
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 可能 | 可能 | 可能 |
读已提交 | 不可能 | 可能 | 可能 |
可重复读 | 不可能 | 不可能 | 可能 |
可串行化 | 不可能 | 不可能 | 不可能 |
1.2、脏读幻读,不可重复读和幻读的区别
①脏读:当一个事务读取到另外一个事务修改但未提交的数据时,就可能发生脏读。
②不可重复读:就是说,比如在A事务中进行多次相同的查询,B事务在A事务多次查询之间修改对应表中的数据,导致A事务多次读取的结果不一致。
③幻读是’‘不可重复读’'的一种特殊场景:例子:事务一将表中性别列的值都更改为1,事务二又添加了一条"性别”的值为0记录,事务一再查询所有的记录时会发现有多了一条记录的“性别为0,这种情况就是所谓的幻读
不可重复读和幻读区别
不可重复读:一个事务中多次读取同一数据,但是由于其他事务的修改,导致两次读取的结果不一致。
幻读:一个事务中多次读取同一范围内的数据,但是由于其他事务的插入操作,导致两次读取的结果不一致。
2、事务的特性
①原子性
事务要么全部提交成功,要么全部失败回滚,不能分割执行其中的操作。
②一致性
事务的执行不会破坏数据关系的完整性和业务逻辑的完整性。
③隔离性
一个事务不会被另一个事务影响,第一个事务执行完成后再执行另一个事务,但处于性能上的考虑,一般都需要事务并发执行,所以隔离程度有区别。
④持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
3、数据库批处理有了解吗
批处理(batch) 操作数据库,批处理指的是一次操作中执行多条SQL语句。
第一步:打开mysql的批处理:
<span style="color:#000000"><span style="background-color:#1d1f21"><code class="language-java">如<span style="color:#a67f59">:</span> url<span style="color:#a67f59">=</span>jdbc<span style="color:#a67f59">:</span>mysql<span style="color:#a67f59">:</span><span style="color:#a67f59">/</span><span style="color:#a67f59">/</span><span style="color:#b5bd68">127.0</span><span style="color:#b5bd68">.0</span><span style="color:#b5bd68">.1</span><span style="color:#a67f59">:</span><span style="color:#b5bd68">3306</span><span style="color:#a67f59">/</span>db5<span style="color:#a67f59">?</span>characterEncoding<span style="color:#a67f59">=</span><span style="color:#b5bd68">UTF</span><span style="color:#a67f59">-</span><span style="color:#b5bd68">8</span><span style="color:#a67f59">&</span>rewriteBatchedStatements<span style="color:#a67f59">=</span><span style="color:#de935f">true</span>
</code></span></span>
第二步:使用Statement接口或者Preparedstatement接口
保存将执行的SQL语句,执行保存的SQL语句
<span style="color:#000000"><span style="background-color:#1d1f21"><code class="language-java">增加批处理:addBatch<span style="color:#999999">(</span>String sql<span style="color:#999999">)</span> 将要执行的<span style="color:#b5bd68">SQL</span>先保存在当前命令列表中,先不执行
执行批处理:<span style="color:#b294bb">int</span><span style="color:#999999">[</span><span style="color:#999999">]</span> <span style="color:#81a2be">executeBatch</span><span style="color:#999999">(</span><span style="color:#999999">)</span> 执行<span style="color:#b5bd68">SQL</span>语句,将批处理中所有<span style="color:#b5bd68">SQL</span>语句执行,
返回一个数组,这个数组是说明每条命令所影响的行数
清空批处理:<span style="color:#81a2be">clearBatch</span><span style="color:#999999">(</span><span style="color:#999999">)</span> <span style="color:#a67f59">:</span> 清空当前批处理的语句
</code></span></span>
4.1、数据库优化
①数据库参数配置优化:
很多配置默认参数(如最大连接数、数据库占用的内存等),不是最佳的配置,例如最大连接数默认为100,当请求超过100时都要等待。
②分库分表:
后面第五题有讲
③优化表结构的设计:
包括字段数据类型,比如人的年龄用无符号的unsigned tinyint即可,没必要用integer。数据类型的长度比如用户的手机号11位长度,没必要用255个长度。有外键约束会影响插入和删除性能,如果程序能够保证数据的完整性,那在设计数据库时就去掉外键。
④SQL语句的优化:
其中最重要的方式就是使用索引,还有#{}防止sql注入
⑤事务优化:
避免大事务,操作的数据比较多的事务会带来风险:锁定太多的数据,造成大量的阻塞和锁超时,回滚时所需时间比较长,执行时间长容易造成主从延迟
服务器层面
①主从复制,读写分离: 一台MySQL服务器同一时间点支持的并发数是有限的,所以增加MySQL服务器的数量也是一种增强数据库性能的方式。使用MySQL主从复制,增删改操作走Master主服务器,查询走Slaver从服务器,这样就减少了只有一台MySQL服务器的压力。
②增加缓存层
增加缓存层,减少数据库连接也是一种优化手段,有些查询可以不用访问数据库,可以通过使用缓存服务器如redis、memcache、elasticsearch等增加缓存,减少数据库的连接
③升级服务器硬件
更快的磁盘IO设备,更强的CPU,更大的内存,更大的网卡流量(带宽)等。
4.2、查询语句优化
①使用索引, 在SQL语句的WHERE和JOIN部分中用到的所有字段上,都应该加上索引。
②尽量防止索引失效,下面第七题有说索引失效原因
③分页查询优化 ,如果数据量较大,一次性获取所有数据会导致性能问题。应该通过使用LIMIT关键字,进行分页查询。
④where后面条件查询只在自己需要的范围查询
⑤select后面尽量不要使用 * 只选择你需要的字段
⑥OR改写成IN ,OR的效率是n级别,IN的效率是log(n)级别,IN的个数建议控制在200以内;
⑦能使用BETWEEN不用IN ,SELECT id FROM t WHERE num BETWEEN 1 AND 5;
⑧使用INNER JOIN代替子查询、使用EXISTS或NOT EXISTS代替IN和NOT IN等。
4.3、explain 执行计划
使用explain 执行计划进行优化,使用explain关键字可以知道MySQL是如何处理你的SQL语句以及表结构的性能瓶颈。使用方法,在select语句前加上EXPLAIN就可以了。如:
<span style="color:#000000"><span style="background-color:#1d1f21"><code class="language-java"><span style="color:#b5bd68">EXPLAIN</span> select <span style="color:#a67f59">*</span> from TableName<span style="color:#999999">;</span>
</code></span></span>
- 1
这篇文章写的很好,看这个就行SQL优化之EXPLAIN执行计划
5、数据库分库分表
分库:就是一个数据库分成多个数据库,部署到不同机器。
①业务量剧增,MySQL单机磁盘容量会撑爆,在高并发的场景下大量请求访问数据库,MySQL单机是扛不住的!分库可以增加磁盘容量,应对高并发,。
垂直分库:比如原来的数据库有用户表,订单表、积分表、商品表把他们拆分成用户库、订单库、积分库、商品库。
水平分库:将表的数据量切分到不同的数据库服务器上,每个服务器具有相同的库和表,只是表中的数据集合不一样。
分表:就是一个表分成多个表。
①数据量太大的话,一个查询SQL没命中索引,千百万数据量的表可能会拖垮这个数据库。
②高度为3的B+树可以存差不多千万的数据,单表数据量超过千万,就需要分表了否则磁盘IO次数会增多。
垂直分表:用户表包含id、name、age、email、desc,如果email、desc等字段不常用,我们把它拆分到另外一张表,命名为用户详细信息表。
水平分表:一个表的数据量太大,可以按照规则(如hash取模、时间范围等),把数据切分到多张表,将常用数据从表中独立出来。比如 在登录业务中只需要查询用户名和密码。
6、什么是索引,索引有几种
①索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。
②根据存储方式分为:
聚集索引(InnoDB引擎)
主键索引属于聚簇索引的叶子节点会存储指针的值和数据行,也就是说数据和索引是在一起,这就是聚簇索引,InnoDB中也只有主键索引才能是聚簇索引。
非聚集索引(MyISAM引擎)
二级索引(辅助索引)属于非聚簇索引,叶子节点只会存储数据行的指针,简单来说数据和索引不在一起,就是非聚聚簇索引;
③根据索引特点可以分为:主键索引,唯一索引,普通索引,多列索引,前缀索引,全文索引
6、索引原理
④索引就像目录,索引利用特定数据结构存储避免进行全表扫描。mysql默认索引结构B+树,特点是一个节点可以有多个分支节点,同时每个分支节点只存储key而不存储数据,只在叶子节点存储数据和key。这样大大降低了查找深度,减少了磁盘IO次数。
7、索引失效的原因/场景
①索引列作为计算的一部分或者使用函数,那么索引会失效。例如,下面这个查询无法使用 actor_ id 列的索引:
<span style="color:#000000"><span style="background-color:#1d1f21"><code class="language-java"><span style="color:#b5bd68">SELECT</span> actor_id <span style="color:#b5bd68">FROM</span> sakila<span style="color:#999999">.</span>actor <span style="color:#b5bd68">WHERE</span> actor_id <span style="color:#a67f59">+</span> <span style="color:#b5bd68">1</span> <span style="color:#a67f59">=</span> <span style="color:#b5bd68">5</span><span style="color:#999999">;</span>
</code></span></span>
- 1
②不符合最左匹配原则,例如定义了 (a,b,c) 联合索引,相当于构造了 (a)、(a,b)、(a,b,c) 索引。如果要使 c 索引实际工作,那么必须在 WHERE 中同时加入 a、b 字段的条件,顺序无所谓。
③WHERE 子句的查询条件里使用了比较操作符 LIKE和正则,第一个字符不是通配符的情况下才能使用索引。比如查询条件是 LIKE ‘abc%’,MySQL 将使用索引,如果条件是 LIKE ‘%abc’,MYSQL 将不使用索引。
<b④查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找。常见的范围查找有:LIKE ‘%abc’,和通过 <,>,<= ,\ >=,\ between,!=,或者 <> 操作符做比较。它们都是导致索引失效。
注意事项:IN() 和exists不是范围匹配,而是多个等值匹配,因此并不会导致索引失效。
⑤ 如果 WHERE 子句带有 or 且有字段不属于索引,那么即使其中带索引的字段也不会使用。
⑥使用了select*,大概率会查询非索引列的数据就无法使用覆盖索引了
⑦对查询结果排序时使用order by,不符合索引结构的顺序。
⑧索引列值为null,致COUNT(*)不能走索引
查询诸如SELECT COUNT(*) FROM Table 的时候,因为HASHSET中不能存储空值的,所以优化器不会走索引。
⑨not in 和 not exists使索引失效 而IN() 和exists不是范围匹配,而是多个等值匹配,因此并不会导致索引失效。
⑩字段类型不同 ,比如你的索引字段是varchar型,但是你搜索条件却是 userid=333,那这样索引不生效。
8、聚集索引和非聚集索引的区别
聚集索引(InnoDB引擎)
主键索引属于聚簇索引的叶子节点会存储指针的值和数据行,也就是说数据和索引是在一起,这就是聚簇索引,InnoDB中也只有主键索引才能是聚簇索引。
非聚集索引(MyISAM引擎)
二级索引(辅助索引)属于非聚簇索引,叶子节点只会存储数据行的指针(innoDB是主键),简单来说数据和索引不在一起,就是非聚聚簇索引;
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
9、什么字段不能做索引
应创建索引的字段
应创建索引的场景
1、作为主键必须有索引;(主键)
2、经常用在连接的列上,主要是一些外键,可以加快连接的速度(外键)
3、经常需要搜索的列上,加快搜索的速度; (需搜索)
4、经常需要排序的列上创建索引,利用索引已经排序,加快排序查询时间;(需排序)
5、 经常需要根据范围搜索的列上,因为索引已经排序,其指定的范围是连续的;(范围)
6、经常使用在WHERE子句中的列上,加快条件的判断速度。(where)
7、如果复合索引中包含的字段经常单独出现在Where子句中,则分解为多个单字段索引;
不应创建索引的字段
1、查询中很少用到的列,有索引并不能提高查询速度。
2、取值很少的列,比如数据表中的性别列
3、text, image和bit数据类型的列,这些列的数据量要么相当大,要么取值很少。
4、被频繁更新的列,会提高搜索性能,但是会降低修改性能
10、什么情况下使用索引
要提升查询速度的时候,使用索引。索引出现的目的就是为了提升查询速度。使用索引的过程中我们应该避免索引失效。
11、MySQL数据库limit分页的使用
limit startIndex,length
startIndex是起始下标,length是长度,limit 0,5 表示从0开始,取长度为5的数据。
limit执行顺序在order by后面
12、说一下怎么做数据库的事务,除注解外如何使用事务
JDBC中进行事务处理
Connection提供了事务处理的方法,通过调用setAutoCommit(false)可以设置手动提交事务;当事务完成后用commit()显式提交事务;如果在事务处理过程中发生异常则通过rollback()进行事务回滚。除此之外还引入了Savepoint(保存点)的概念,允许通过代码设置保存点并让事务回滚到指定的保存点。
Spring 中进行事务处理
Spring事务包括编程式事务和声明式事务。
编程式事务需要在代码中手动控制事务的开始、提交和回滚等操作
首先通过transactionManager.getTransaction方法获取事务对象status,然后在try块中执行转账操作,最后通过transactionManager.commit(status)提交事务。如果在转账操作中发生了异常,则会通过transactionManager.rollback(status)回滚事务。
声明式事务则是通过在配置文件中声明事务的切入点和通知等信息来自动控制事务的行为。
声明式的事务管理是用Spring的AOP来实现的;在配置文件中声明事务管理器、事务通知等,然后在需要使用事务的方法上添加事务切面的注解即可。可以配置只读事务和回滚事务(传播行为为REQUIRED)当出现错误后进行回滚操作。在项目中通过aop切入事务到serivce层,这样做能使一次业务逻辑操作,包括几个数据库操作都控制在一个事务中。
下面代码中首先声明了事务管理器transactionManager,然后定义了事务通知tx:Advice,该通知会在transferMoney方法执行时进行事务管理。最后通过aop:config和aop:advisor来将txAdvice应用于transferPointcut定义的切入点上。
声明式事务注解方式
在需要使用事务的方法上添加@Transactional注解,并通过该注解的属性来指定事务的传播机制、隔离级别、超时时间等信息。
在下面的代码中,通过@Transactional注解来声明了事务的传播机制为Propagation.REQUIRED,隔离级别为Isolation.DEFAULT,超时时间为3600秒。在方法执行时,Spring会根据注解中的信息自动管理事务的行为。
指定只读事务的办法为:
bean配置文件中,prop属性增加“read-Only”
或者用注解方式@Transactional(readOnly=true)
相当于将数据库设置成只读数据库,此时若要进行写 的操作,会出现错误。
13、事务的七种传播行为
什么是事务的传播行为:事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法时,事务如何传播。
必须具有事务的上下文 | 不需具有事务上下文 |
---|---|
PROPAGATION REQUIRED 有事务在进行,那么被调用端将在该事务中运行否则的话重新开启一个事务 | PROPAGATION SUPPORTS不需具有事务但如果有一个事务的话,它也可以在这个事务中运行 |
PROPAGATION MANDATORY 必须在一个事务中运行,如果没有事务,将抛出异常表示 | PROPAGATION NOT SUPPORTED 总是不需具有事务,并挂起任何存在的事务。 |
PROPAGATION REQUIRES NEW 没有事务就开启一个新的事务。如果事务已经存在,则将这个存在的事务挂起。 | PROPAGATION NEVER 总是不需具有事务地,如果存在事务,抛出异常 |
PROPAGATION NESTED有事务在运行中,则嵌套在事务中运行,外层事务抛出异常回滚,那么内层事务必须回滚,但是内层事务不影响外层事务。如果封装事务不存在,则同propagation.required的一样 |
15、缓存和数据库同步
1、定时同步:应用程序可以定期将MySQL中的数据同步到Redis中。这种方法的优点是实现简单,缺点是可能会导致数据不一致,因为数据在同步的过程中可能会被修改。
2、实时同步:可以使用消息队列实现MySQL和Redis之间的实时同步。当MySQL中的数据发生变化时,将变更的数据以消息的方式发送到消息队列。触发器或者消息队列会立即通知Redis进行更新。执行对应的数据同步操作。这种方法的优点是实时性高,缺点是实现复杂。
3、双写模式:可以将MySQL和Redis同时写入,确保数据的一致性。这种方法的优点是实现简单,缺点是可能会影响性能。
4、读写分离:可以将MySQL用于写操作,而将读操作从MySQL转移到Redis中。从缓存读取数据,如果读不到从数据库加载然后读入缓存。这种方法的优点是实现简单,提高系统的性能,缺点是可能会导致数据不一致,因为MySQL写操作的数据和Redis读操作的数据之间的同步延迟,与存在一定的延迟。。读写分离是指,只在MySQL中执行写操作。
5.异步写入:异步写入是指先将数据写入MySQL,然后异步将数据写入Redis。这种方法可以提高系统的性能,但是不能保证数据的实时性。
6.利用MySQL的binlog实时同步:通过解析MySQL的binlog,可以获取到数据库的更新操作日志,然后将变更的数据实时同步到Redis中。可以使用Canal等开源工具来实现binlog的解析。
7.利用MySQL的触发器实时同步:在MySQL中创建触发器,当数据发生变更时,触发器会将变更的数据发送到Redis中。这种方式需要在MySQL中编写触发器逻辑。
17、缓存和数据库同步的双删策略了解嘛
双删策略的执行步骤:
①首次删除缓存:在更新数据库之前,首先删除缓存中的数据。这一步是为了防止在更新数据库的过程中,有线程读取到旧的缓存数据。
②更新数据库:执行数据库的更新操作。
③再次删除缓存:在数据库更新完成后,再次删除缓存中的数据。这一步是为了确保在数据库更新后,缓存中不会保留旧的或不一致的数据。同时,由于此时数据库已经更新完成,新的数据将在下次查询时被加载到缓存中。
注意点:
缓存删除失败的处理:如果缓存删除失败,需要采取重试机制或其他补偿措施来确保缓存数据的最终一致性。
性能考虑:频繁的缓存删除操作可能会对缓存系统的性能产生影响,因此需要根据实际业务场景进行权衡和优化。
事务一致性:在数据库更新和缓存删除之间,需要确保事务的一致性,以避免出现部分更新或回滚的情况。
18、innodb引擎的事务原理
19、了解mvcc吗?
20、b树,b+树的区别
2.3)多线程,锁,并发处理等
1、线程生命周期
线程的生命周期是指一个线程从创建到终止所经历的各个阶段。在Java中,线程的生命周期通常包括以下五种状态:
①新建(New):线程被创建时处于新建状态。它还没有开始运行,没有执行任何代码。
②就绪(Runnable):当线程被启动后,它进入就绪状态。此时,线程已经准备好运行,但是操作系统可能还没有分配到处理器资源。
③运行(Running):当操作系统将处理器资源分配给线程时,它进入运行状态。此时,线程正在执行其代码。
④阻塞(Blocked):当线程需要等待某个条件满足(例如等待I/O操作完成)时,它进入阻塞状态。在阻塞状态下,线程会暂时放弃处理器资源,直到等待的条件满足。
⑤终止(Terminated):当线程执行完毕或因异常而终止时,它进入终止状态。此时,线程不再占用任何资源。
2.1、synchronized关键字
synchronized 关键字,代表加锁,保证被它修饰的方法或者代码块只能有一个线程执行。 synchronized是一个对象锁,也就是它锁的是一个对象。我们无论使用哪一种方法,synchronized都需要有一个锁对象,它可以修饰实例方法,修饰静态方法,修饰代码块
①synchronized修饰实例方法, 在方法上加上synchronized关键字即可。进入同步代码前要获得当前实例(调用这个方法的实例)的锁 。
<span style="color:#000000"><span style="background-color:#1d1f21"><code class="language-java"><span style="color:#b294bb">public</span> <span style="color:#b294bb">class</span> SynchronizedTest1 <span style="color:#999999">{</span>
<span style="color:#b294bb">public</span> <span style="color:#b294bb">synchronized</span> <span style="color:#b294bb">void</span> <span style="color:#81a2be">test</span><span style="color:#999999">(</span><span style="color:#999999">)</span> <span style="color:#999999">{</span>
System<span style="color:#999999">.</span>out<span style="color:#999999">.</span><span style="color:#81a2be">println</span><span style="color:#999999">(</span><span style="color:#b5bd68">"synchronized 修饰 方法"</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#999999">}</span>
</code></span></span>
②synchronized修饰静态方法,加上synchronized关键字即可, 由于静态方法不属于任何一个实例对象,归整个类所有,不依赖于类的特定实例,被类的所有实例共享。给静态方法加synchronized锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前静态方法所在类的Class对象的锁。
<span style="color:#000000"><span style="background-color:#1d1f21"><code class="language-java"><span style="color:#b294bb">public</span> <span style="color:#b294bb">static</span> <span style="color:#b294bb">synchronized</span> <span style="color:#b294bb">void</span> <span style="color:#81a2be">test</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">{</span>
i<span style="color:#a67f59">++</span><span style="color:#999999">;</span>
<span style="color:#999999">}</span>
</code></span></span>
补充:线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
③修饰代码块: synchronized修饰代码块需要传入一个对象。进入同步代码库前要获得给定对象的锁,这里的this 。
<span style="color:#000000"><span style="background-color:#1d1f21"><code class="language-java"><span style="color:#b294bb">public</span> <span style="color:#b294bb">class</span> SynchronizedTest2 <span style="color:#999999">{</span>
<span style="color:#b294bb">public</span> <span style="color:#b294bb">void</span> <span style="color:#81a2be">test</span><span style="color:#999999">(</span><span style="color:#999999">)</span> <span style="color:#999999">{</span>
<span style="color:#b294bb">synchronized</span> <span style="color:#999999">(</span><span style="color:#b294bb">this</span><span style="color:#999999">)</span> <span style="color:#999999">{</span>
System<span style="color:#999999">.</span>out<span style="color:#999999">.</span><span style="color:#81a2be">println</span><span style="color:#999999">(</span><span style="color:#b5bd68">"synchronized 修饰 代码块"</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#999999">}</span>
<span style="color:#999999">}</span>
</code></span></span>
补充;synchronized(object) ,表示进入同步代码库前要获得 给定对象的锁
synchronized(类.class) ,表示进入同步代码前要获得 给定 Class 的锁
注意:
①原子性:确保线程互斥的访问同步代码。synchronized保证只有一个线程拿到锁,进入同步代码块操作共享资源,因此具有原子性。
②可见性:保证共享变量的修改能够及时可见。当某线程进入synchronized代码块前后,线程会获得锁,清空工作内存,从主内存拷贝共享变量最新的值到工作内存成为副本,执行代码,将修改后的副本的值刷新回主内存中,线程释放锁。其他获取不到锁的线程会阻塞等待,所以变量的值一直都是最新的。
③有序性:synchronized内的代码和外部的代码禁止排序,至于内部的代码,则不会禁止排序,但是由于只有一个线程进入同步代码块,因此在同步代码块中相当于是单线程的,根据 as-if-serial 语义,即使代码块内发生了重排序,也不会影响程序执行的结果。
④悲观锁:synchronized是悲观锁。每次使用共享资源时都认为会和其他线程产生竞争,所以每次使用共享资源都会上锁。
⑤独占锁(排他锁):synchronized是独占锁(排他锁)。该锁一次只能被一个线程所持有,其他线程被阻塞。
⑥非公平锁:synchronized是非公平锁。线程获取锁的顺序可以不按照线程的阻塞顺序。允许新来的线程有可能立即获得监视器,而在等待区中等候已久的线程可能再次等待。这样有利于提高性能,但是也可能会导致饥饿现象
⑦可重入锁:synchronized是可重入锁。持锁线程可以再次获取自己的内部的锁,可一定程度避免死锁。
2.2、并发编程的三个概念
①原子性指的是一个不可以被分割的操作,即这个操作在执行过程中不能被中断,要么全部不执行,要么全部执行。且一旦开始执行,不会被其他线程打断。
②可见性 指的是一个线程修改了共享变量后,其他线程能立即感知这个变量被修改。
③有序性 指程序按照代码的先后顺序执行。在Java内存模型中,为了提升效率是允许编译器和处理器对指令进行重排序,当然重排序不会影响单线程的运行结果,但是对多线程会有影响。
3.1、volatile关键字
volatile 是最轻量的同步机制。只能用于变量(类的成员变量、类的静态成员变量),volatile是为了保证多个线程间拿到的变量都是最新的同一个内容,用volatile修饰后使的能够实时同步;
被volatile修饰之后就保证了:
①可见性:保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。除了volatile,Java中的synchronized和final两个关键字 以及各种 Lock也可以实现可见性。
②顺序性:禁止进行指令重排序。
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量。
③volatile关键字无法保证原子性,更准确地说是volatile关键字只能保证单操作的原子性,比如 x=1,但是无法保证复合操作的原子性,比如x++
3.2、synchronized 和 volatile 的区别
1、volatile 关键字是线程同步的轻量级实现,所以 volatile性能肯定比synchronized关键字要好 。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 。
2、volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
3、volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。
4、volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
5、volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
4、ThreadLocal
ThreadLocal叫做线程本地变量,ThreadLocal想让线程间对某个变量不相互干扰,隔离开来;ThreadLocal中填充的变量属于当前线程独有的变量,对其他线程而言是隔离的。
①ThreadLocal是JDK包提供的,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题
②ThreadLocal相当于线程间的数据隔离。Synchronized则是为用于线程间的共享数据加锁。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
③ThreadLocal 适用于变量在线程间隔离而在方法或类间共享的场景。最常见的ThreadLocal使用场景为用来解决数据库连接、Session 管理等
threadlocl是作为当前线程中属性ThreadLocalMap集合中的某一个Entry的key值,不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的。
5.1、 线程池介绍与优势
①线程池介绍
线程池是一种多线程处理形式,可以理解为多个线程的集合。线程是需要时就创建,执行完任务就销毁,而线程池是取用一个创建好的线程,用完就放回去。
<span style="color:#000000"><span style="background-color:#1d1f21"><code class="language-java"> <span style="color:#969896">//线程池(JDK)</span>
ExecutorService executorService <span style="color:#a67f59">=</span> Executors<span style="color:#999999">.</span><span style="color:#81a2be">newFixedThreadPool</span><span style="color:#999999">(</span><span style="color:#b5bd68">16</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#969896">//千人并发访问</span>
<span style="color:#b294bb">for</span><span style="color:#999999">(</span><span style="color:#b294bb">int</span> i<span style="color:#a67f59">=</span><span style="color:#b5bd68">0</span><span style="color:#999999">;</span>i<span style="color:#a67f59"><</span><span style="color:#b5bd68">1000</span><span style="color:#999999">;</span>i<span style="color:#a67f59">++</span><span style="color:#999999">)</span><span style="color:#999999">{</span>
executorService<span style="color:#999999">.</span><span style="color:#81a2be">submit</span><span style="color:#999999">(</span><span style="color:#b294bb">new</span> Runnable<span style="color:#999999">(</span><span style="color:#999999">)</span> <span style="color:#999999">{</span>
<span style="color:#999999">@Override</span>
<span style="color:#b294bb">public</span> <span style="color:#b294bb">void</span> <span style="color:#81a2be">run</span><span style="color:#999999">(</span><span style="color:#999999">)</span> <span style="color:#999999">{</span>
studentService<span style="color:#999999">.</span><span style="color:#81a2be">queryAllStudentCount</span><span style="color:#999999">(</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#999999">}</span>
<span style="color:#999999">}</span><span style="color:#999999">)</span><span style="color:#999999">;</span>
<span style="color:#999999">}</span>
</code></span></span>
②线程池优势
1、降低系统资源消耗,提高系统响应速度:通过重用已存在的线程,降低线程创建和销毁造成的消耗;无需等待新线程的创建便能立即执行;
2、线程并发数的管控,统一的分配,调优和监控。因为线程若是无限制的创建,可能会产生OOM(内存溢出),并且会造成cpu过度切换。
3、提供更强大的功能,延时定时线程池。
5.2、 线程池参数
核心线程数量、最大线程数、等待队列数、每一个线程的执行时间、线程的名称等参数的线程。
参数 | 释义 |
---|---|
corePoolSize | 线程池中的常驻核心线程数。当有请求任务来之后,若线程池已创建的线程数小于corePoolSize,会通过创建一个新线程来执行该任务。当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。 |
maximumPoolSize | 线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。 |
keepAliveTime | 空闲线程的存活时间。当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。 |
unit | keepAIiveTime的单位 |
workQueue | 任务队列。用于传输和保存等待执行任务的阻塞队列 |
threadFactory | 线程工厂。用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号) |
handler | 线程饱和策略。当线程池和队列都满了,再加入线程会执行此策略。 |
策略 | 释义 |
---|---|
CallerRunsPolicy | 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大。 |
AbortPolicy | 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。 |
DiscardPolicy | 直接丢弃,其他啥都没有。 |
DiscardOldestPolicy | 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入。 |
5.3、创建线程池的方式, 线程池有几种
创建线程池的方式 (使用Executors顶层容器静态类实现创建)
<span style="color:#000000"><span style="background-color:#1d1f21"><code class="language-java">Executory<span style="color:#999999">.</span><span style="color:#81a2be">newFixedThreadPool</span><span style="color:#999999">(</span><span style="color:#b294bb">int</span><span style="color:#999999">)</span><span style="color:#999999">;</span><span style="color:#969896">//创建固定容量大小的线程池</span>
</code></span></span>
- 1
线程池有几种
种类 | 释义 |
---|---|
newFixedThreadPool | 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。(优点是适合长期任务,性能好。缺点是不会复用线程,每运行一个Runnable都会通过ThreadFactory创建一个线程) |
newScheduledThreadPool | 创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。 |
newCachedThreadPoo | 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。(优点是适合短期异步任务或者负载很轻的服务。缺点是可以无限的新建线程,容易造成堆外内存溢出。) |
newSingleThreadExecutor | 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。(优点是一个任务一个任务执行,保证顺序性。缺点是前一个任务的延迟或异常都将会影响到之后的任务) |
5.4、线程池的启动策略?
①线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。
②当调用 execute() 方法添加一个任务时,线程池会做如下判断:
1、如果正在运行的线程数量小于核心线程数,那么马上创建线程运行这个任务;
2、如何大于或等于核心线程数,那么将这个任务放入队列。
3、如果队列满了,而且正在运行的线程数量小于最大线程数,那么还是要创建线程运行这个任务;
4、如果队列满了,而且正在运行的线程数量大于或等于 最大线程数,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。
③当一个线程完成任务时,它会从队列中取下一个任务来执行。
④当一个线程超过一定的时间(keepAliveTime)没事做,并且当前运行的线程数大于核心线程数,那么这个线程就被停掉。
6、 AQS
①AQS是什么?
AQS定义了一套多线程访问共享资源的同步器框架。 来实现同步类(Lock、Semaphore、ReentrantLock等)。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。