蚂蚁金服面试及笔试(附自己的答案)

本次进行了一轮笔试,然后进行一面,预计一面挂了,自我感觉答得很凌乱,虽然问的基本都会。面试除了心态,还有就是充分的准备,后续多多总结关注细节吧。以下是后续整理的简要面试提问:

1.自我介绍

1.自我简介:毕业于西电,就职于xx厂,负责Java后台研发
2.项目简介及技术选型:项目,出发点及功能,技术选型:框架用的是spring boot,数据库是mysql,持久层框架是mybatis,也在之前的项目用过jpa,jpa是一种规范,mybatis是一种具体实现,无所谓孰优孰劣,看具体场景。客户端和服务端消息交互的语音交互部分,为了节省效率,选择的是全双工的websocket,其他交互则是http的rest接口为主。引进了第三方的消息交互组件activeMq,是为了解耦和异步(第三种优点是削峰,不过本项目主要是为了解耦和异步)。为了解决海量数据的存储问题,引入了fdfs组件。本产品在部署上可分为单机和集群部署,在客户的使用需求比较小的情况下,则使用单机模式。当客户群体比较大则服务考虑集群模式,集群模式引入了sping cloud里的Eureka和config,配置文件托管在服务器上的git仓库。在内部多文件处理上为了并发节省效率,使用了多线程,使用的是定长线程池。为了便于全文检索,引入了Elasticsearch。为了对应的数据收集分析需求,还在对应的逻辑处进行埋点,以便后续运维人员对产品的各项指标把控。在功能开发完成后,配合测试人员,进行版本的冒烟、性能测和稳定性测试。版本发往现场使用后,对现场运维人员反馈的问题,进行跟踪和排查解决。

2.多线程,关键字threadlocal

1.多线程使用场景: cpu利用效率更好以及响应更快。我在项目中使用的是定长线程池,因为线程是稀缺资源,如果被无限创建,则不仅消耗系统资源,还有害于系统稳定性。利用线程池,则可对线程进行相应管理,提高响应速度以及降低资源消耗。
2.threadlocal:线程局部变量,即threadlocal为每个线程创建个变量副本,那么每个线程便访问自己的变量副本。线程变量副本存储位置是在线程的ThreadLocalMap里。它的主要应用场景是为了解决数据库连接和session管理的。

3.synchronized原理

1.synchronized是可修饰代码块、方法、对象以及类的悲观锁。
2.原理:在jdk1.6之前是利用了monitor对象来完成,每个对象都有个监视器锁,当Monitor被占用时就会处于锁定状态,线程执行monitorenter时会尝试进入监视器,如果monitor的进入数为0.则该线程进入monitor,且将进入数设置为1,获取有monitor的线程重复进入,则相应加一减一。其他线程尝试进入时,就会阻塞,直到monitor为0,则可获取monitor的进入权。在jdk1.6及之后,为了减少获得锁和释放锁带来的性能损耗,提高使用效率,引入了偏向锁和轻量级锁。锁可从偏向锁升级到轻量级锁,再从轻量级锁,升级到重量级锁,这个锁的升级是单向过程。
轻量级锁,引入的本意是为了,在没有多线程竞争的条件下,减少重量级锁所带来的损耗。轻量级锁的使用场景是:多个线程交替执行同步块的情况。如果在同一时间有多个线程同时访问同步块,则轻量级锁就会膨胀为重量级锁。
偏向锁,引入的本意是为了减少不必要的轻量级锁执行路径,因为轻量级锁的获取与释放依赖多次CAS原子指令。而偏向锁,只在置换ThreadId时依赖一次CAS原子指令。相对于轻量级锁适用于多线程交替执行同步块的使用场景。偏向锁的使用场景则是,单线程访问同步块时进一步提高性能。
更深入了解,可移步1.Java并发编程:Synchronized及其实现原理
2.Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)

4.volitile作用

1.保证变量对所有线程的可见性。对变量的修改,会实时同步到主内存,变量的读,也是从主内存去读。
2.禁止被修饰 的前后代码指令重排序优化。
详情可移步Java并发编程:volatile关键字解析

5.JVM内存构造,垃圾回收机制。常量所在位置,不同版本jdk有什么区别;full gc的垃圾回收算法,及原理,标记-清理算法如何标记的,如何判断是否被引用。

内存构造:堆、虚拟机栈、方法区、本地方法栈以及程序计数器。
JVM垃圾回收机制:标记清理算法、标记压缩算法以及分代回收算法。
常量所在位置,根据jdk版本不同而不同,jdk1.7之前是在方法区的常量池,jdk1.7被移到堆的永久代里。jdk1.8时还是在堆里,不过永久代被元空间取而代之,常量池位置并没变化。
full gc 的垃圾回收算法:标记清理算法和标记压缩算法。标记清理,通过遍历所有的Gc Roots,然后将所有Gc Roots可达对象标记为存活对象,将没有标记的对象清除掉。

6.正则表达式使用(这个问题感觉就是面试官根据简历随口问的。当时有点懵,项目用过几次,主要用做字符串匹配)

