郑州 Java 面试题

常见题库

https://github.com/cosen1024/Java-Interview
https://github.com/whx123/JavaHome
https://github.com/lvminghui/Java-Notes
https://github.com/cosen1024/Java-Interview

== 和equals 区别

== 基本类型比较的是值,对象类型比较的是地址,equals 默认情况下也是比较地址,,类可以重写 equals 方法,重写后比较内容。
https://cloud.tencent.com/developer/article/2016113
integer 类型重写了 equals 方法, == 比较的是地址,可能会返回false,所以两个 integer 类型比较应该使用 equals。
https://blog.csdn.net/gb4215287/article/details/124351393

String 对 equals 重写,先比较地址,然后比较内容。
new String("a") 和 c = a + "111" 都会创建新对象,而不会使用常量池。
https://blog.csdn.net/weixin_42476601/article/details/88659715

Int 和 Integer 的区别

int是java的基础类型数据,integer是对象类型。

通过valueOf将int转换为Integer类型,通过intValue将Integer转换为int类型。

int放在栈中,Integer是对象所以放在堆中;

int 变量默认值是0,integer变量是NULL;

new的执行过程

例如:Person jack = new Person();

  1. 首先会将对象类加载到内存;
  2. 执行对象的静态代码块;
  3. 在堆中给对象开辟空间;
  4. 给对象的静态属性进行初始化;
  5. 给对象的静态属性进行显示初始化(用户值);
  6. 执行普通代码块;
  7. 执行构造函数;
  8. 将内存地址赋值给栈内存中的符号变量;
    https://blog.csdn.net/qq_38270106/article/details/89876626

对象的加载过程

https://blog.csdn.net/shengmingqijiquan/article/details/77932864

详细版:

https://www.cnblogs.com/ironHead-cjj/p/11443057.html

https://blog.csdn.net/xuguoli_beyondboy/article/details/42248179

JDK的类加载器是懒加载模式。一般情况,在第一次创建对象时才会将类装载到内存。

第一次创建对象:

初始化类

父类–静态变量
父类–静态块
子类–静态变量
子类–静态块

创建对象

父类–普通变量
父类–普通块
父类–构造器
子类–变量
子类–普通块
子类–构造器

常用的加密算法

https://www.cnblogs.com/zhixie/p/13407823.html

ClassNotFoundException和NoClassDefFoundError的区别:

ClassNotFoundException的意思是找不到类(就是单纯的找不到),针对的是类似反射方式动态加载的类,这是一个检查异常。

NoClassDefFoundError是运行时错误,针对静态加载的类,编译时检查的类,编译的时候好好的,运行时突然没了,可能打包的锅。

JAVA.lang.Class作用

JAVA文件编译后是字节码文件,当被加载到JVM内存空间中,变成运行类,对应Class类的一个实例,保存着该运行类的类型信息。

详解:

https://www.cnblogs.com/superxuezhazha/p/12353312.html

类加载器

JRE提供的类加载器有三种:

bootstrap classloader 引导类加载器,用于核心库,原生代码实现。

extensions classloder 扩展类加载器,用于扩展库

system classloder 系统类加载器,就是开发者编写的类。

基本上所有的类加载器都是java.lang.ClassLoader类的一个实例。

他们之间的关系是:引导类加载器<-扩展类加载器<-系统类加载器,“<-”是继承。可以通过 getParent()方法获取父类

详解:

https://developer.ibm.com/zh/articles/j-lo-classloader/

StringBuilder线程为什么不安全?

StringBuffer类的所有公开方法被synchronized关键字修饰了,所以线程安全,适合多线程运行。

StringBuilder适合单线程运行。

他们底层是依靠char数组实现字符串的,String类也是。
https://blog.csdn.net/weixin_45393094/article/details/104526603

String为什么不可变?

从它所带来的优点讲:

1、安全性

String的不可变特性,保证了数据的确定性,也就是说参数的传递是可靠的。

2、效率

堆中的字符串常量池保证了,字符串一样的变量(可以通过hash码比较),引用同一个对象,减少对象的创建,当然这也是不可变保证的前提。

<https://blog.csdn.net/weixin_44946052/article/details/89409188

异常

https://blog.csdn.net/hephaestushao/article/details/88085511

Java的异常老祖宗是Throwable,它有两个分支:

  • Error——错误,系统层的问题。
  • Exception——异常,应用层的问题。

Error

是不可控的,程序无法处理,大多数与应用程序代码没关系。

比如虚拟机错误、内存溢出、线程死锁。

Exception

