JAVA高阶面试题
1 Synchronized锁升级的原理
答:Synchronized在jdk1.6之前是通过重量级锁的方式实现。重量级锁底层是通过Mutex Lock来实现互斥锁的一个功能,Mutex是系统方法,调用的时候用户需要切换到内核状态执行。在jdk1.6版本之后,增加了锁的升级机制。线程访问synchronized同步代码块时,根据线程的竞争情况在不加重量级锁的情况下,引入了偏向锁和轻量级锁(自旋锁)。
偏向锁 - 当前的锁偏向于某个线程,通过CAS机制来修改偏向锁的一个标记,这种锁适合同一个线程多次去申请同一个锁资源的情况,并没有其他线程竞争的一个场景中。
轻量级锁 - 基于自适应自旋的机制,通过多次自旋去重试竞争锁。
如果有线程竞争锁资源,首先会使用偏向锁方式去竞争锁资源,如果能拿到锁就表示加锁成功。如果失败,说明当前已经有其他线程占用了偏用锁,就需要将锁升级到轻量级锁。竞争锁的线程会根据自适应自旋次数,去尝试占用锁资源,如果没有成功,就再升级到重量级锁,在重量级锁状态下,没有竞争到锁的线程状态就阻塞(Blocked),就需要等待锁的释放再执行。
另外可以使用乐观锁版本链,来解决多个并行事务的竞争问题。
2 反射的优缺点
答:动态的获取对象的成员变量,属性,方法。
java.lang.reflect(construct,field,methhod)
优点:可以在运行的过程中动态对类进行修改和操作。提高代码的利用率,比如动态代理。在运行时获取任意一个类的方法,属性,并且通过反射进行动态调用。
缺点:反射会涉及动态类型的解析,所以JVM无法对这些代码进行优化。代码可读性会下降,会破坏代码的抽象性。
3 分布式事务的理解和解决方案(跨库)
答:分布式事务 - 指存在多个跨库事务的事务一致性问题,或者指在分布式架构下由多个应用节点组成多个事务之间一致性问题。
第一种:基于XA协议强一致性事务方案。性能低。
第二种:基于可靠消息最终一致性方案的弱一致性事务方案。TCC事务模型,基于可靠性消息最终一致性方案。Seata的Saga事务模型。通过异步补偿去达到数据的最终一致性。并发量比较高的场景。
4 TCC解决方案是什么
https://maimai.cn/article/detail?fid=1740188015&efid=j81Zo3C7scjwKzhqm4nbuw
TCC(Try-Confirm-Cancel)是一种常用的分布式事务解决方案,它将一个事务拆分成三个步骤:
• T(Try):业务检查阶段,这阶段主要进行业务校验和检查或者资源预留;也可能是直接进行业务操作。
• C(Confirm):业务确认阶段,这阶段对Try阶段校验过的业务或者预留的资源进行确认。
• C(Cancel):业务回滚阶段,这阶段和上面的C(Confirm)是互斥的,用于释放Try阶段预留的资源或者业务。
5 分布式CAP理解(CP或者AP)
答:CAP模型是指在分布式架构上不能同时满足三个点
一致性(Consistency):请求访问分布式系统里面每一个节点都能获取最新的数据
可用性(Availability):每一次请求都要有效的访问,但不保证数据是最新
分区容错性(Partition tolerance):对通信损耗的一个要求,如果系统不能达到一致性就会发生一个分区的情况
最常见的例子是读写分离,某个节点负责写入数据,然后将数据同步到其它节点,其它节点提供读取的服务,当两个节点出现通信问题时,你就面临着选择A(继续提供服务,但是数据不保证准确),C(用户处于等待状态,一直等到数据同步完成)
6 分布式架构服务注册中心是AP还是CP
答:Eureka服务注册中心是AP(可用性), zookeeper是CP(强一致)
7 Mybatis分布理解
答:有三种方式实现分布
7.1 直接在select的语句上增加数据库提供的分页关键字,然后在应用程序里面传递当前页,以及每页展示条数
7.2 使用Mybatis提供的rowbounds对象,实现内存级别分页
7.3 基于Mybatis里面的Interceptor拦截器在select语句执行之前动态拼接分页关键字,pageHelper
8 Mysql中MyISAM和InnoDB引擎区别
8.1 数据存储方式不同
MyISAM - 数据和索引是分开存储
InnoDB - 数据和索引是一起存储
8.2 支持事务不同,MyISAM不支持事务,InnoDB支持事务
8.3 对锁支持不同,MyISAM只支持表锁,InnoDB支持行锁,表锁,间隙锁,临键锁
8.4 外键 - MyISAM不支持外键,InnoDB支持外键
9 Spring如何解决循环依赖问题
答:循环依赖是指一个或多个bean存在直接或间接的一个依赖关系。
直接依赖:A依赖B, B依赖A
间接依赖:ABC间互相依赖
自我依赖:自己bean依赖
利用缓存机制解决循环依赖问题,spring设计了三级缓存解决循环依赖的问题,分别是一级缓存:singletonObjects;二级缓存:earlySingletonObjects;三级缓存:singletonFactories;
- 一级缓存:singletonObjects,存放初始化后的单例对象;
- 二级缓存:earlySingletonObjects,存放实例化,未完成初始化的单例对象(未完成属性注入的对象);
- 三级缓存:singletonFactories,存放ObjectFactory对象;
具体说明
A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化BB实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的AB顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A放到一级缓存中。
10 强引用,软引用,弱引用,虚引用
答:对象不同的可达性状态,对GC有影响。
强引用 - 普通对象的引用,只要还有强引用指向一个对象,表示对象还活着。GC是无法回收这类对象。只要把赋值为null时,才会进行内存的一个回收。
软引用 - 如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
弱引用 - 只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
虚引用 - 虚引用必须和引用队列(ReferenceQueue)联合使用。任何时候都可能被垃圾回收器回收。虚引用并不会决定对象的生命周期。
引用类型 | 被垃圾回收时间 | 用途 | 生存时间 |
---|---|---|---|
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软引用 | 当内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | 正常垃圾回收时 | 对象缓存 | 垃圾回收后终止 |
虚引用 | 正常垃圾回收时 | 跟踪对象的垃圾回收 | 垃圾回收后终止 |
11 缓存击穿
原因:
1. 缓存数据在过期的时候没有数据了,全部打到数据库。2.查询不存在的key, 全部打到数据库。
解决:续期,多级缓存,分布式锁,布隆过滤器
1. 对热点key不设置过期时间(相对数据量不大),或者在访问数据的时候对数据过期时间进行续期。
2. 对于访问量较高的缓存数据,可以设计多级缓存,尽量减少后端存储设备的压力。
3. 使用分布式锁,当发现缓存失效时,不是先从数据库加载,而是先获取分布式锁,获得分布式锁的线程从数据库查询数据写回到缓存里面。
4. 对于恶意攻击类的场景,可以使用布隆过滤器,应用启动时把存在的数据缓存到布隆过滤器里面。
12 分布式和微服务理解
分布式:单体的应用分布到不同的网络计算机中,集群满足不了高并发而产生的一种设计思想。简单来说,就是一群独立计算机集合共同对外提供服务,但是对于系统用户来说,就像是一台计算机在提供服务一样。
微服务: 微服务架构风格是一类将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值的开发方式。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API)。颗粒度更细,将业务拆分,订单服务,商品服务,用户服务等等。
13 接口和抽象类
抽象类是对类的抽象,接口是对行为的抽象,抽象类定义的是类,接口定义的是行为。
- 抽象类是从子类中发现了公共的东西,泛化出父类,然后子类继承父类。abstract关键字修饰的类称为抽象类。
- 接口是根本不知道子类的存在,方法如何实现还不确认,只是预先定义行为规范。使用interface关键字修饰。
抽象类场景:假设某个项目的所有HTTP请求都要用相同的方式进行权限判断、访问日志记录和异常处理,那么就可以定义一个抽象的基类,让所有的controller都继承这个抽象基类,在抽象基类的service方法中实现上述功能,在各个子类中只是完成各自的业务逻辑代码。
1 import java.io.IOException; 2 import javax.servlet.ServletException; 3 import javax.servlet.http.HttpServlet; 4 import javax.servlet.http.HttpServletRequest; 5 import javax.servlet.http.HttpServletResponse; 6 7 public abstract class BaseServlet extends HttpServlet { 8 public final void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { 9 10 // 记录访问日志 11 // 进行权限判断 12 } 13 14 protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; 15 // 注意访问权限定义成protected,显得既专业,又严谨,因为它是专门给子类用的 16 } 17 18 class MyServlet1 extends BaseServlet { 19 protected void doService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { 20 // 本Servlet只处理的具体业务逻辑代码 21 } 22 23 }
接口场景:泛指供别人调用的方法或者函数。从这里,我们可以体会到Java语言设计者的初衷,它是对行为的抽象。
public interface Alarm { public static final String effect ="提醒/警告";//相当于常量 void alarm(); default void alarmMode() { System.out.println("叫"); }; }
设计一个报警门继承Door类和实现Alarm接口
public interface Alarm { public static final String effect ="提醒/警告"; void alarm(); default void alarmMode() { System.out.println("叫"); }; } public abstract class Door { //定义抽象类 private String brand;//品牌 private double price;//价格 public Door() { } public Door(String brand, double price) { this.brand = brand; this.price = price; } public abstract void open();//抽象开门方法 public abstract void close();//抽象关门方法 public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } } public class SecurityDoor extends Door implements Alarm { public SecurityDoor() { } public SecurityDoor(String brand, double price) { super(brand, price); } @Override public void open() { System.out.println("64位加密密码开启"); } @Override public void close() { System.out.println("三层防盗依次关闭"); } @Override public void alarm() { System.out.println("默认开启报警"); } }
14 浅拷贝与深拷贝
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
- 深拷贝在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。
clone方法执行的是浅拷贝, 在编写程序时要注意这个细节
1 public class Person implements Cloneable{ 2 3 private int age ; 4 private String name; 5 6 public Person(int age, String name) { 7 this.age = age; 8 this.name = name; 9 } 10 11 public Person() {} 12 13 public int getAge() { 14 return age; 15 } 16 17 public String getName() { 18 return name; 19 } 20 21 @Override 22 protected Object clone() throws CloneNotSupportedException { 23 return (Person)super.clone(); 24 } 25 } 26 27 ==================== 28 Person p = new Person(23, "zhang"); 29 Person p1 = (Person) p.clone(); 30 31 String result = p.getName() == p1.getName() 32 ? "clone是浅拷贝的" : "clone是深拷贝的"; 33 34 System.out.println(result); 35 ==================== 36 输出结果: 37 clone是浅拷贝的
如果想要深拷贝一个对象, 这个对象必须要实现Cloneable接口,实现clone方法,并且在clone方法内部,把该对象引用的其他对象也要clone一份 , 这就要求这个被引用的对象必须也要实现Cloneable接口并且实现clone方法。
1 static class Head implements Cloneable{ 2 public Face face; 3 4 public Head() {} 5 public Head(Face face){this.face = face;} 6 @Override 7 protected Object clone() throws CloneNotSupportedException { 8 //return super.clone(); 9 Head newHead = (Head) super.clone(); 10 newHead.face = (Face) this.face.clone(); 11 return newHead; 12 } 13 } 14 15 static class Face implements Cloneable{ 16 @Override 17 protected Object clone() throws CloneNotSupportedException { 18 return super.clone(); 19 } 20 } 21 22 ============ 23 public static void main(String[] args) throws CloneNotSupportedException { 24 25 Body body = new Body(new Head(new Face())); 26 27 Body body1 = (Body) body.clone(); 28 29 System.out.println("body == body1 : " + (body == body1) ); 30 31 System.out.println("body.head == body1.head : " + (body.head == body1.head)); 32 33 System.out.println("body.head.face == body1.head.face : " + (body.head.face == body1.head.face)); 34 35 36 } 37 38 ============ 39 输出结果: 40 body == body1 : false 41 body.head == body1.head : false 42 body.head.face == body1.head.face : false