正则表达式的三个使用场景:
1.匹配
2.切割
3.替换

7.spring boot 和spring框架的区别,spring boot用的时候踩到的坑

spring boot 是在spring的基础上,搭建的全新的微服务框架,目的是简化spring的搭建和开发过程,其优点如下:
1.可快速构建项目
2.对主流开发框架的无配置继承
3.项目可独立运行,无需依赖外部servlet容器
4.提供运行时的应用监控
5.无需配置复杂的 xml
缺点:
1.资料较少且不够深入。
2.集成度太高,不太容易了解底层。

8.hashmap的底层实现原理

这是个老生常谈的问题,基本十次面试有九次问。还是得好好缕清楚。
底层实现原理:数组+链表,其主干是Entry数组,包含key-value键值对。数组是hashmap的主体,链表是为了解决哈希冲突。对于查找和添加而言,若定位到位置不含链表,则查找很快,仅需一次寻址即可。如果含有链表,则遍历链表,存在即覆盖,否则新增。对于查找而言,则需要遍历链表,依次通过对key对象的equals方法进行逐一对比查找。就性能考虑,链表越少越好。
关于不同版本的hashmap差异:在jdk1.7及之前,是数组+链表。在jdk8,也是数组+链表,不过对链表处进行了优化,若链表长度大于8,则将链表转为红黑树。
想了解更多的话可移步HashMap实现原理及源码分析

9.jstack、jmap及jstat

1.jstack查看服务线程,常用用法:jstack pid
2.jmap查看服务内存,常用用法:jmap pid 或jmap -histo pid 以及把内存dump到二进制文件中,用mat分析。(命令格式:jmap -dump:format=b,file=heap.bin pid)
3.jstat 实时监控资源和性能,常用用法;jstat -gc pid ,统计gc时的heap信息
更多详情可移步tack(查看线程)、jmap(查看内存)和jstat(性能分析)命令

ps:非面试部分及附加笔试题和自己的笔试答案,仅供参考,如有不同见解,欢迎沟通交流

在线笔试部分,两道题,限时40分钟解答。

题目一:
现需做一个部门管理功能,如公司有多个大部门A、B、C…,每个部门下面有多个子部门,子部门还有子部门以此类推,如部门A下面有部门AB、AC、AD,部门AB下面有AE、AF,部门AC下面有AG、AH、AI,部门AE下面有AJ等,每个部门的子部门个数和部门的层数均为无限,每个部门的父部门要么没有要么是一个。 针对上述需求完成如下题目:
1,设计表结构存储上述部门关系;
2,定义领域模型Department类;
3,实现删除部门方法:void delete(Integer departmentId),要求删除指定部门的同时删除其下面的所有层级的子部门,如删除部门AB的同时要删除它的子部门AE、AF和AE的子部门AJ;

1.表结构(departmentId,parentDepartmentId)

2.   public class  Department{
         
         /**
	      * 部门id
	      */
         private Integer departmentId;
         
          /**
	      * 父部门id
	      */
         private Integer parentDepartmentId;
         
         public Integer getDepartmentId() {
			return departmentId;
		}

		public void setDepartmentId(Integer departmentId) {
			this.departmentId = departmentId;
		}

		public Integer getParentDepartmentId() {
			return parentDepartmentId;
		}

		public void setParentDepartmentId(Integer parentDepartmentId) {
			this.parentDepartmentId = parentDepartmentId;
		}         
}

3. public class DepartmentDao{
       
         void delete(Integer departmentId){
             // 判断是否有子部门
             if(containsDepartmentId(departmentId)){
                 //查询子部门id
                 int sunDepartmentId = selectSunDepartmentId(departmentId);
                 // 递归删除子部门
                 delete(sunDepartmentId);
             }
             // 删除本部门数据
             deleteDepartment(departmentId);
    }
}

题目二:
设计一个栈,这个栈除了支持普通的push, pop操作之外,还提供一个函数min(), 以返回栈中元素的最小值,要求min()的时间复杂性为O(1).
提示:可以用现有的任何数据结构,如队列、队、另外的栈,等等。 优化:要求实现push(), pop()的时间复杂性也为O(1)

class MinStack
{
    
    private Stack dataStack;
    private Stack minStack;
    
    public MinStack(){
        this.dataStack = new Stack();
        this.minStack = new Stack();
    }
    
    public void push(int x)
    {
        if(this.minStack.isEmpty()){
            this.minStack.push(x);
        }else if(x <= this.min()){
            this.minStack.push(x);
        }
        this.dataStack.push(x);
    }
    public int pop()
    {
        if(this.dataStack.isEmpty()){
           throw new RuntimeException("stack is Empty");
        }
        int result = this.dataStack.pop();
        if(result == this.min()){
            this.minStack.pop();
        }
        return result;
    }

    public int top()
    {
        if(this.dataStack.isEmpty()){
           throw new RuntimeException("stack is Empty");
        }      
        return this.dataStack.peek();
    }

    public int min()
    {
        if(this.minStack.isEmpty()){
           throw new RuntimeException("stack is Empty");
        }  
        return this.minStack.peek();
    }
}

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