程序本身的问题,也是自己可以处理的问题。

Exception分为:

  1. uncheckException——运行时异常,代码写的有问题
  2. CheckException——编译异常,外部依赖文件/库有问题

提示:他们俩都继承Exception。

运行时异常

又称为非受检异常,也就是在java文件的编译时无法检查该问题,但在运行时会报异常。

比如你接收了一个Null值,这是你控制不了的,这种运行时出现的问题由“虚拟大哥“机处理,指的是运行时你没法处理的异常。

编译异常

又称为受检异常,在编译时JDK会检查出来,这种异常需要我们捕捉。

比如IO异常、SQL异常,指运行时如果出现错误开发者能处理的异常。

https://blog.csdn.net/lgxlovett/article/details/51329897?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

他们的关系:

参考:

https://blog.csdn.net/qq_29229567/article/details/80773970

接口和抽象类的区别

接口的作用是定义所需要的功能,维护时容易更换实现类。

抽象类的作用是规范子类的基本功能。

hashCode为什么存在

https://zhuanlan.zhihu.com/p/43001449

JAVA中的变量

1、成员变量(实例变量,属性)

2、本地变量(局部变量)

3、类变量(静态属性)

一、成员变量(实例变量,属性)

1.1-成员变量:(在类中定义,  访问修饰符  修饰符  type name = value)

1.2-什么是成员变量?

成员变量就是类中的属性。当new对象的时候,每个对象都有一份属性。一个对象中的属性就是成员变量。

1.3-作用范围?

在类内部,任何地方都可以访问成员变量。

1.4- 生命周期?(在内存中存在的时间)

出生: new对象的时候,开辟内存空间。

死亡: 堆内存地址没有引用,变成垃圾,被垃圾回收器回收后。

二、局部变量(本地变量)

2.1-局部变量:(修饰符  type name = value)

2.2- 什么是局部变量?

方法的形式参数以及在方法中定义的变量。

2.3-作用范围?

形参:在方法体中任何位置都可以访问。

方法中定义变量:从定义处开始,直到所在代码块结束。

2.4  生命周期?(在内存中存在的时间)

出生:运行到创建变量的语句时。

死亡:超过了其作用范围。总结:

三、类变量(静态属性)

3.1-类变量:(访问修饰符 static type name = value)

3.2-什么是类变量?

被static修饰的属性。

3.3-作用范围?

在类变量定义之后。

3.4- 生命周期?(在内存中存在的时间)

出生:类加载时,类变量就分配内存空间。

死亡:JVM退出

Integer的缓冲池

直接赋值的方式,例如 Integer a = 100,其中a的是是一个常量。

因为JDK1.5之后,Integer有缓冲池(IntegerCache)的概念,当直接赋值时,若值的二进制表示范围<=1个字节,那么创建的对象放在常量池当中,常量池若存在该值,直接从常量池获取引用,否则直接创建Integer对象。

该缓冲池减少了内存的占用。JDK好像一直想解决内存占用问题。

参考:

https://blog.csdn.net/z249486888/article/details/83831936

Collection

List

  • ArrayList
    ArrayList内部通过数组实现。数组是固定大小,ArrayList的数组会预先扩容,新的容量按如下公式获得:新容量+=旧容量 >> 1,大概就是旧容量的1.5倍-1。ArrayList要插入(中间插入)或者删除元素都需要对数组元素排序。因此,它不适合删除和插入元素,但因为数组的性质,列表所有的元素是顺序且连续存储,所以索引和增加(尾部插入)操作速度快。

  • Vector
    Vector和ArrayList实现原理一样,但是Vector是线程同步的,即多线程访问是安全的。相对的,它查询速度比较慢。

  • LinkedList
    内部通过双向链表节后存储数据。链表是一种首尾相连的结构,就像自行车链条一样,可以替换任意一段。同理放在计算机上存储数据也一样,对于删除和插入操作非常方便,速度快。但是每个元素之间几乎不连续,每个元素携带上一个元素和下一个元素的内存地址。因此,要访问某个元素,只能逐个查找,从当前元素身上获取下个元素的内存地址。LinkedList插入和删除操作速度快,索引慢。

    - add()操作 delete()操作 insert操作 index取值操作 iterator取值操作
    ArrayList 极优 极优
    LinkedList 极优

参考:

https://blog.csdn.net/wuchuanpingstone/article/details/6678653

https://blog.csdn.net/Nicolas12/article/details/81368080

Set

  • HashSet:基于哈希表实现,存入数据是按照哈希值,所以并不是按照存入的顺序排序,为保证存入的唯一性,存入元素哈希值相同时,会使用 equals 方法比较,如果比较出不同就放入同一个哈希桶里。
  • TreeSet:基于红黑树实现,支持有序性操作,每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。
  • LinkHashSet( HashSet+LinkedHashMap ):继承于 HashSet、又是基于 LinkedHashMap 来实现的, 具有 HashSet 的查找效率。

Set接口

https://www.cnblogs.com/21-forever/p/10945039.html

HashSet 的value是final 修饰的object 对象,只起一个占位作用。

Map

TreeMap

利用红黑树实现元素的自动排序。

默认情况下通过key值的自然顺序进行排序,并且可序列化。

红黑树:从根节点到任一节点中的黑色节点数量相同。

LinkedHashMap

其中的Entry继承了HashMap中的Node(只有next节点)内部类,

而LinkedHashMap又给他加上了before和after节点,实现双向节点以及迭代器。

https://www.cnblogs.com/LiaHon/p/11180869.html

HashMap

扩容

HashMap 默认会创建一个长度 16 的链表数组,代表16个hash槽,插入数据时,对key 进行hash计算,决定放到哪个hash槽中,每个hash槽又是一个链表结构,当hash槽长度大于8时,转为红黑树结构,小于8使用链表结构
https://zhuanlan.zhihu.com/p/69284871

在JDK1.6下,当节点数量大于等于阈值时触发扩容;

在JDK1.7下,当节点熟练大于等于阈值并且当前要插入的bucket(桶)不能为空;

在JDK1.8下,当前节点大于阈值。当链表大于8时,链表转换成红黑树(红黑树的查找效率为o(logN),要优于链表的o(N))。

https://blog.csdn.net/t894690230/article/details/51329017

链表转红黑树

https://blog.csdn.net/wo1901446409/article/details/97388971

当链表长度大于等于8且桶长度大于64,将链表转换为红黑树结构。

数组元素

在1.7中用Entry对象存储数据(只有next和value节点)。

在1.8中用Node(和Entry一样实现的Map.Entry接口)代替了。

https://zhuanlan.zhihu.com/p/56059366

https://zhuanlan.zhihu.com/p/135296335

HashMap和HashTable区别?

HashTable中的方法加入了synchronize关键字;

HashMap为什么是不安全的?

在jdk1.7中,在多线程环境下,扩容时采用头插法造成环形链或数据丢失

在jdk1.8中,在多线程环境下,会发生数据覆盖的情况。

参考:

https://blog.csdn.net/qq9808/article/details/80850498

rehash

rehash = 扩容 + 重新散列。注意,是以2的幂次方扩容。

参考:https://www.cnblogs.com/qcblog/p/8449624.html

HashMap的扩容机制

jdk1.7的时候,当最大链表长度

jdk1.8时:

当存入对象的个数大于等于阈值则扩容;

当存入对象后,最大链表长度大于8,数组总长度小于64的话,也会扩容。

当数组长度大于等于

2的幂次方。用这个算式可以减少哈希碰撞的几率。

https://blog.csdn.net/gududedabai/article/details/85784161

HashMap多线程操作可能出现死循环?

其根本原因是:假如线程A对Entry数组扩容后修改了链表元素 next 和prev 指向,线程B也在对Entry数组扩容。此时问题出现了:线程A已经修改了链表元素的指向,所以线程B的操作都是错误了,在这样的互斥操作中造成了两个(也有可能是多个)链表元素互相指向(也就是死循环)。

https://www.cnblogs.com/xrq730/p/5037299.html

ConcurrentHashMap

多线程操作hashmap 有哪些问题?
假设有两个线程同时进行put操作,可能会出现有一个线程的操作被覆盖掉。
如果put操作没有加锁,在并发执行时,put操作不能保证执行的顺序行。
比如 A和B两个线程, 不能保证A一定能保证在B前面操作。

HashEntry:

等同于value和next变量被volatile修饰的Entry类。

在jdk1.7的时候:

由Segment数组实现,Segment继承了ReentrantLock(它是一个重入锁),每个Segment对象包含一个HashEntry数组(对应全表的一段数据),这样的话多个线程访问不同数组区域就可以同时读写。

在jdk1.8的时候:

Segment没有继承ReentrantLock,用CAS+Synchronized保证并发访问;数据容器由HashEntry改为Node节点,但作用都是相同的;val和next属性用valatile修饰,保证数据的可见性。

重点是写入数据的变化:

