经过了几个公司的面试,谈谈我这次找工作的面试经历。
工作快五年了,所以给自己定位是找一份Java高级工程师的工作。
由于疫情原因基本都是先电话面试(PS:更多的原因是me在上海,想要找重庆或成都的工作)。
再废话一句,今年真的能不换工作就不换工作吧,因为基本都会被面试官压制。
Java高级工程师的岗位对应聘者的任何一门技术都必须要求知晓其原理,并能够针对性的提出相应的改进方案。经过几轮面试主要总结了以下常见的问题:
乐观锁、悲观锁(共享锁(其它名词有读锁、S锁)、排他锁(其它名词有写锁、X锁))的理解和应用,产生死锁的原因有哪些,如何避免死锁。需要了解synchronized与reetrentlock实现原理及区别。
实现原理可详见这篇博文《深入理解Java并发之synchronized实现原理》
本质上采用的是自循锁实现的,在高并发场景下消耗CPU资源较大,性能不如synchronized。
①READ_UNCOMMIT(读未提交)
A事务已执行,但未提交;B事务查询到A事务的更新后数据;A事务回滚,出现脏数据,存在脏读、不可重复度、幻读问题;
②READ_COMMIT(读已提交)
在同一个事务中,相同的数据前后获取的信息不一致,存在不可重复读、幻读问题;
③REPEATABLE_READ(可重复读)
它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行,理论上也存在幻读问题,MySQL InnoDB默认用了REPEATABLE READ,但可以使用next-key locks解决幻读问题,例如:
select * from user where age>10 lock in share mode;
④SERIALIZABLE(串行化读)
最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
①ROPAGATION_REQUIRED
支持当前事务,假设当前没有事务。就新建一个事务。
②PROPAGATION_SUPPORTS
支持当前事务,假设当前没有事务,就以非事务方式运行。
③PROPAGATION_MANDATORY
支持当前事务,假设当前没有事务,就抛出异常。
④PROPAGATION_REQUIRES_NEW
新建事务,假设当前存在事务。把当前事务挂起。
⑤PROPAGATION_NOT_SUPPORTED
以非事务方式运行操作。假设当前存在事务,就把当前事务挂起。
⑥PROPAGATION_NEVER
以非事务方式运行,假设当前存在事务,则抛出异常。
⑦PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
JVM是作为一个高级JAVA程序员必须要掌握的一个知识点,主要集中问JVM运行时内存划分、GC、性能调优。
PC程序计数器(存储下一条即将执行的指令,属于线程私有),JVM虚拟机栈(对象的引用、基本常量、未能逃逸出的对象,属于线程私有),本地方法栈(属于线程私有),堆(存储对象实例,属于线程共享区域),方法区(运行时常量,静态变量,加载的类信息,JIT编译信息,本质上属于堆里面的逻辑分区,属于线程共享区域)。
JVM内存模型,自Java7后内存模型分为新生代、老年代、元数据空间,常用的GC方法标记-清除算法、标记-压缩算法、复制算法、分代收集算法,常见的收集器有Serial、ParNew、CMS、G1(目前最常用的服务端收集器,不产生碎片,可预测时间停顿),详细了解GC请点击。
常用的调优方法,如何分析堆内存,栈内存,如何查看程序运行时的线程状态。当出现栈溢出、堆溢出、OOM、内存溢出时如何解决,哪些原因会导致这些问题。
针对Spring Cloud主要是对Spring 全家桶的理解,提供了哪些解决方案。
配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion。
事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Spring Cloud Config联合实现热部署。
云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。
熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。
Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。
日志收集工具包,封装了Dapper和log-based追踪以及Zipkin和HTrace操作,为SpringCloud应用实现了一种分布式追踪解决方案。
大数据操作工具,作为Spring XD的替代产品,它是一个混合计算模型,结合了流数据与批量数据的处理方式。
基于spring security的安全工具包,为你的应用程序添加安全控制。
操作Zookeeper的工具包,用于使用zookeeper方式的服务发现和配置管理。
数据流操作开发包,封装了与Redis,Rabbit、Kafka等发送接收消息。
提供云端负载均衡,有多种负载均衡策略可供选择,可配合服务发现和断路器使用。
Turbine是聚合服务器发送事件流数据的一个工具,用来监控集群下hystrix的metrics情况。
Feign是一种声明式、模板化的HTTP客户端。
提供云端计划任务管理、任务调度。
Spring Boot式的启动项目,为Spring Cloud提供开箱即用的依赖管理。
点击查看Spring Cloud中文文档
需要了解Spring Boot核心功能,与传统Spring MVC的区别,如何构建自己的Spring Boot应用,核心依赖,如何自定义一个Spring Boot Starter。
点击查看Spring Boot中文文档
需要了解java.util.concurrent.Executors类中常用的几种创建线程池的方式,创建线程池的几个核心参数,task的调度执行过程。
//五种常用的创建线程池的方式
Executors.newFixedThreadPool(3);
Executors.newSingleThreadExecutor();
Executors.newScheduledThreadPool(3);
Executors.newWorkStealingPool(3);
Executors.newCachedThreadPool();
//线程池的核心参数
int corePoolSize = 1;//核心线程数
int maximumPoolSize = 5;//最大线程数
long keepAliveTime = 1;//线程保活时间
TimeUnit unit = TimeUnit.SECONDS;//时间单位
BlockingQueue workQueue = new LinkedBlockingQueue();//task队列
new ThreadPoolExecutor(corePoolSize , maximumPoolSize ,keepAliveTime, unit,workQueue)
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
通过手动创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。见下水果店代理果农卖水果的案例。
1)创建销售接口(Sale):
/**
* @author frank
* @date 2020年6月21日
*/
public interface Sale {
public void sale(String fruit);
}
2)果农实现销售接口:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author frank
* @date 2020年6月21日
*/
public class FruitGrower implements Sale {
private static final Logger log = LoggerFactory.getLogger(FruitGrower.class);
@Override
public void sale(String fruit) {
log.info("果农卖水果:{}",fruit);
}
}
3) 水果店实现销售接口:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author frank
* @date 2020年6月21日
*/
public class FruitShop implements Sale {
private static final Logger log = LoggerFactory.getLogger(FruitShop.class);
private FruitGrower fruitGrower;
/**
* @param fruitGrower
*/
public FruitShop(FruitGrower fruitGrower) {
this.fruitGrower = fruitGrower;
}
@Override
public void sale(String fruit) {
log.info("商店收到到顾客买水果订单,代理果农卖水果:{}",fruit);
fruitGrower.sale(fruit);
}
}
4)测试
/**
* @author frank
* @date 2020年6月16日
*/
public class Test {
public static void main(String[] args) {
FruitGrower grower = new FruitGrower();
Sale sale = new FruitShop(grower);
sale.sale("桃子");
}
}
输出结果:
10:48:09.303 [main] INFO com.ace.study.FruitShop - 商店收到到顾客买水果订单,代理果农卖水果:桃子
10:48:09.306 [main] INFO com.ace.study.FruitGrower - 果农卖水果:桃子
代理类在程序运行时创建的代理方式被成为动态代理,继续沿用上面案例,创建动态代理:
1)创建动态代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author frank
* @date 2020年6月21日
*/
public class DynamicProxy implements InvocationHandler {
private static final Logger log = LoggerFactory.getLogger(DynamicProxy.class);
private Object object;
/**
* @param object
*/
public DynamicProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("执行方法前:{}",method.getName());
Object result = method.invoke(object, args);
log.info("执行方法后:{}",method.getName());
return result;
}
}
2) 测试
import java.lang.reflect.Proxy;
/**
* @author frank
* @date 2020年6月16日
*/
public class Test {
public static void main(String[] args) {
FruitGrower grower = new FruitGrower();
DynamicProxy proxyHandle = new DynamicProxy(grower);
Sale sale = (Sale) Proxy.newProxyInstance(grower.getClass().getClassLoader(), grower.getClass().getInterfaces(),
proxyHandle);
sale.sale("苹果");
}
}
输出结果:
13:58:51.940 [main] INFO com.ace.study.DynamicProxy - 执行方法前:sale
13:58:51.943 [main] INFO com.ace.study.FruitGrower - 果农卖水果:苹果
13:58:51.943 [main] INFO com.ace.study.DynamicProxy - 执行方法后:sale
本文到此暂告一段落啦,后续如遇到其它问题,将持续更新。