JAVA高级工程师-面试经历(含面试问题及解答)

经过了几个公司的面试,谈谈我这次找工作的面试经历。

工作快五年了,所以给自己定位是找一份Java高级工程师的工作。

由于疫情原因基本都是先电话面试(PS:更多的原因是me在上海,想要找重庆或成都的工作)。

再废话一句,今年真的能不换工作就不换工作吧,因为基本都会被面试官压制。

Java高级工程师的岗位对应聘者的任何一门技术都必须要求知晓其原理,并能够针对性的提出相应的改进方案。经过几轮面试主要总结了以下常见的问题:

一、锁

乐观锁、悲观锁(共享锁(其它名词有读锁、S锁)、排他锁(其它名词有写锁、X锁))的理解和应用,产生死锁的原因有哪些,如何避免死锁。需要了解synchronized与reetrentlock实现原理及区别。

(1)synchronized

实现原理可详见这篇博文《深入理解Java并发之synchronized实现原理》

(2)reetrentlock

本质上采用的是自循锁实现的,在高并发场景下消耗CPU资源较大,性能不如synchronized。

二、事务

(1)Mysql事务隔离级别(read uncommit、read commit、repeatable read、serializable),MySQL默认事务隔离级别repeatable read,见下说明:

①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(串行化读)

最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

(2)Spring事务传播机制(support、not support、require、require new、mandatory、never、nested)见下说明:

①ROPAGATION_REQUIRED

支持当前事务,假设当前没有事务。就新建一个事务。

②PROPAGATION_SUPPORTS

支持当前事务,假设当前没有事务,就以非事务方式运行。

③PROPAGATION_MANDATORY

支持当前事务,假设当前没有事务,就抛出异常。 

④PROPAGATION_REQUIRES_NEW

新建事务,假设当前存在事务。把当前事务挂起。

⑤PROPAGATION_NOT_SUPPORTED

以非事务方式运行操作。假设当前存在事务,就把当前事务挂起。

⑥PROPAGATION_NEVER

以非事务方式运行,假设当前存在事务,则抛出异常。

⑦PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

三、JVM

JVM是作为一个高级JAVA程序员必须要掌握的一个知识点,主要集中问JVM运行时内存划分、GC、性能调优。

(1)JVM运行时内存划分

PC程序计数器(存储下一条即将执行的指令,属于线程私有),JVM虚拟机栈(对象的引用、基本常量、未能逃逸出的对象,属于线程私有),本地方法栈(属于线程私有),堆(存储对象实例,属于线程共享区域),方法区(运行时常量,静态变量,加载的类信息,JIT编译信息,本质上属于堆里面的逻辑分区,属于线程共享区域)。

(2)GC

JVM内存模型,自Java7后内存模型分为新生代、老年代、元数据空间,常用的GC方法标记-清除算法、标记-压缩算法、复制算法、分代收集算法,常见的收集器有Serial、ParNew、CMS、G1(目前最常用的服务端收集器,不产生碎片,可预测时间停顿),详细了解GC请点击。

(3)性能调优

常用的调优方法,如何分析堆内存,栈内存,如何查看程序运行时的线程状态。当出现栈溢出、堆溢出、OOM、内存溢出时如何解决,哪些原因会导致这些问题。

四、Spring Cloud

针对Spring Cloud主要是对Spring 全家桶的理解,提供了哪些解决方案。

(1)Spring Cloud Config

配置管理工具包,让你可以把配置放到远程服务器,集中化管理集群配置,目前支持本地存储、Git以及Subversion。

(2)Spring Cloud Bus

事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Spring Cloud Config联合实现热部署。

(3)Eureka

云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。

(4)Hystrix

熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。

(5)Zuul

Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。

(6)Spring Cloud Sleuth

日志收集工具包,封装了Dapper和log-based追踪以及Zipkin和HTrace操作,为SpringCloud应用实现了一种分布式追踪解决方案。

(7)Spring Cloud Data Flow

大数据操作工具,作为Spring XD的替代产品,它是一个混合计算模型,结合了流数据与批量数据的处理方式。

(8)Spring Cloud Security

基于spring security的安全工具包,为你的应用程序添加安全控制。

(9)Spring Cloud Zookeeper

操作Zookeeper的工具包,用于使用zookeeper方式的服务发现和配置管理。

(10)Spring Cloud Stream

数据流操作开发包,封装了与Redis,Rabbit、Kafka等发送接收消息。

(11)Ribbon

提供云端负载均衡,有多种负载均衡策略可供选择,可配合服务发现和断路器使用。

(12)Turbine

Turbine是聚合服务器发送事件流数据的一个工具,用来监控集群下hystrix的metrics情况。

(13)Feign

Feign是一种声明式、模板化的HTTP客户端。

(14)Spring Cloud Task

提供云端计划任务管理、任务调度。

(15)Spring Cloud Starters

Spring Boot式的启动项目,为Spring Cloud提供开箱即用的依赖管理。

点击查看Spring Cloud中文文档

五、Spring Boot

需要了解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)静态代理

通过手动创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。见下水果店代理果农卖水果的案例。

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 - 果农卖水果:桃子

(2)动态代理

代理类在程序运行时创建的代理方式被成为动态代理,继续沿用上面案例,创建动态代理:

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

 

本文到此暂告一段落啦,后续如遇到其它问题,将持续更新。

你可能感兴趣的:(java,面试,面试,java)