先用CAS+自旋尝试修改数据;如果失败,则执行Synchronized修饰的代码块。

参考:

https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/

https://www.jianshu.com/p/d0b37b927c48

https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/

遍历map

通过keySet()获取keys 进行遍历,或者通过 entrySet,同时得到key和value。
https://blog.csdn.net/tjcyjd/article/details/11111401

按锁的特性进行分类:

乐观锁 && 悲观锁

乐观锁的心态很好,不需要加锁,通过算法保证线程的安全。

悲观锁比较害怕,获取数据的时候先加锁,确保安全。Java中,synchronized关键字和Lock的实现类都是悲观锁。

应用场景

  • 悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
  • 乐观锁适合读操作多的场景,  不加锁的特点能够使其读操作的性能大幅提升。

区别:

https://blog.csdn.net/fhy569039351/article/details/83040384

自旋锁&& 适应性旋锁

举个例子,你去银行柜台排队取钱,你前面有一人正在取钱,你告诉柜台的工作人员:轮到我时,麻烦您叫我,我去椅子上坐会儿。

这是悲观锁的工作过程,当遇到其他线程正在占用锁资源时,当前线程会被系统挂起,也就是阻塞。

在生活中,当银行柜台工作人员叫你时,你跑过去办理业务。这其中的消耗=工作人员叫你+你奔跑。

在线程中的消耗=系统将线程唤醒+线程的切换。

自旋锁的解决方案

当前线程无法获取锁的控制权,那就一直等待(随便做点事情,不要离开),不要放弃cpu的控制权。

这个前提是你的CPU有多个核心或者支持超线程。

缺点:

自旋锁本身是有缺点的,它不能代替阻塞。自旋等待虽然避免了线          程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。

自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

互斥锁

简介

在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于,公司部门里,我在使用着打印机打印东西的同时(还没有打印完),别人刚好也在此刻使用打印机打印东西,如果不做任何处理的话,打印出来的东西肯定是错乱的。
  在线程里也有这么一把锁——互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )

互斥锁的特点

  1. 原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量;
  2. 唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;
  3. 非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。

公平锁 && 非公平锁

简介

公平锁:

多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

非公平锁:

多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

Synchronized

锁的对象是类的实例,持有锁的是线程。
https://blog.csdn.net/enjoyinwind/article/details/7729624

可重入锁 && 非可重入锁

可重入锁解决的问题:

如下:两个方法同时被加锁,如果首先执行method1,则该线程拥有Test对象的锁,但如果synchronized不是可重入锁,当method1方法调用method2,发现method2需要等待method1锁释放,但是method1的执行又必须依赖method2,于是循环等待,形成死锁。
也就是,执行方法B时需要将方法A的锁住的对象释放掉

独享锁&& 共享锁

  • 独享锁是指该锁一次只能被一个线程所持有。
  • 共享锁是指该锁可被多个线程所持有。

自旋锁

在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

互斥锁/读写锁

上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。

  • 互斥锁在Java中的具体实现就是ReentrantLock
  • 读写锁在Java中的具体实现就是ReadWriteLock

ReentrantLock

通过类实现锁的设计,属于重入锁,显示地加锁和解锁。‘

分为公平锁和非公平锁,可以通过构造方法指定。

https://crossoverjie.top/2018/01/25/ReentrantLock/

类加载器

双亲委派模式

java-1.2之后引入。

作用:避免类的重复加载。

原理:当一个类加载器收到了类的加载请求,它并不会自己先去加载,二十委托给父类加载。如果父类加载器也继承了其他类加载器,则进一步向韩国委托,依次递归,请求最终将达到顶层的引导类加载器。如果父类加载器可以完成任务,直接返回;否则,子类尝试去加载,这就是双亲委派。

参考:

https://www.jianshu.com/p/9df9d318e838

类的加载过程

https://blog.csdn.net/m0_38075425/article/details/81627349

String

intern()

是一个native方法。

new String是在堆上创建一个字符串对象,

String.intern方法会在常量池中创建字符串并返回引用。

序列化

https://www.cnblogs.com/niceyoo/p/10596657.html

https://www.cnblogs.com/9dragon/p/10901448.html

反射应用

https://blog.csdn.net/R_s_x/article/details/100670643

https://blog.csdn.net/huangliniqng/article/details/88554510

线程池

https://crossoverjie.top/2018/07/29/java-senior/ThreadPool/

Spring中两种动态代理的区别

基于JDK实现的动态代理,其委托类和代理类共同实现同一接口类。

基于CGLib实现的动态代理,当委托类没有实现接口时,代理类继承委托类完成方法增强。

