互联网大厂面试:Java核心、并发、框架与中间件知识大考验
在互联网大厂的一间明亮的面试室内,严肃的面试官坐在桌前,对面坐着略显紧张的求职者王铁牛。一场对Java核心知识、并发编程、各类框架和中间件的考验即将开始。
第一轮提问 面试官:首先问你几个基础的Java核心知识问题。Java中有哪些基本数据类型? 王铁牛:Java的基本数据类型有byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那说说String类为什么是不可变的? 王铁牛:因为String类被final修饰,它的成员变量value数组也是final的,所以一旦创建就不能改变。 面试官:很好。那讲讲HashMap和HashTable的区别。 王铁牛:HashMap是非线程安全的,HashTable是线程安全的;HashMap允许键和值为null,HashTable不允许。
第二轮提问 面试官:接下来聊聊JUC和多线程的内容。什么是线程安全? 王铁牛:线程安全就是多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。 面试官:不错。那讲一下ReentrantLock和synchronized的区别。 王铁牛:呃……它们好像都能实现同步,区别嘛,我有点不太确定。 面试官:再问你,线程池有哪些重要的参数? 王铁牛:嗯……有核心线程数,还有……其他的我记不太清了。
第三轮提问 面试官:现在来谈谈框架相关的问题。Spring的IOC和AOP是什么? 王铁牛:IOC是控制反转,就是把对象的创建和依赖关系的管理交给Spring容器;AOP是面向切面编程,可以在不修改原有代码的情况下增强功能。 面试官:回答得还行。那Spring Boot和Spring有什么区别? 王铁牛:Spring Boot好像是简化了Spring的开发,但是具体的区别我也说不太清楚。 面试官:最后问你,MyBatis的一级缓存和二级缓存是怎么回事? 王铁牛:这个……我只听说过有缓存,具体不太明白。
面试官:今天的面试就到这里,你先回家等通知吧。我们会综合评估你的表现后再和你联系。
问题答案
- Java基本数据类型:
- Java有8种基本数据类型,分为4类:
- 整数类型:byte(1字节)、short(2字节)、int(4字节)、long(8字节)。
- 浮点类型:float(4字节)、double(8字节)。
- 字符类型:char(2字节)。
- 布尔类型:boolean(理论上1位,但实际实现中通常占1字节)。
- Java有8种基本数据类型,分为4类:
- String类为什么是不可变的:
- String类被final修饰,这意味着它不能被继承。
- String类内部维护了一个被final修饰的char数组
value,用于存储字符串的字符序列。final修饰的数组意味着数组的引用不能被改变,但数组元素的值理论上可以改变。不过,String类没有提供任何方法来修改这个数组的元素,所以一旦创建了String对象,其内容就不能被修改。 - 不可变的好处包括:线程安全、可以作为HashMap的键、提高性能(因为可以缓存hash值)等。
- HashMap和HashTable的区别:
- 线程安全:HashMap是非线程安全的,在多线程环境下可能会出现数据不一致的问题;HashTable是线程安全的,它的大部分方法都使用了synchronized关键字进行同步。
- 键和值是否允许为null:HashMap允许键和值为null,不过键只能有一个null;HashTable不允许键和值为null,否则会抛出NullPointerException。
- 性能:由于HashTable是线程安全的,所以在单线程环境下,HashMap的性能通常比HashTable高。
- 线程安全:
- 线程安全是指在多线程环境下,一个对象或方法在被多个线程同时访问时,能够保证数据的一致性和正确性,不会出现数据竞争、脏读、幻读等问题。实现线程安全的方式有很多,比如使用synchronized关键字、ReentrantLock、原子类等。
- ReentrantLock和synchronized的区别:
- 语法层面:synchronized是Java的关键字,是内置的语言特性;ReentrantLock是一个类,需要手动创建和使用。
- 锁的获取和释放:synchronized会自动获取和释放锁,当代码块或方法执行完毕,锁会自动释放;ReentrantLock需要手动调用
lock()方法获取锁,调用unlock()方法释放锁,通常需要在finally块中释放锁,以确保锁一定会被释放。 - 锁的特性:ReentrantLock提供了更多的特性,比如可中断锁(
lockInterruptibly()方法)、公平锁(通过构造函数指定是否为公平锁)、尝试获取锁(tryLock()方法)等,而synchronized是不可中断的,并且是非公平锁。 - 性能:在JDK 1.6之前,synchronized的性能较差,ReentrantLock的性能更好;但在JDK 1.6之后,JVM对synchronized进行了优化,两者的性能差距不大。
- 线程池的重要参数:
- corePoolSize:核心线程数,线程池在创建后,默认情况下线程数量为0,当有任务提交时,会创建新的线程来执行任务,直到线程数量达到核心线程数。
- maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。当任务队列满了,并且线程数量达到核心线程数后,会继续创建新的线程,直到达到最大线程数。
- keepAliveTime:线程空闲时间,当线程池中的线程数量超过核心线程数时,空闲的线程在经过keepAliveTime时间后会被销毁。
- unit:keepAliveTime的时间单位,如TimeUnit.SECONDS、TimeUnit.MILLISECONDS等。
- workQueue:任务队列,用于存储等待执行的任务。常见的任务队列有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue等。
- threadFactory:线程工厂,用于创建线程。可以自定义线程工厂来设置线程的名称、优先级等属性。
- handler:拒绝策略,当任务队列满了,并且线程数量达到最大线程数时,新提交的任务会被拒绝,此时会调用拒绝策略来处理这些任务。常见的拒绝策略有AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)等。
- Spring的IOC和AOP:
- IOC(控制反转):
- 传统的对象创建和依赖关系管理是由程序员在代码中手动完成的,而IOC是将对象的创建和依赖关系的管理交给Spring容器。Spring容器会根据配置文件或注解来创建和管理对象,并将对象之间的依赖关系注入到相应的对象中。
- IOC的实现方式主要有两种:依赖注入(DI)和依赖查找(DL),其中依赖注入是最常用的方式。依赖注入又分为构造函数注入、Setter方法注入和接口注入等。
- AOP(面向切面编程):
- AOP是一种编程范式,它允许在不修改原有代码的情况下,对程序的某些特定部分进行增强。在Spring中,AOP主要用于实现日志记录、事务管理、权限验证等功能。
- AOP的核心概念包括切面(Aspect)、连接点(Join Point)、切点(Pointcut)、通知(Advice)和引入(Introduction)等。切面是包含了通知和切点的模块,连接点是程序执行过程中的某个点,切点是对连接点的筛选,通知是在切点处执行的代码,引入是为现有类添加新的方法或属性。
- IOC(控制反转):
- Spring Boot和Spring的区别:
- 开发效率:Spring Boot是Spring的扩展,它通过自动配置和起步依赖等特性,简化了Spring应用的开发过程。使用Spring Boot可以快速搭建一个Spring应用,减少了大量的配置文件和样板代码,提高了开发效率。
- 配置方式:Spring需要手动配置大量的XML文件或Java配置类来完成各种功能的配置;而Spring Boot采用了约定大于配置的原则,通过自动配置机制,根据项目中引入的依赖自动进行配置,只需要少量的配置就可以完成大部分的功能。
- 嵌入式服务器:Spring Boot内置了嵌入式服务器,如Tomcat、Jetty等,不需要额外部署服务器就可以直接运行应用;而Spring应用通常需要部署到外部的服务器中运行。
- MyBatis的一级缓存和二级缓存:
- 一级缓存:
- 一级缓存是SqlSession级别的缓存,每个SqlSession都有自己的一级缓存。当同一个SqlSession执行相同的查询语句时,第一次查询会将结果存储在一级缓存中,后续相同的查询会直接从缓存中获取结果,而不需要再次访问数据库。
- 一级缓存的生命周期与SqlSession相同,当SqlSession关闭或清空缓存时,一级缓存中的数据会被清除。
- 二级缓存:
- 二级缓存是Mapper级别的缓存,多个SqlSession可以共享同一个Mapper的二级缓存。当开启二级缓存后,不同的SqlSession执行相同Mapper的查询语句时,会先从二级缓存中查找结果,如果缓存中没有,则查询数据库,并将结果存储在二级缓存中。
- 二级缓存的生命周期比一级缓存长,需要手动配置开启,并且可以通过实现Cache接口来自定义缓存策略。
- 一级缓存: