面试自己的一些准备
Spring 为何需要三级缓存解决循环依赖,而不是二级缓存?
Java八股文面试题视频教程,Java面试八股文宝典(含阿里、腾迅大厂java面试真题…)
基本数据类型的存储
基本数据类型,如果声明在方法内,则存储在方法栈中,如果在类内声明,则存储在堆中;如果是引用类型,则声明在方法中时,通过方法栈指向堆,声明在类中时存储在堆中,指向值存储区域
Spring常见面试总结
Java线程池七个参数详解
Synchronized和ReentrantLock的区别
Synchnorized类锁和对象锁的区别,对象锁的时候,就是交换的堆中的实例对象的markworld;
而对象锁的时候,在加载对象的时候会在堆中生成一个class对象的(java.lang.class,这个与方法取中的instanceKlass的java_mirror字段互指),对这个对象进行加锁。
牛客某个人的八股
JWT详解
手写单例模式
ThreadPoolExecutor
知识
字节二面问Java ThreadPool线程池?我来教你应对



注意看:线程池监控里面,获取线程池大小,获取当前活跃工作线程数量等,都需要使用ReentrantLock来开锁实现。
ThreadPoolExecutor源码实现中用的是ReentrantLock锁?
公平锁:并发环境中,每个线程在获取时会先查看此锁维护的队列,如果为空,或者当前线程是等待队列的第一个就占有锁,否则就会加入到队列中,以后会按照FIFO的规则从队列中取到自己。
非公平锁:上来就直接尝试占有锁,如果尝试失败,再采用类似公平锁的方式。
ReentrantLock而言,通过构造器指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
Synchronized和ReentrantLock的区别
synchronize锁升级
线程A在进入同步代码块前,①先检查MarkWord中的线程ID是否与当前线程ID一致,如果一致(还是线程A获取锁对象),则无需使用CAS来加锁、解锁。
如果不一致,②再检查是否为偏向锁,如果不是,则自旋等待锁释放。
如果是,③再检查该线程是否存在(偏向锁不会主动释放锁),如果不在,则CAS设置线程ID为线程A的ID,此时依然是偏向锁。
如果还在,④则暂停该线程,同时将锁标志位设置为00即轻量级锁(将MarkWord复制到该线程的栈帧中并将MarkWord设置为栈帧中锁记录)。线程A自旋等待锁释放。
⑤如果自旋次数到了该线程还没有释放锁,或者该线程还在执行,线程A还在自旋等待,这时又有一个线程B过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。
⑥如果该线程释放锁,则会唤醒所有阻塞线程,重新竞争锁。
手写有关参数的
Redis内存回收
内存过期策略 + 内存淘汰策略
Redis过期策略
周期删除
惰性删除:用的时候看下是否删除
redis中两者相结合
Redis淘汰策略
默认:noeviction,不淘汰任何key,但是内存满时不允许写入新的数据。
IO多路复用
运行时非运行时异常
下面这一题貌似选C,其他的都是对的。