在1.7的时候,基于JDK的动态代理性能较弱;在1.8的时候,基于JDK的动态代理性能较强。

https://zhuanlan.zhihu.com/p/106336169

Mysql

Mysql中的MVCC原理

https://blog.csdn.net/w2064004678/article/details/83012387

聚簇索引和辅助索引

https://www.cnblogs.com/ghl666/p/11934215.html

Mysql中的唯一约束可以有多个空值(sqlServer中不可以哦):

https://blog.csdn.net/qq_40414738/article/details/104911552

联合索引的失效条件

1、索引列不能做任何操作(计算、函数、类型转换(自动或手动)),会导致全表扫描
2、一个列使用范围条件后,右边的列会索引失效。
3、避免select *,避免select 为建立联合索引的字段
4、不使用 != 或者 <> ,会导致全表扫描
5、is null 或者 is not null 会走索引,但效率低
6、要保证联合索引中的字段按顺序出现,如果出现 a b d,中间缺个c,会导致索引只作用在 a b列。

举例:

create table myTest(
         a int,
         b int,
         c int,
         KEY a(a,b,c)
    );
(1)    select * from myTest  where a=3 and b=5 and c=4;   ----  abc顺序
abc三个索引都在where条件里面用到了,而且都发挥了作用


(2)    select * from myTest  where  c=4 and b=6 and a=3;
where里面的条件顺序在查询之前会被mysql自动优化,效果跟上一句一样


(3)    select * from myTest  where a=3 and c=7;
a用到索引,b没有用,所以c是没有用到索引效果的


(4)    select * from myTest  where a=3 and b>7 and c=3;     ---- b范围值,断点,阻塞了c的索引
a用到了,b也用到了,c没有用到,这个地方b是范围值,也算断点,只不过自身用到了索引


(5)    select * from myTest  where b=3 and c=4;   --- 联合索引必须按照顺序使用,并且需要全部使用
因为a索引没有使用,所以这里 bc都没有用上索引效果


(6)    select * from myTest  where a>4 and b=7 and c=9;
a用到了  b没有使用,c没有使用


(7)    select * from myTest  where a=3 order by b;
a用到了索引,b在结果排序中也用到了索引的效果,a下面任意一段的b是排好序的


(8)    select * from myTest  where a=3 order by c;
a用到了索引,但是这个地方c没有发挥排序效果,因为中间断点了,使用 explain 可以看到 filesort


(9)    select * from mytable where b=3 order by a;
b没有用到索引,排序中a也没有发挥索引效果

https://blog.csdn.net/qq_35275233/article/details/87888809

解决 like '%name%' 导致索引失效

如果 select 只有一个字段且是like 比较的字段,比如 select name from user where name like '%name%',则索引正常。
如果 是select name, age from user where name like '%name%',出现了多个字段,则需要建立覆盖索引,且不能单独建立索引。
https://blog.csdn.net/hzy38324/article/details/44975859

设计安全的接口

https://www.cnblogs.com/codeon/p/5900914.html

https://www.cnblogs.com/codeon/p/6123863.html

设计规范

https://zhuanlan.zhihu.com/p/86446096

Spring

如何解决设值循环依赖

spring会递归创建半成品对象,然后依次填充属性完成属性注入。

https://zhuanlan.zhihu.com/p/84267654

spring只支持单例模式下的设值循环加载。

Bean的加载过程

https://www.jianshu.com/p/9ea61d204559

获取BeanName

合并Bean定义

实例化

属性填充

获取Bean

列举

SpringMVC的加载过程

用户向服务器发送一次请求,这个请求会先到达DispatcherServlet;

DispatcherServlet调用(根据请求的URL)HandlerMapping找到对应的Controller;

然后DispatcherServlet再调用HandlerAdapter执行Controller中的方法并获得方法返回的ModelAndView;

然后Dispatcher调用ViewResolver解析(将模型数据填充到页面)JSP页面;

https://www.cnblogs.com/hamawep789/p/10840774.html

并发

线程有哪些状态?

new 初始状态:创建了一个线程对象,但没用调用 start 方法;

runnable 运行状态:就绪(ready)和运行中(running)统称为“运行”;

线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

blocked 阻塞状态,表示线程阻塞于锁;

waiting 等待:调用join或者wait方法导致,等待被其他线程唤醒;

time_waiting 超时等待:在指定时间后自行返回;

terminated 终止:线程已经执行完毕;

https://blog.csdn.net/pange1991/article/details/53860651

阻塞和挂起

