目录
5.2. StringBuilder、StringBuffer、String的区别
7.2. 为什么重写了equals()需要重写hashCode()
3.1. HashSet、LinkedHashSet、TreeSet各自实现和特点
4.1. HashMap(必考,必须了解put方法具体过程、扩容、1.8优化)
4.2. Hashtable(笔试喜欢考和hashMap的区别)
4.3. LinkedHashMap:了解特点和底层数据结构
5. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用run() 方法?
6.2. synchronized和ReentrantLock 的区别
2. JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代
5.三大日志(undo log、redo log、bin log)
1.JAVA基础
1.静态方法为什么不能调用非静态成员
这个要从java的内存机制去分析,首先当你 New 一个对象的时候,并不是先在堆中为对象开辟内存空间,而是先将类中的静态方法(带有static修饰的静态函数)的代码加载到方法区,然后再在堆内存中创建对象。所以说静态方法会随着类的加载而被加载。当你new一个对象时,该对象存在于堆内存中,this关键字一般指该对象,但是如果没有 new对象,而是通过类名调用该类的静态方法也可以。
1.类的静态成员(静态和方法)属于类本身,在类加载的时候就会分配内存,可以 通过类名直接去访问,2.非静态成员(变量和方法)属于类的对象,所以只有在类的对象禅师(创建实例)的时候才会分配内存,然后通过类的对象去访问。
静态方法在对象实例化之前,在方法区占用内存,非静态方法需要类对象实例化之后才会被分配内存。静态方法优先于类对象实例化,并且只加载一次,可以直接用类名调用,优先于对象存在,被所有对象共享。
2. final、static关键字
final关键字
- final关键字可以用于成员变量、本地变量、方法及类。
- 修饰方法:表示方法不能被重写。
- 修饰类:表示该类不能被继承。
- 修饰变量:表示常量,必须在声明时赋值,赋值后不能被修改。
- 接口中声明的所有变量本身是final的。
- 按照Java规范,final变量就是常量,通常变量名要大写。
static关键字
- 用来修饰成员变量,将其变为类的成员,从而实现所有对象对于该成员的共享。
- 用来修饰成员方法,将其变为类方法,可以直接使用“类名.方法名”的方式调用,常用于工具类。
- 静态块用法:将多个类成员放在一起初始化,使得程序更加规整,优点是可以优化程序性能,因为它只在类初始被加载时执行一次,其中理解对象的初始化过程非常关键。
- 静态导包用法:将类的静态方法导入到当前类中,从而直接使用“方法名”即可调用类方法,更加方便。
- static final用来修饰成员变量,即全局常量。
3. 重写重载的区别?
- 定义不同:重载是定义相同的方法名、参数不同,重写是子类重写父类的方法
- 范围不同:重载是在一个类中,重写是子类与父类之间的
- 多态不同:重载是编译时的多态性,重写是运行时的多态性
- 参数不同:重载的参数个数、参数类型、参数的顺序可以不同,重写父类子方法参数必须相同
- 修饰不同:重载对修饰范围没有要求,重写要求重写方法的修饰范围大于被重写方法的修饰范围
- 多态是一个类需要表现出多种形态,子类重写父类的方法,使子类具有不同的方法实现
4. 解释一下面向对象(三大特性)?
面向对象的三大特点是:封装、继承、多态。
1.封装 (encapsulation),隐藏对象的属性和实现细节,仅对外,公开接口,控制在程序中属性的读取和修改的访问级别。将对象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。
2. 继承(inheritance)是面向对象软件技术当中的一个概念。如果一个类别A“继承”另一个类别B,就把这个A称为“B的子类”,而把B称为“A的父类”。继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码。在令子类继承父类的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。另外,为子类追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类的行为在编译期就已经决定,无法在执行期扩充。
- 子类具有父类非private的属性和方法
- 子类可以扩展父类没有的属性和方法
- 子类可以重写父类的方法
继承的缺点:子类和父类具有强耦合性。
3.多态的定义:同一种操作对于不同的对象有着不同的定义,不同的解释,不同的执行结果。即同一个对象调用一个相同的方法得到不同的结果。
第一个特点:重载。
定义:在同一个类中方法名相同,参数列表不一样我们称之为重载。
特点:
- 方法名必须相同
- 参数类型不同
- 参数个数不同
- 参数的顺序不能一样
- 返回值可以不相同
第二个特点:重写
定义 : 子类为了满足自己的需求而进行的相同方法的不同实现方式,进行方法的重写。
特点 :
- 重写的方法必须是虚方法,要用override关键字。
- 重写的方法名必须相同。
- 重写的方法参数必须相同
- 重写的方法返回值必须相同
5. String类
5.1. String为什么不可变
String不可变是因为字符数组被final修饰,final修饰引用类型,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,也就是一直引用同一个对象,但这个对象完全可以发生改变。
String不能被改变最主要的是字符数组被private修饰。而且String也没有提供可以修改字符数组的api,外界不可访问value[],自然String就不能被修改。
实现hashcode的不可变性
String的value数组的指向地址不可改变,而StringBuffer是提供了方法可以修改value指向的数组的元素
5.2. StringBuilder、StringBuffer、String的区别
1.基本区别
String的对象不可变,StringBuffer和StringBuilder的对象是可变的
2.性能区别
三者中StringBuilder执行速度最佳,StringBuffer次之,String的执行速度最慢(String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,String对象一旦创建后该对象是不可更改的,后两者的对象是变量是可以更改的)
3.安全区别
String、StringBuffer是线程安全的,StringBuilder是线程不安全的(所以如果程序是单线程的使用StringBuilder效率高,如果是多线程使用StringBuffer或者String)
其次总结下这三者的相同:
- 三者在java中都是用来处理字符串的
- 三个类都被final修饰,因此都是不可继承的
- StringBuilder与StringBuffer有公共父类AbstractStringBuilder(抽象类)
6. 静态方法为什么不能调成员方法、成员变量?
静态方法在对象实例化之前就被分配内存,而成员方法,成员变量要在对象实例化之后才会分配内存。静态方法为对象所共有。
7. equals和hashcode
7.1. ==和equals的区别
一、对象类型不同
1、equals():是超类Object中的方法。
2、==:是操作符。
二、比较的对象不同
1、equals():equals是Object中的方法,在Object中equals方法实际"ruturn (this==obj)",用到的还是"==",说明如果对象不重写equals方法,实际该对象的equals和"=="作用是一样的,都是比较的地址值(因为"=="比较的就是地址值),但是大部分类都会重写父类的equals方法,用来检测两个对象是否相等,即两个对象的内容是否相等,例如String就重写了equals方法,用来比较两个字符串内容是否相同。
2、==:用于比较引用和比较基本数据类型时具有不同的功能,比较引用数据类型时,如果该对象没有重写equals方法,则比较的是地址值,如果重写了,就按重写的规则来比较两个对象;基本数据类型只能用"=="比较两个值是否相同,不能用equals(因为基本数据类型不是类,不存在方法)。
三、运行速度不同
1、equals():没有==运行速度快。
2、==:运行速度比equals()快,因为==只是比较引用。
7.2. 为什么重写了equals()需要重写hashCode()
如果一个只重写了equals(比较所有属性是否相等)的类 new 出了两个属性相同的对象。这时可以得到的信息是这个属性相同的对象地址肯定不同,但是equals是true,hashCode返回的是不相等的(一般不会出现hash碰撞)。
也就是说这个类对象违背了Java对于两个对象相等的约定。违背约定的原因是 可靠的equals判断两个对象是相等的,但是他们两个的散列码确是不相等的。
equals重写之后,对象比对值相同,但是一般不发生hash碰撞,所以hashcode值不同就违背了equals的原则。
8. 反射
8.1. 有哪些类
8. 2. 做什么用的
一般来说反射是用来做框架的,而且是框架设计的灵魂,或者说可以做一些抽象度比较高的底层代码
9. 动态代理
(1)JDK动态代理:JDK使用反射机制创建代理类对象,JDK具有创建对象的能力
(2)CGLIB动态代理:利用面向对象三大特性继承的关系,完成代理操作
10. 异常
throws和thow关键字
1)throws出现在方法的声明中,表示该方法可能会抛出的异常,然后交给上层调用它的方法程序处理,允许throws后面跟着多个异常类型;
2)一般会用于程序出现某种逻辑时程序员主动抛出某种特定类型的异常。throw只会出现在方法体中,当方法在执行过程中遇到异常情况时,将异常信息封装为异常对象,然后throw出去。throw关键字的一个非常重要的作用就是 异常类型的转换(会在后面阐述道)。
throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行
throw则一定抛出了某种异常对象。两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由方法去处理异常,真正的处理异常由此方法的上层调用处理。
11. BIO、NIO、AIO
1.BIO (同步阻塞I/O模式)
数据的读取写入必须阻塞在一个线程内等待其完成。
这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。
2.NIO(同步非阻塞)
同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞I/O模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。
3.AIO (异步非阻塞I/O模型)
public void execute(JobExecutionContext context) throws JobExecutionException;异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。
12. 浅拷贝和深拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。序列化属于深拷贝
13. 值传递和引用传递
值传递:是指在调用函数时,将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,就不会影响到实际参数
引用传递:是指在调用函数时,将实际参数的地址传递到函数中,那么在函数中对参数进行修改,将会影响到实际参数
引用数据类型分为两个部分,引用变量和对象,这两个部分放在不同的地方,引用变量在栈中,而对象是放在堆内存中的,引用变量指向对象。
引用类型中的String的值是放在常量池中,我们改变副本的值不会影响到原来的值。
14. 包装类
boolean初始值为false,Boolean为null
14.1. 基本类型和包装类型有什么区别
- 包装类型可以为 null,而基本类型不可以。它使得包装类型可以应用于 POJO 中,而基本类型则不行。那为什么 POJO 的属性必须要用包装类型呢? 数据库的查询结果可能是 null,如果使用基本类型的话,因为要自动拆箱(将包装类型转为基本类型,比如说把 Integer 对象转换成 int 值),就会抛出 NullPointerException 的异常。
- 包装类型可用于泛型,而基本类型不可以。泛型不能使用基本类型,因为使用基本类型时会编译出错。
- 基本类型比包装类型更高效。基本类型在栈中直接存储的具体数值,而包装类型则存储的是堆中的引用。 很显然,相比较于基本类型而言,包装类型需要占用更多的内存空间。
15. 序列化(了解)
序列化:指堆内存中的java对象数据,通过某种方式把对存储到磁盘文件中,或者传递给其他网络节点(网络传输)。这个过程称为序列化,通常是指将数据结构或对象转化成二进制的过程。即将对象转化为二进制,用于保存,或者网络传输。
反序列化:把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。也就是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。与序列化相反,将二进制转化成对象。
序列化作用
① 想把内存中的对象保存到一个文件中或者数据库中时候;
② 想用套接字在网络上传送对象的时候;
③ 想通过RMI传输对象的时候
16.泛型相关
extends与super
extends上限通配符,用来限制类型的上限,只能传入本类和子类,add方法受阻,可以从一个数据类型里获取数据;
super下限通配符,用来限制类型的下限,只能传入本类和父类,get方法受阻,可以把对象写入一个数据结构里
17.内部类
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
2.Java集合
1. 集合接口的继承关系
2. List
2.1. ArrayList、LinkedList的区别
3. set
3.1. HashSet、LinkedHashSet、TreeSet各自实现和特点
HashSet是基于哈希表(Hash表)实现的,它不能保证线程安全,其中允许存在null元素,但null元素只能有1个。
当程序员向HashSet中插入一个对象时, HashSet会调用该对象的hashCode()方法(如果该对象没定义,会调用Object)来得到该对象的hashCode值;然后会根据hashCode值来决定该对象在HashSet中的存放位置,如果遇到两个对象的hashCode值一致的情况则说明它们相等, HashSet同样不会允许插入重复的元素。
上述描述包含了一层隐藏的含义, HashSet不能保证插入次序和遍历次序一致。
相比之下,LinkedHashSet同样是基于Hash表,它也是根据元素的hashCode值来决定元素的存储位置的,但是它同时也采用了链表的方式来保证插入次序和遍历次序一致。
而TreeSet是SortedSet接口的唯一实现类,它是用二叉树存储数据的方式来保证存储的元素处于有序状态。
4. Map
4.1. HashMap(必考,必须了解put方法具体过程、扩容、1.8优化)
4.2. Hashtable(笔试喜欢考和hashMap的区别)
HashMap线程不安全,HashTable线程安全,并且并发度低,只有一把锁,同一个时刻只能有一个线程操作
4.3. LinkedHashMap:了解特点和底层数据结构
HashMap 是无序的kv键值对容器,TreeMap 则是根据key进行排序的kv键值对容器,而LinkedHashMap同样也是一个有序的kv键值对容器,区别是其排序方式依据的是进入Map的先后顺序
LinkedHashMap 存储结构和 HashMap 相同,依然是数组+链表+红黑树
LinkedHashMap 额外持有一个双向链表,维护插入节点的顺序
实际的元素存储与HashMap一致,依然是数组+链表+红黑树的形式
区别在于:
除了维护数组+链表的结构之外,还根据插入Map先后顺序维护了一个双向链表的头尾head,tail
Node基本结构,相比较HashMap而言,还增加了 before,after 两个分别指向双向链表中前后节点的属性
4. 4. TreeMap:了解特点和底层数据结构
TreeMap 实现的是
NavigableMap
, 而不是直接实现Map,
继承体系:Map -> SortMap -> NavigbleMap,
基本上这两个接口就是提供了一些基于排序的获取kv对的方式
- 树结构为二叉排序树(且不能出现相等的情况)
- 重排的方法可以保证该树为红黑树
特点:
- TreeMap 底层结构为红黑树
- 红黑树的Node排序是根据Key进行比较
- 每次新增删除节点,都可能导致红黑树的重排
- 红黑树中不支持两个or已上的Node节点对应
红黑值
相等
5. CopyOnWriteArrayList
Copy-On-Write简称COW。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite并发容器用于读多写少的并发场景
3.多线程
1. 线程和进程的区别(常考)
1、进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)
2、进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
3、线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
4、但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
1、因为进程拥有独立的堆栈空间和数据段,所以每当启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这对于多进程来说十分“奢侈”,系统开销比较大,而线程不一样,线程拥有独立的堆栈空间,但是共享数据段,它们彼此之间使用相同的地址空间,共享大部分数据,比进程更节俭,开销比较小,切换速度也比进程快,效率高,但是正由于进程之间独立的特点,使得进程安全性比较高,也因为进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。一个线程死掉就等于整个进程死掉。
2、体现在通信机制上面,正因为进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便。
3、体现在CPU系统上面,线程使得CPU系统更加有效,因为操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
4、体现在程序结构上,举一个简明易懂的列子:当我们使用进程的时候,我们不自主的使用if else嵌套来判断pid,使得程序结构繁琐,但是当我们使用线程的时候,基本上可以甩掉它,当然程序内部执行功能单元需要使用的时候还是要使用,所以线程对程序结构的改善有很大帮助。
2. 线程的状态
2.1线程的创建方式
1.继承Thread类
2.继承Runnable接口(无返回值)
3.继承Callable接口(有返回值)和Future
4. 使用线程池创建线程
Java5 提供了 Future 接口来代表 Callable 接口里 call() 方法的返回值,并且为 Future 接口提供了一个实现类 FutureTask,这个实现类既实现了 Future 接口,还实现了 Runnable 接口,因此可以作为Thread 类的 target。在 Future 接口里定义了几个公共方法来控制它关联的 Callable 任务
Executors 提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService 接口。
3. 死锁(重点)
3.1. 死锁的条件
java同步机制解决了线程安全问题,但是同时也引发了死锁现象。
死锁现象如何解决:没法解决,只能尽量避免死锁现象。
死锁出现的根本原因:
- 存在两个或以上的线程存在。
- 多线程必须共享两个或者以上的资源
四个必要条件:(互斥,保持条件与请求,不可剥夺,循环等待)
1.互斥
资源必须处于非共享模式,即一次只有一个进程可以使用。如果另一进程申请该资源,那么必须等待直到该资源被释放为止。
2.占有并等待(保持条件与请求)
一个进程至少应该占有一个资源,并等待另一资源,而该资源被其他进程所占有。
3.非抢占(不可剥夺)
资源不能被抢占。只能在持有资源的进程完成任务后,该资源才会被释放。
4.循环等待
有一组等待进程 {P0, P1,…, Pn}, P0 等待的资源被 P1 占有,P1 等待的资源被 P2 占有,…,Pn-1 等待的资源被 Pn 占有,Pn 等待的资源被 P0 占有。
3.2. 怎么打破死锁条件,避免死锁
1〉打破互斥条件。即允许进程同时访问某些资源。但是,有的资源是不允许被同时访问的,像打印机等等,这是由资源本身的属性所决定的。所以,这种办法并无实用价值。
〈2〉打破不可抢占条件。即允许进程强行从占有者那里夺取某些资源。就是说,当一个进程已占有了某些资源,它又申请新的资源,但不能立即被满足时,它必须释放所占有的全部资源,以后再重新申请。它所释放的资源可以分配给其它进程。这就相当于该进程占有的资源被隐蔽地强占了。这种预防死锁的方法实现起来困难,会降低系统性能。
〈3〉打破占有且申请条件。可以实行资源预先分配策略。即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部资源需求时,才一次性地将所申请的资源全部分配给该进程。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又申请资源的现象,因此不会发生死锁。
(4)打破循环等待条件,实行资源有序分配策略。采用这种策略,即把资源事先分类编号,按号分配,使进程在申请,占用资源时不会形成环路。所有进程对资源的请求必须严格按资源序号递增的顺序提出。进程占用了小号资源,才能申请大号资源,就不会产生环路,从而预防了死锁。这种策略与前面的策略相比,资源的利用率和系统吞吐量都有很大提高。
4. sleep和wait的区别(重点)
用interrupt打断唤醒
5. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不
能直接调用run() 方法?
因为类Thread中的start方法中,调用了Thread中的run方法。顺便说下,类A继承了Tread类,在A中写run方法,就会覆盖掉Thread中的run方法,所以此时调用start方法后,实现的是自己的run方法体里面的代码。
如果我们直接调用子线程的run()方法,其方法还是运行在主线程中,代码在程序中是顺序执行的,所以不会有解决耗时操作的问题。所以不能直接调用线程的run()方法,只有子线程开始了,才会有异步的效果。当thread.start()方法执行了以后,子线程才会执行run()方法,这样的效果和在主线程中直接调用run()方法的效果是截然不同的。
run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
start()方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;
6. synchronized关键字
6.1. synchronized的锁升级(了解)
synchronized锁有四种状态,无锁,偏向锁,轻量级锁,重量级锁,这几个状态会随着竞争状态逐渐升级,锁可以升级但不能降级,但是偏向锁状态可以被重置为无锁状态
6.2. synchronized和ReentrantLock 的区别
7. volatile关键字
8. 并发编程三个重要特性
原子性----悲观锁/乐观锁
有序性----volatile
可见性----volatile
9. ThreadLocal作用和原理(了解)
因为key是弱引用,而value是强引用,所以有可能会存在内存泄漏
10. 线程池(重点)
10.1. 线程池的参数
10. 2. 线程池执行任务的流程
1. 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
2. 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
3. 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
4. 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
5. 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
10. 3. 默认的线程池
JDK中提供的ThreadPoolExecutor类
11. 原子类(了解)
11.1. 知道CAS操作
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”(CAS保存了V位置的原来A值,当再次去访问时,若还是旧A,则替换成B,否则,不替换)
通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
类似于CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法可以对该操作重新计算。
CAS非阻塞
CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作
12. 理解乐观锁悲观锁
13. AQS
AQS:也就是队列同步器,这是实现 ReentrantLock 的基础。
AQS 有一个 state 标记位,值为1 时表示有线程占用,其他线程需要进入到同步队列等待,同步队列是一个双向链表
当获得锁的线程需要等待某个条件时,会进入 condition 的等待队列,等待队列可以有多个。
当 condition 条件满足时,线程会从等待队列重新进入同步队列进行获取锁的竞争。
ReentrantLock 就是基于 AQS 实现的,ReentrantLock 内部有公平锁和非公平锁两种实现,差别就在于新来的线程是否比已经在同步队列中的等待线程更早获得锁。
和 ReentrantLock 实现方式类似,Semaphore 也是基于 AQS 的,差别在于 ReentrantLock 是独占锁,Semaphore 是共享锁。
14. 并发工具类
倒数计数器CountDownLatch
栅栏Barrier
3.JVM
1. java内存区域,各自的作用(重点)
2. JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代
首先对象被加载到eden区,eden区和survivor(from和to)区为新生代,eden区内存不足的时候,将eden区和from区的存活对象复制到to区,然后交换from和to区
如此几轮下来,还存活的对象晋升为老年代。(最多15轮),当eden内存不足,或者这个对象过大的话会提前晋升
3. 强引用,软引用,弱引用,虚引用(了解)
4. 如何判断对象是否死亡?(两种)
可达性分析和三色标记法
可能会存在并发漏标问题
5. 垃圾收集有哪些算法,各自的特点?
标记清除法:找到GC root对象(那些一定不会被回收的对象),沿着引用链栈,直接或者间接引用到的对象加上标记。释放未加标记对象的内存。
可能会产生内存碎片
标记整理法:多了一步整理操作,避免了内存碎片,当然性能上也就慢了
标记复制法:将内存分为from和to区,to总是空闲,from存储新创建的对象。存活对象从from复制到to区,复制过程自然完成了碎片整理。然后交换from和to区
6. 垃圾收集器,各自的特点
7. 类的加载过程(重点)
7.1. 类加载器
7. 2. 双亲委派机制
4.Redis
1. redis的数据类型和底层实现(重点)
7.1. 什么是跳表
2. redis过期删除机制
1. 定时删除
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。
2. 惰性删除放任过期键不管,每次从键空间中获取键时,检查该键是否过期,如果过期,就删除该键,如果没有过期,就返回该键。
3. 定期删除
每隔一段时间,程序对数据库进行一次检查,删除里面的过期键,至于要删除哪些数据库的哪些过期键,则由算法决定。
3. redis持久化(重点)
RDB(周期性)和AOF(实时性)
4. redis事务
redis的事务中,一次执行多条命令,本质是一组命令的集合,一个事务中所有的命令将被序列化,即按顺序执行而不会被其他命令插入
在redis中,事务的作用就是在一个队列中一次性、顺序性、排他性的执行一系列的命令。
5. 缓存雪崩和缓存穿透问题
缓存雪崩
缓存击穿
缓存穿透
6. redis实现分布式锁(了解)
实现分布式锁
- 基于MySQL的分布式锁
- 基于Redis的分布式锁
- 基于ZooKeeper的分布式锁
分布式锁的特点
- 互斥性。(在分布式集群中,同一个方法在同一时间只能被一台机器上的一个线程获得)。
- 可重入性。(递归调用不应该被阻塞、避免死锁)。
- 锁的超时。(避免死锁、死循环等意外情况)。
- 加锁和解锁必须为同一客户端。(除非锁到期自动收回,否则加锁和解锁需为同一客户端)。
- 获取锁的时候,使用setnx加锁,key为锁名,value值为一个随机生成的UUID。
- 获取锁成功后,使用expire命令为锁添加一个超时时间,若超过这个时间则放弃获取锁。
- 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
优点:
- 依赖于Redis高并发的特性,所以性能极佳。
- 过期时间不好控制,需要考虑续锁问题。
- 实现相对复杂,需要考虑的因素太多。
- 非阻塞,操作失败后,需要轮询,占用cpu资源。
- 主节点挂掉,未成功同步的情况下,可能导致多个节点获取到锁。
7. redis集群(了解)
- 主从复制模式
- Sentinel(哨兵)模式
- Cluster 模式
5.MySql
1. 存储引擎
1.1. MyISAM和InnoDB区别?
1. InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
2. InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败;
3. InnoDB是聚簇索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。
MyISAM是非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
也就是说:InnoDB的B+树主键索引的叶子节点就是数据文件,辅助索引的叶子节点是主键的值;而MyISAM的B+树主键索引和辅助索引的叶子节点都是数据文件的地址指针。
4. InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快(注意不能加有任何WHERE条件);5. Innodb不支持全文索引,而MyISAM支持全文索引,在涉及全文索引领域的查询效率上 MyISAM速度更快高;PS:5.7以后的InnoDB支持全文索引了
6. MyISAM表格可以被压缩后进行查询操作
7. InnoDB支持表、行(默认)级锁,而MyISAM支持表级锁
8、InnoDB表必须有唯一索引(如主键)(用户没有指定的话会自己找/生产一个隐藏列Row_id来充当默认主键),而Myisam可以没有
9、Innodb存储文件有frm、ibd,而Myisam是frm、MYD、MYI
Innodb:frm是表定义文件,ibd是数据文件
Myisam:frm是表定义文件,myd是数据文件,myi是索引文件
2. 索引(重点)
2.1. 索引的优缺点
优点
- 通过创建唯一索引可以保证数据库表中每一行数据的唯一性。
- 可以给所有的 MySQL 列类型设置索引。
- 可以大大加快数据的查询速度,这是使用索引最主要的原因。
- 在实现数据的参考完整性方面可以加速表与表之间的连接。
- 在使用分组和排序子句进行数据查询时也可以显著减少查询中分组和排序的时间
缺点
- 增加索引也有许多不利的方面,主要如下:
- 创建和维护索引组要耗费时间,并且随着数据量的增加所耗费的时间也会增加。
- 索引需要占磁盘空间,除了数据表占数据空间以外,每一个索引还要占一定的物理空间。如果有大量的索引,索引文件可能比数据文件更快达到最大文件尺寸。
- 当对表中的数据进行增加、删除和修改的时候,索引也要动态维护,这样就降低了数据的维护速度。
2.2. 索引的分类
按数据结构分类可分为:B+tree索引、Hash索引、Full-text索引。
按物理存储分类可分为:聚簇索引、二级索引(辅助索引)。
按字段特性分类可分为:主键索引、普通索引、前缀索引。
按字段个数分类可分为:单列索引、联合索引(复合索引、组合索引)。
2.3.聚簇索引和非聚簇索引
2.4 B+Tree