HashMap为什么线程不安全
JDK1.7 HashMap线程不安全体现在:死循环、数据丢失
JDK1.8 HashMap线程不安全体现在:数据覆盖
类型擦除与解决方案
Java通过反射获取泛型类型信息
在Java中可以通过反射获取泛型信息的场景有如下三个:
- (1)成员变量的泛型
- (2)方法参数的泛型
- (3)方法返回值的泛型
在Java中不可以通过反射获取泛型信息的场景有如下两个:
- (1)类、接、泛型方法 声明的泛型。(只要是声明处的泛型,那就都获取不了,但应该可以通过字节码技术进行获取,因为这些泛型信息应该还是被保存在了class文件中没有丢失的)
- (2)局部变量的泛型,是因为反射连局部变量都访问不了,就更不要提访问它的泛型了,非要访问的话必须通过字节码技术。
Java中的泛型会被类型擦除,那为什么在运行期仍然可以使用反射获取到具体的泛型类型?
运行期只能获取当前class文件中包含泛型信息的泛型类型
而不能在运行时动态获取某个泛型引用的类型
可以通过反射获取到的泛型信息一定是某个class作为成员变量、方法返回值等位置的具体泛型类型,比如:
1 | public class Test<T> { |
可以通过反射获取成员test的Integer泛型信息,但是无法获取item的实际类型。
RabbitMQ和Kafka比较
优先选择RabbitMQ的条件:
·高级灵活的路由规则
·消息时序控制(控制消息过期或消息延迟)
·高级的容错处理能力,在消费者更有可能处理消息不成功的情景中(瞬时或持久)
·更简单的消费者实现
优先选择Kafka的条件:
·吞吐量更高
·严格的消息顺序
·延长消息留存时间,包括过去消息重放的可能,消息回溯
·传统解决方案无法满足的高伸缩能力
Comparable和Comparator两个接口的区别

Spring框架中的单例Bean是线程安全的吗?
答案:不是
Spring默认的bean是单例的,也没有进行封装处理,所以不是线程安全的。
但是,共享不一定会有线程安全问题。
如果某个bean我们定义了共享数据,且可以对共享数据进行修改,这样才会造成线程安全问题。比如我们在线程中定义一个count++,那么这个数就是不安全的,每次线程到来都会被执行一次,count值加一。
controller、service、dao本身不是安全的,但是,我们只在这些方法中相互调用,那么不会产生线程安全问题。
Dao层会操作数据库,但是每一个和数据库连接的connection,都会被数据库的事务机制管理,如果开启了Spring事务机制,那么也会被其管理。这都不会造成线程安全问题。
如果,我们必须要在bean中定义有状态的实例或变量,那么我们可以有以下几种办法,保证安全。
1 使用ThreadLocal,把变量变成线程私有的
2 synchronized、lock、CAS,保证互斥访问临界区
3 把bean从单例(singleton)设置成原型(protopyte)
ThreadLocal使用不当造成内存溢出
ThreadLocal,底层使用map,当前线程的对象作为key,value则是你想让变成私有属性的变量,或实例对象。那么当该线程要使用这个变量或实例时候,就可以从map中get出来。
每一个线程到来后,都会去创建map,如果是线程池中的核心线程,这个线程不死亡,它每次执行到这里都会往map中存对象,可能导致内存溢出。
如何解决呢?线程每次get(key)使用过后,就把map中的数据删除掉,remove(key)。
多线程run()中如何捕获运行时异常?
在实现Runnable或者继承Thread类实现多线程的时候,都要实现或者重写run()方法,查看源码可以看到run()是不允许抛出异常的,那么如果在run()方法中产生了运行时异常如何捕获呢?
Thread类中有UncaughtExceptionHandler这个接口来实现对异常的捕获,我们只需将其方法重写,创建这个实现类的实例O,在线程start之前,使用thread.setUncaughtExceptionHandler(O)将这个实例传入此方法。下面直接看例子。如果在将第四行代码注释掉后可以发现直接抛出异常,无法捕获异常,即使你在主函数中用try,catch捕获也是会直接抛出异常的。那么将第四行注释取消后,运行结果如下,可以看到已经捕获异常。
java正确关流_JAVA中如何优雅的关闭各种流
快速失败(fail-fast)以及其中的问题与安全失败(fail-safe)
Fail-fast
单线程
当在用迭代器(Iterator)或者增强for循环(增强for循环的底层也是迭代器)对一个集合进行遍历操作时,如果遍历的过程中集合的结构发生了变化,就会抛出并发修改异常ConcurrentModificationException。
要想避免抛出异常,应该使用Iterator对象的remove()方法。
多线程
当一个线程在对一个集合进行遍历操作的时候,如果其他线程对这个集合的结构进行了修改,就会抛出并发修改异常ConcurrentModificationException。
fail-safe
Java在java.util.concurrent包中为我们提供了安全失败的集合类,如 ConcurrentHashMap、CopyOnWriteArrayList等。其原理在于开始遍历之前会将原集合完整的复制一份,前者在复制的集合上进行遍历,在原集合上进行元素添加、删除操作,后者反之,这样就可以避免了ConcurrentModificationException异常。
当然,这类集合也有一些缺点:iterator不能保证返回集合更新后的数据,因为其工作在集合克隆上,而非集合本身。其次,创建集合拷贝需要相应的开销,包括时间和内存。
java中的hashCode计算方法总结
string的属性 value 就是字符数组,计算方法,s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
1 | public int hashCode() { |
为什么重写equals方法,还必须要重写hashcode方法
1.为了提高效率
2.为了保证同一个对象
a 保证在equals相同的情况下hashcode值必定相同,如果重写了equals而未重写hashcode方法,可能就会出现两个没有关系的对象equals相同的(因为equal都是根据对象的特征进行重写的),但hashcode确实不相同的。
b hash类存储结构(HashSet、HashMap等等)添加元素会有重复性校验,校验的方式就是先取hashCode判断是否相等(找到对应的位置,该位置可能存在多个元素),然后再取equals方法比较(极大缩小比较范围,高效判断),最终判定该存储结构中是否有重复元素。
Java中抽象类和接口的介绍及二者间的区别
抽象类的特点
1、抽象类不能被实例化,即不能使用new关键字来实例化对象,只能被继承;
2、包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法;
3、抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;
4、抽象类中的抽象方法只有方法体,没有具体实现;
5、如果一个子类实现了父类(抽象类)的所有抽象方法,那么该子类可以不必是抽象类,否则就是抽象类;
6、抽象类可以包含属性、方法、构造方法,但是构造方法不能用于实例化,主要用途是被子类调用。
接口
1、接口可以包含变量、方法;变量被隐士指定为public static final,方法被隐士指定为public abstract(JDK1.8之前);
2、接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题;
3、一个类可以实现多个接口;
4、JDK1.8中对接口增加了新的特性:
4.1、默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;
4.2、静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。
数据量很大的排序问题 大量数据如何排序
问题一:若有1T的数据,比如 只有两列,身份证号和姓名 需要实现由大到小排序,你用什么办法,能否做到 复杂度为O(n),说说你的思路和想法?
问题二:有10个G的数据,也是一样,比如两列,身份证号和姓名,如果两条数据一样,则表示该两条数据重复了,现在给你512的内存,把这10G中重复次数最高的10条数据取出来。
我的思路是:这么大的数据,用普通的排序一定不行,
可以这样,用身份证号的前三位切割这个数据,这样会分成999份,
每一份再进行排序,比如构造一个平衡二叉树,最典型的的就是TreeMap和TreeSet(TreeSet底层是使用了TreeMap算法,而TreeMap算法底层是实现了红黑树的平衡二叉树的排序);
然后按照文件名进行排序,这样就实现了大数据排序;
因为排序二叉树的复杂度为O(lgn)到O(n) ;
因此我们可以做到 O(n)
问题二:
解法是一样的 按照身份证号前三位 分割999份,然后对这每个文件找到重复的最多的十条,这样,我们得到了999个文件,每个文件有 10条数据
在对这个999*10条进行排序找到 重复率最高的十条即可;
大数据的文件归并排序
第一 将数据分成若干份(我们这里以10份为例子),每份都接近内存的最大数据量,这样子,将文件上的数据读入内存中,将它排序,结束后我们就可以得到10份有序的文件数据了。
第二步,归并文件,将两个文件归并成一个有序的文件后,在让这个有序的文件与第三个文件归并,这样依次下来,我们就可以得到一个有序的文件了。
ThreadLocal 详解
进程和程序的区别
进程是动态的,程序是静态的:程序是有序代码的集合,进程是程序的执行。进程有核心态/用户态。
程序是我们写下来的一段代码,而进程的产生是因为代码开始执行了
进程的组成包括程序、数据和进程控制块(即进程状态信息)
反射得到的对象和new的对象区别
1 在使用反射的时候,必须确保这个类已经加载并已经连接了。使用new的时候,这个类可以没有被加载,也可以已经被加载。
2 new出来的对象我们无法访问其中的私有属性,但是通过反射出来的对象我们可以通过setAccessible()方法来访问其中的私有属性。
3 new静态编译,反射动态编译。new关键字是强类型的,效率较高。反射是弱类型的,效率低。
4 反射提供了一种更加灵活的方式创建对象,得到对象的信息。Spring AOP和Java动态代理都是基于反射。
设计模式:静态代理和动态代理的区别 以及 springAop和cglib的区别
1 JDK动态代理是通过持有对象来实现的(就是一个类继承Proxy,然后持有该类(一开始用object引用持有,后面确定的时候传参放入.class)):1.动态代理的实现类的类型是不固定的,所以只能是Object,因为Object能包容一切
增强的代码要留给开发人员自己写,所以不能写死
2 通过继承父类所有公开的方法,然后重写这些方法,在重写时增强这些方法。
性能对比:
jdk创建对象的速度远大于cglib,这是由于cglib创建对象时需要操作字节码。
cglib执行速度略大于jdk,所以比较适合单例模式。
另外由于cglib的大部分类是直接对Java字节码进行操作,这样生成的类会在JVM的永久代中。如果动态代理操作过多,容易造成永久代满,触发OutOfMemory。
核心思想:
jdk是通过持有对象来实现增强的;cglib是通过继承来实现增强的
值得注意的是
spring默认使用jdk动态代理,如果类没有接口,在使用cglib实现动态代理。
红黑树
- AVL的左右子树高度差不能超过1,每次进行插入/删除操作时,几乎都需要通过旋转操作保持平衡
- 在频繁进行插入/删除的场景中,频繁的旋转操作使得AVL的性能大打折扣
红黑树通过牺牲严格的平衡,换取插入/删除时少量的旋转操作,整体性能优于AVL
红黑树插入时的不平衡,不超过两次旋转就可以解决;删除时的不平衡,不超过三次旋转就能解决
红黑树的红黑规则,保证最坏的情况下,也能在 O(log2N)时间内完成查找操作。
规则:
- 节点不是黑色,就是红色(非黑即红)
- 根节点为黑色
- 叶节点为黑色(叶节点是指末梢的空节点
Nil
或Null
) - 一个节点为红色,则其两个子节点必须是黑色的(根到叶子的所有路径,不可能存在两个连续的红色节点)
- 每个节点到叶子节点的所有路径,都包含相同数目的黑色节点(相同的黑色高度)
Java在云原生的破局利器——AOT(JIT与AOT)
java实现多继承的三种方式
三个实现java多继承的方法:多层继承、内部类、接口
1 多层继承:一般不建议超过三层,孙子类会获得父类的所有非私有方法和属性。(B继承A,C继承B,C就间接性的的继承A和B)
2 成员内部类:直接在类的内部定义成员类,使之成为该类的一部分,达到高内聚的效果。
3 接口:java中可以继承多个接口,实现该接口的实现类需要实现该接口及父接口的所有方法。(IC继承IB,IC impl实现IC 就相当于继承了它们)
Linux 查看日志的方法
魔鬼面试官必问:ConcurrentHashMap 线程安全吗?
线程创建的三种方式
而使用Runnable接口和Callable接口的方式,区别在于前者不能获得线程执行结束的返回值,后者可以获得线程执行结束的返回值。
而继承父类和实现接口这两种方式的优缺点是: - 采用接口的方式创建线程,优点是线程类还可以继承于其他类,并且多个线程可以共享一个线程体,适合多个线程处理同一份资源的情况。缺点是编程稍微麻烦一点点。 - 采用继承的方式创建线程,优点是编程稍微简单一点点。缺点是因为线程类已经继承了Thread类,所以就不能继承其他的父类了。 所以,通常情况下,更推荐采用接口的方式来创建线程。如果需要返回值,就使用Callable接口,否则使用Runnable接口即可。
时间空间复杂度概念
在进行算法分析时,语句总的执行次数 T(n) 是关于问题规模 n 的函数,进而分析 T(n) 随 n 的变化情况并确定 T(n) 的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n) = O(f(n)) 。它表示随问题的规模 n 的增大,算法执行时间的增长率和 f(n) 的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中 f(n) 是问题规模 n 的某个函数。
算法的空间复杂度通过计算算法所需要的存储空间实现,算法的空间复杂度的计算公式记作:S(n)=O(f(n)),其中,n 为问题规模,f(n) 为语句关于 n 所占存储空间的函数。
二分排序
1 | // 二分排序中,关键字的比较次数采用折半查找,数量级为O(nlogn), * 但是元素移动次数为O(n^2),因此时间复杂度为O(n^2)。 |
Author: Jcwang
Permalink: http://example.com/2022/08/29/%E5%85%AB%E8%82%A1%E9%9D%A2%E8%AF%95%E5%87%86%E5%A4%87/