挂起就是释放CPU控制权,对象锁也会释放,进入线程等待队列等待唤醒。

调用sleep方法进入了睡眠状态,但没有交出CPU的控制权;

调用wait方法就是交出CPU的控制器,排队等待唤醒;

举个例子:

挂起就好比我离开一会,有事叫我;

睡眠就像上班期间有点瞌睡眯了一会后继续工作。

阻塞呢就是它占着厕所拉屎,那你就需要等一会,表示现在的状态。

https://blog.csdn.net/willmu/article/details/98734121

https://blog.csdn.net/u012050154/article/details/50903326

异步和阻塞

阻塞表示当执行一条复杂任务时,等待执行结果;非阻塞就是直接往下执行程序,至于你怎么拿到上一个任务的返回结果,那不是我该关心的。

而同步就是阻塞时的具体操作,同步执行的线程会一直询问上级系统是否执行完毕;异步执行的线程不会主动询问任务的执行结果。继续往下执行,当上个任务执行完毕后,上级系统会主动通知当前线程。

总结:

阻塞和非阻塞可以理解成设计方案或者状态;

同步和异步就是具体解决方案。

参考:

https://zhuanlan.zhihu.com/p/22707398

类的加载

当class文件被加载时,类信息会放到方法区,还会为该class文件对象创建对象的CLass对象到堆中,该对象存储了class对象的信息。

类的反射

反射是一种机制,SUN公司提供的让程序在运行中能获取类的信息,还能直接创建类的对应。

这里可能有疑问,为什么不能new出来个对象?

new 需要你提前在java文件中编写创建对象的代码,经过JVM编译执行就能创建类的的对象。

问题来了,假如你没有提前在代码中编写创建对象的代码呢或者换个说法:通过new创建对应必须import导入对应的类,这就造成了依赖,怎么解决这种问题?

反射机制可以在运行中通过全限定类名加载,解决了运行时无法创建对象的问题。

反射就是通过Java开发包提供的接口方法(在Java.lang.reflect下)去获取类的各种信息,就好比给你开了一个管理员账号,你可以查看所有成员的信息。

https://blog.csdn.net/LemonTreey/article/details/54095043

比如说Java程序中的数据库驱动就是通过Class.forName()方法加载:

https://blog.csdn.net/yangguosb/article/details/77990131

反射的存在让对应的创建变得灵活:

https://blog.csdn.net/bule_sky_wait_me/article/details/78702517

Class.forName():加载类并执行静态代码块和静态方法;

ClassLoder:加载类至内存;

https://blog.csdn.net/qq_27093465/article/details/52262340

Class.forName()的作用:

https://blog.csdn.net/fengyuzhengfan/article/details/38086743

反射的应用场景:

https://segmentfault.com/a/1190000010162647

IO通信

IO通信模式有:BIO、NIO、AIO

BIO是以线程池为基础设施的同步通信,由一个独立的Acceptor线程负责给客户端分配线程;

NIO是通过双向通道+多路复用Selector+缓冲区的一个同步通信,提高了线程资源的利用率,只处理有请求的客户端流;

AIO是调用内核去读取数据,然后通知应用程序。

https://blog.csdn.net/yjp198713/article/details/79300420

SpringBoot

启动类 @SpringBootApplication 三个核心注解

@SpringBootConfiguration:

和@Configuration一样,用于加载用户Bean,也就是由开发者定义的Bean.

@EnableAutoConfiguration:

它会自动加载一些基础的或者必须的Bean,

比如 Servlet,JDBC,DataSource,如果你继承了第三方框架它也会帮你加载到IOC;

这些基础的Bean在 spring-boot-autoconfigure.jar下的META-INF/spring.factories 文件中一一列举.

@ComponentScan:

也是就是Bean加载,只不过是大范围的去批量加载,省事;

参考:

https://www.cnblogs.com/zgwjava/p/10892810.html

定时任务

如果间隔时间为 5 分钟,任务执行时间为6分钟。
设置 @Scheduled(fixedRate = 5000)
当上次任务执行完成之后 ,间隔5000ms 之后再执行。
或者通过 @Asyn 注解
https://blog.csdn.net/u013425438/article/details/100658786

Redis

五数据类型:
string
hash
list
set
zset

持久化机制:
RDB
定时将内存中的数据快照写入磁盘,优点是效率高,缺点是会出现一定的数据丢失,不能保证完整性。
AOF
以日志的形式实时记录所有执行的命令,优点是数据近乎完整,缺点是效率低,备份文件比较大恢复慢。

