《互联网大厂Java面试大揭秘:核心知识与热门框架全考验》

26 阅读10分钟

面试官:第一轮提问开始。首先,讲讲Java中的多线程如何实现线程同步?

王铁牛:嗯……可以用synchronized关键字,它能保证在同一时刻只有一个线程访问被它修饰的代码块或方法。

面试官:不错,回答正确。那再问你,线程池有哪些参数,分别有什么作用?

王铁牛:线程池有corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue和threadFactory这些参数。corePoolSize是核心线程数,maximumPoolSize是最大线程数,keepAliveTime是线程池线程数量超过corePoolSize时,多余的空闲线程的存活时间,unit是keepAliveTime的时间单位,workQueue是任务队列,threadFactory是线程工厂。

面试官:很好,看来你对线程池有一定了解。最后一个问题,在多线程环境下,如何保证HashMap的线程安全?

王铁牛:呃……这个嘛,好像可以用ConcurrentHashMap,它是线程安全的哈希表。

面试官:好,第一轮提问结束。接下来是第二轮。说说JVM的内存模型。

王铁牛:JVM内存模型包括堆、栈、方法区、程序计数器、本地方法栈。堆是存放对象实例的地方,栈是存放局部变量和方法调用的地方,方法区存放类信息、常量、静态变量等,程序计数器记录当前线程执行的字节码指令地址,本地方法栈用于执行本地方法。

面试官:回答得还行。那Spring框架的核心特性有哪些?

王铁牛:Spring框架的核心特性有依赖注入、面向切面编程、IoC容器、事务管理。

面试官:嗯,有一定了解。再问你,Spring Boot的自动配置原理是什么?

王铁牛:这个……不太清楚,好像是根据类路径下的依赖来自动配置一些东西。

面试官:第二轮提问完毕。现在进入第三轮。MyBatis的#{}和${}有什么区别?

王铁牛:#{}会将传入的数据当成一个字符串,会对传入的数据加引号;${}会直接将传入的数据拼接到SQL中,不会加引号。

面试官:那Dubbo的服务注册与发现是怎么实现的?

王铁牛:不太明白,好像是通过注册中心来实现的。

面试官:最后一个问题,Redis的数据结构有哪些,分别适用于什么场景?

王铁牛:这个……我知道有字符串、哈希、列表、集合、有序集合。但具体适用于什么场景,我不太确定。

面试官:好,三轮提问都结束了。回去等通知吧。