2.5最左前缀原则
索引可以简单如一个列(a),也可以复杂如多个列(a, b, c, d),即联合索引。
如果是联合索引,那么key也由多个列组成,同时,索引只能用于查找key是否存在(相等),遇到范围查询(>、<、between、like左匹配)等就不能进一步匹配了,后续退化为线性查找。因此,列的排列顺序决定了可命中索引的列数。
例子:
如有索引(a, b, c, d),查询条件a = 1 and b = 2 and c > 3 and d = 4,则会在每个节点依次命中a、b、c,无法命中d。(很简单:索引命中只能是相等的情况,不能是范围匹配)
3. 事务:(重点)
3.1. 事务的四大特性
原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Duration)
3.2 事务的隔离级别
读未提交、读已提交、可重复读以及可串行化
mysql默认可重复读,因为主从复制存在大量不一致
4. MVCC(了解)
MVCC每条数据都有多版本快照
InnoDB里面每个事务有一个唯一的事务ID,叫作transaction id。它是在事务开始的时候向InnoDB的事务系统申请的,是按申请顺序严格递增的。
InnoDB利用了“所有数据都有多个版本”的这个特性,实现了“秒级创建快照”的能力。
更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”除了update语句外,select语句如果加锁,也是当前读。
可重复读的核心就是一致性读(consistent read);而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。
- 在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;
- 在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。
5.三大日志(undo log、redo log、bin log)