缓存穿透:

如果有人恶意请求不存在的key,请求会落到数据库。
使用布隆过滤器,能过滤掉大部分的key,但是有误判的情况。其原理是提前将数据库的key计算成hash值,请求时,用同样的算法计算用户提供的key,如果出现hash值相同,就说明这个key被计算过,也就是存在缓存,可以查,如果不同则拒绝。
https://cloud.tencent.com/developer/article/2046488

缓存击穿:
热点key 突然失效了,短时间内大量请求落到数据库。
1、业务允许的话,设置key永不过期
2、可以 redis查询不加锁,mysql查询时加锁,这样不至于mysql 会崩掉,第一次请求处理完,数据放到redis,后面的请求从redis 读取。
缓存雪崩:
redis 短时间内大量的key失效,导致请求落到数据库。
1、redis 添加数据时,key 设置不同的过期时间。
2、做熔断,流量达到阈值时,返回系统拥挤之类的提示。

SQL 优化

常见的索引类型:
Hash、BTree、FullText
hash适合等值查询;
btree 适合范围查询;
FullText 全文索引,解决 like模糊查询效率低,进行分词。只有支持MyIsam引擎支持;
索引种类:
普通索引,加速条件检索。
唯一索引,保证列值唯一性,插入时会检查是否存在;
主键索引,比如id列,作为行的标识;
全文索引:
对文本内容进行分词;

聚簇索引:
数据和索引放在一块,在数据结构中,找到索引可以立即拿到数据,例如id列默认作为聚簇索引。
非聚簇索引,数据和索引分开,找到缩印后通过其叶子节点获取数据所在的行,然后拿到数据,例如hash、btree索引。

优化sql:
1、where条件字段建立索引
2、避免索引失效,or(左右有一个没索引)、like 、含非的条件,如not、!=、not like、使用内置函数等会导致索引失效。
3、可以通过 explain查看索引是都生效,以及扫描得行数。
4、避免select *,会导致全表扫描。
5、大数据量下分页优化,如果同时包含了select *和limit,匹配条件时会出现大量的回表操作,可以先用limit只查出符合条件的id列表,然后通过in子查询,查出id对应的数据。

Mybatis

MyBatis一级缓存介绍 - MyBatis中文官网
一级缓存
作用域是同一个session会话中,并发更新会导致出现脏读。
同一个会话中,第二次查询会读取缓存。
默认开启,插入或更新会导致缓存清除
二级缓存
作用域是同一个mapper文件中,因为不能保证所有查询都在一个mapper,所以可能会导致脏读。
默认不开启。

RabbitMQ

使用rabbitmq的好处
1、解耦,调用不同的模块,以前的写法是直接调用其他模块的方法,现在可以通过 mq 作为载体,不同模块之间传递消息。
2、mq 的发送是异步的,加快程序处理速度。
3、削峰,出现短暂的流量高峰期,mq可以作为数据缓冲,避免客户端因为瞬时流量大导致宕机。

避免消息丢失:
服务端消息丢失,比如重启主机后,数据丢失,配置持久化。
客户端消息丢失,正常情况下收到消息就算是消费成功, 但业务处理可能会失败,把应答机制改为手动,当业务处理完成,手动发送 ack 确认。
重复性消费:
每个消息设置一个唯一id,消费之前查询是否存在。
消息顺序性:
简单的话,为同一类型的业务单独创建一个队列,一个队列只分配一个消费者,避免连续性数据被分发。

https://zhuanlan.zhihu.com/p/384200726
https://blog.csdn.net/upstream480/article/details/121054760
https://www.cnblogs.com/-wenli/p/13047059.html

线程的创建

Runnable 和thread
1、继承 Thread类
重写 run方法

调用 start () 执行线程

2、实现 runnable 接口
实现run方法
将创建的对象传递到 thread的构造器中,创建 thread 的对象 ,调用 start 启动线程
开发中优先使用 runnable 接口适合多个线程共享数据的情况。
3、实现callable 接口
实现 call 方法
有点是可以获取返回值。
https://blog.csdn.net/weixin_43824267/article/details/112706385

内存溢出排查

使用jmap生成dump文件,就是虚拟机内存快照。
可以通过MAT工具或者jdk自带的jvisualvm.exe 分析dump文件,可以看到哪个类中的属性出现了溢出。
https://blog.csdn.net/u010430495/article/details/87283064

https://blog.csdn.net/Deronn/article/details/106110137
https://www.cnblogs.com/hi3254014978/p/14158539

vue