答案:

  1. Java多线程实现线程同步
    • synchronized关键字:它是Java中用于实现线程同步的关键字。当一个线程访问被synchronized修饰的代码块或方法时,它会首先检查对象的锁。如果对象的锁未被其他线程持有,该线程将获得锁并执行代码块或方法。在同一时刻,只有一个线程能够获得该对象的锁,从而保证了同一时刻只有一个线程能够访问被synchronized修饰的代码块或方法,实现了线程同步。例如,在一个银行账户类中,对账户余额的修改操作可以用synchronized修饰,防止多个线程同时修改导致数据不一致。
  2. 线程池参数及作用
    • corePoolSize(核心线程数):线程池创建时初始化的线程数量。当提交的任务数小于corePoolSize时,线程池会创建新的线程来执行任务。
    • maximumPoolSize(最大线程数):线程池允许存在的最大线程数量。当提交的任务数大于corePoolSize且任务队列已满时,线程池会创建新的线程来执行任务,直到线程数量达到maximumPoolSize。
    • keepAliveTime(存活时间):当线程池中的线程数量超过corePoolSize时,多余的空闲线程在经过keepAliveTime指定的时间后会被销毁。
    • unit(时间单位):keepAliveTime的时间单位,比如TimeUnit.SECONDS表示秒。
    • workQueue(任务队列):用于存放提交到线程池但尚未被执行的任务。当提交的任务数大于corePoolSize时,任务会被放入workQueue中。
    • threadFactory(线程工厂):用于创建线程池中的线程,通过它可以定制线程的一些属性,比如线程名、线程优先级等。
  3. 多线程环境下保证HashMap线程安全
    • 使用ConcurrentHashMap:ConcurrentHashMap是线程安全的哈希表。它采用了分段锁的机制,将整个哈希表分成多个段,每个段都有自己独立的锁。当一个线程访问ConcurrentHashMap时,它只需要获取自己要操作的段的锁,而不是整个哈希表的锁,这样大大提高了并发性能。例如,在一个多线程的电商系统中,多个线程可以同时对ConcurrentHashMap进行读写操作,而不会出现数据竞争问题。
  4. JVM内存模型
    • 堆:是JVM中最大的一块内存区域,用于存放对象实例。所有的对象实例都在堆中创建。堆可以分为新生代、老年代和永久代(在JDK 8之后永久代被元空间取代)。新生代主要存放新创建的对象,老年代存放经过多次垃圾回收后仍然存活的对象,永久代(元空间)存放类信息、常量、静态变量等。
    • 栈:每个线程都有自己独立的栈。栈中主要存放局部变量和方法调用的上下文信息。当一个方法被调用时,会在栈中创建一个栈帧,栈帧中包含局部变量表、操作数栈、动态链接、方法出口等信息。方法执行完毕后,对应的栈帧会被销毁。
    • 方法区:存放类信息、常量、静态变量等数据。它是各个线程共享的内存区域。在JDK 8之后,永久代被元空间取代,元空间使用本地内存,而不是像永久代那样使用JVM的堆内存。
    • 程序计数器:是一块较小的内存区域,它记录了当前线程执行的字节码指令地址。每个线程都有自己独立的程序计数器,它是线程私有的。
    • 本地方法栈:用于执行本地方法(用C、C++等语言编写的方法)。它与Java栈的结构类似,也是每个线程都有自己独立的本地方法栈。
  5. Spring框架的核心特性
    • 依赖注入(Dependency Injection):通过将对象之间的依赖关系由程序主动创建改为由容器来注入,降低了对象之间的耦合度。例如,一个用户服务类依赖于数据库连接类,在Spring中可以通过配置将数据库连接类注入到用户服务类中,而不是在用户服务类中直接创建数据库连接对象。
    • 面向切面编程(Aspect - Oriented Programming,AOP):允许将一些横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,以一种更模块化的方式进行处理。比如,在多个业务方法中都需要进行日志记录,使用AOP可以将日志记录逻辑统一处理,而不需要在每个业务方法中都重复编写日志记录代码。
    • IoC容器(Inversion of Control Container):即控制反转容器,它负责创建、配置和管理对象之间的依赖关系。开发人员只需要描述对象之间的依赖关系,由IoC容器来完成对象的创建和组装。
    • 事务管理:Spring提供了统一的事务管理机制,方便开发人员管理数据库事务。可以通过声明式事务或编程式事务来实现事务控制。例如,在一个电商订单处理系统中,通过Spring的事务管理可以确保订单的创建、商品库存的更新等操作要么全部成功,要么全部失败,保证数据的一致性。
  6. Spring Boot自动配置原理
    • Spring Boot的自动配置是基于条件注解实现的。它会根据类路径下的依赖来自动配置一些组件和功能。当Spring Boot应用启动时,会遍历所有的自动配置类(这些自动配置类通常在META - INF/spring.factories文件中定义)。每个自动配置类都通过@Conditional注解来判断是否满足配置的条件。
    • 例如,如果类路径下存在某个数据库驱动的依赖,那么对应的数据库自动配置类就会生效,它会自动配置数据源、事务管理器等相关组件。这些自动配置类会根据一些默认的属性值和条件来进行配置,开发人员也可以通过在application.properties或application.yml文件中自定义属性来覆盖默认配置,从而实现更个性化的应用配置。
  7. MyBatis的#{}和${}区别
    • #{}:它会将传入的数据当成一个字符串,会对传入的数据加引号。这样可以防止SQL注入攻击。例如,当传入一个参数id时,MyBatis会将其作为一个字符串处理,如“SELECT * FROM user WHERE id = #{id}”,如果传入的值是恶意的SQL语句,也不会被执行。
    • :它会直接将传入的数据拼接到SQL中,不会加引号。这种方式可能会导致SQL注入问题,所以使用时要特别小心。例如,“SELECTFROMuserWHEREid={}:它会直接将传入的数据拼接到SQL中,不会加引号。这种方式可能会导致SQL注入问题,所以使用时要特别小心。例如,“SELECT * FROM user WHERE id = {id}”,如果传入的id值包含恶意SQL语句,就可能会被执行,造成安全隐患。
  8. Dubbo服务注册与发现实现
    • Dubbo的服务注册与发现是通过注册中心来实现的。当一个服务提供者启动时,它会将自己提供的服务信息(包括服务接口、实现类、服务地址等)注册到注册中心。注册中心可以是Zookeeper、Nacos等。
    • 服务消费者启动时,会从注册中心订阅自己需要的服务信息。注册中心会实时推送服务提供者的变更信息给服务消费者。当服务提供者的服务地址发生变化或者服务下线时,注册中心会及时通知服务消费者,服务消费者会根据新的服务信息来调用服务。这样就实现了服务的动态发现和注册,使得服务消费者能够找到可用的服务提供者,并且能够适应服务提供者的变化。
  9. Redis的数据结构及适用场景
    • 字符串(String):适用于缓存、计数器、分布式锁等场景。比如,缓存一个热点数据可以用字符串类型;记录一个网站的访问量可以用计数器功能,每次访问时对对应的字符串值进行自增操作;实现分布式锁可以通过设置一个具有过期时间的字符串来实现。
    • 哈希(Hash):适合存储对象,比如存储一个用户的信息(用户名、年龄、邮箱等),可以将这些字段和值存储在一个哈希中。
    • 列表(List):可以用于消息队列、任务队列等场景。例如,一个任务调度系统可以将任务放入一个列表中,工作线程从列表中取出任务并执行。
    • 集合(Set):适用于去重、交集、并集等操作。比如,统计用户的兴趣爱好,将每个用户的兴趣爱好存放在集合中,可以方便地进行交集操作来找出共同的兴趣爱好,也可以通过集合的特性实现去重功能。
    • 有序集合(Sorted Set):可以用于排行榜、热门列表等场景。比如,根据文章的阅读量对文章进行排序,将文章的ID和阅读量作为有序集合的元素,通过有序集合的排序功能可以方便地获取阅读量排行榜。