在页面加载完加入逻辑的钩子函数
mounted
https://blog.csdn.net/weixin_44283432/article/details/108091735
绑定属性 v-bind

线程计数器 CountDownLatch

https://blog.csdn.net/u010978399/article/details/117771620

主线程中使用 await 等待任务执行结束。

线程池分类

线程池有两大类,executors 和 threadpoolexecutor ,executors 使用无界的任务队列,容易内存溢出。建议使用threadpoolexecutor

threadpoolexecutor 线程池参数

参数是 最小线程数量,最大线程数量,最大存活时间,任务队列,拒绝策略

多线程事务问题

使用编程式事务,TransactionTemplate,执行execute方法,参数是一个transactionCallback接口,具体的类是TransactionCallbackWithoutresult,try catch手动捕捉异常,出现问题回滚,setRollBackOnly

idea 一键重命名方法

shift + F6
或者 鼠标右键》refactor》rename

事务的四大特性

原子性、一致性、隔离性、持久性
原子性:事务中的多条sql要不全部成功,要不全部失败
一致性,数据修改前后符合业务正确性。
持久性:数据的改变是永久性的。
隔离性:可以设置隔离级别

aop 怎么写

类上面添加 @Aspect @Component
设置路径表达式 @Pointcut
before 在方法在方法执行前
around 需要手动调用 procedd方法执行目标方法
after 在方法之后执行
一般只需要 around 环绕切面就够了

解决重复付款问题

一般支付系统会分为商品订单和支付流水,一对多的关系。

主要是问题是如果用户点击了两次付款按钮,产生了两个流水订单。
如果两笔流水都成功的话,就会重复付款。

第一次付款后,支付状态未及时同步,让用户以为付款未成功,继而发起了新的付款流水。
多渠道支付也会导致重复支付,停留在微信支付页面,然后再点击支付宝付款,就会出现两个支付窗口,导致重复付款。

解决方法:
1、加锁
在申请支付或者支付回调时加同一个分布式锁,
因为可能在处理支付回调过程中,用户发起了新的支付申请,导致重复付款。

2 关闭流水订单
用户发起新的支付申请时,检查是否存在支付中的流水订单,调用第三方api关闭付款操作,并将流水改为关闭状态。

3已支付流水退款
有部分支付平台无法关闭流水订单的付款功能。
对第二次重复付款的流水调用第三方平台api退款。
4 主动轮询
通过定时任务定时,在30分钟内对支付失败或者支付中的流水订单主动刷新支付状态。
5
支付成功后,mqtt 通知客户端刷新支付结果,防止再次点击付款按钮。
总结一下:
重复支付解决方案有两种,
一种是,当用户点了两次支付,有两个待支付界面,产生两笔流水,我们在第二次点击的时候,判断下,通过api 把之前的流水给关闭付款功能(部分平台不支持关闭),保证只有一个待支付流水。
第二种是网络延迟导致支付状态没更新,那么我们在回掉时判断,如果订单已经支付成功,那么本次回调走退款api 。
创建流水和处理回调要加锁,定时任务主动拉取支付状态。

https://juejin.cn/post/7121554911229116430#heading-8

@Controller和@RestController 有什么区别

@RestController 是组合型注解,@RestController类中包含了 @Controller 和@ResponseBody两个注解。

双亲委派机制

https://mp.weixin.qq.com/s/6nJ-6cDLW6TfysWV5ZB3Iw
总结一下
jvm 内置加载器有(从下向下排序):BootClassLoader(启动类加载器)->ExtClassLoader(扩展类加载器)->AppClassLoader(系统类加载器)。通过parent 属性指向父加载器

每个加载器都有自己默认的读取路径
当用java 命令执行一个类时 Test.class,默认由 AppClassLoader 执行,然后委托上一层加载器执行,直到parent 为 null,也就是到了 最后一层,也就是 BootClassLoader,BootClassLoader 在自己路径加载 Test.class,如果成功就结束,如果失败,由下一层ExtClassLoader 尝试查找,如果上层加载器均为找到Test.class 文件,那么由当前的AppClassLoader 加载。
这里有个情况,如果ExtClassLoader 加载成功了,但发现Test.class 依赖了 A.class,ExtClassLoader 委托上层加载器加载,如果失败,ExtClassLoader 尝试加载,如果也没有找到,则抛弃异常,所以得出结论,依赖类的加载由Test.class的加载器(ExtClassLoader)开始,不会向下传递。

posted @ 2023-06-17 09:40  EggCode  阅读(38)  评论(0编辑  收藏  举报