Java面试场景高频题

一、什么是布隆过滤器?有什么用?

  1. 首先需要明白一个场景:缓存穿透,缓存穿透是指大量请求参数在Redis中无法查到,导致请求直接请求了数据库。
  2. 解决缓存穿透的办法
    1. 就是增加布隆过滤器
    2. 查询到的数据库数据加入到缓存中(如果大量请求不存在的数据,会Redis里面会出现很多无用的数据)
    3. 对请求参数增加限制,不符合规则的直接拒绝
  3. 什么是布隆过滤器?
    1. 底层数据结构:bitmap
    2. 存储方法
      1. 存储数字直接按照bit位存储
      2. 存储字符串,存储字符串会使用多次hash计算
    3. 优点
      1. 高效的查询性能
      2. 较低的内存需求
      3. 支持高并发访问
      4. 可扩展性强
      5. 简单高效的实现
    4. 缺点
      1. 存在误判的可能(假阳),数据不存在
      2. 不支持删除操作
      3. 无法获取元素本身的内容
      4. 初始化时存在计算开销

二、查询200条数据耗时200ms,如何在500ms内查询1000条数据?

  1. 使用多线程分批查找
java 复制代码
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        List<Future<List<String>>> futures = new ArrayList<>();
        List<String> result = Collections.synchronizedList(new ArrayList<>());

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 5; i++) {
            futures.add(executorService.submit(ThreadDemo::demo))
            ;
        }

        for(Future<List<String>> future : futures){
            List<String> list = future.get();
            result.addAll(list);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("耗时:"+(endTime - startTime) );
    }
    
    public static List<String> demo() throws InterruptedException {
        List<String> res = new ArrayList<>();
        for (int i = 0; i < 200; i++) {
            res.add(String.valueOf(i));
        }
        Thread.sleep(200);
        return res;
    }
  1. 并行查找
java 复制代码
public class ParallelQueryDemo {
    
    // 模拟单个查询方法(简化版)
    private List<String> queryData(int start, int size) {
        // 模拟查询耗时,假设查询时间与数据量成正比
        try {
            Thread.sleep(size);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return IntStream.range(start, start + size)
                       .mapToObj(i -> "Data-" + i)
                       .collect(Collectors.toList());
    }
    
    // 并行查询实现
    public List<String> parallelQuery(int totalSize, int batchSize) {
        int batches = (totalSize + batchSize - 1) / batchSize;
        
        return IntStream.range(0, batches)
                .parallel()  // 开启并行流
                .mapToObj(i -> {
                    int start = i * batchSize;
                    int currentSize = Math.min(batchSize, totalSize - start);
                    return queryData(start, currentSize);
                })
                .flatMap(List::stream)
                .collect(Collectors.toList());
    }
    
    public static void main(String[] args) {
        ParallelQueryDemo demo = new ParallelQueryDemo();
        
        long startTime = System.currentTimeMillis();
        List<String> result = demo.parallelQuery(1000, 200);
        long duration = System.currentTimeMillis() - startTime;
        
        System.out.println("查询到数据量: " + result.size());
        System.out.println("耗时: " + duration + "ms");
    }
}

三、事务失效的场景

  1. 代码抛出TimeoutException时,可以使用注解@Transactional(rollbackFor = {TimeoutException.class})添加自己想添加的异常类型(非RuntimeException)
  2. 调用本类的方法时,没有创建动态代理的对象,故无法进行增加处理;
    1. 通过将当前类注入,然后调用方法,解决事务失效的问题
    2. 通过((xxx)AopContext.currentProxy()).methodName()获取到当前调用的动态代理对象。 推荐
  3. 在多线程调用另外一个事务方法,多个线程使用的是同一个事务吗?子线程抛出异常,主线程能否进行回滚呢?
    1. 子线程:通过((xxx)AopContext.currentProxy()).methodName()获取到当前调用的动态代理对象。 推荐
    2. 想要实现子线程异常,主线程也同时回滚,需要引入分布式事务,因为Spring创建多线程时,每个线程单独持有JDBC的连接,故无法进行事务回滚
    3. 注意 使用(xx)AopContext.currentProxy()时需要在启动类上增加@EnableAspectJAutoProxy(exposeProxy = true)
  4. 调用private方法、final修饰的方法
  5. 类没有被SPring管理
  6. 数据库不支持回滚

四、count(1)、count(*)、count(某个字段)的区别

  1. count(*):统计所有行的数量,包括NULL值
  2. count(1):统计所有行的数量,1被视为常量值
  3. count(字段):该字段非NULL的值
  4. MyISAM中count(*)较快,因为可以直接获取到总行数
  5. Innodb中count(*)会选择索引,然后利用索引统计总行数
  6. count(字段):首先会判断字段是否有索引,没有则全表扫描,有索引则走索引(就算有索引也会过滤掉NULL)

五、写一个死锁

java 复制代码
    public static Object res1 = new Object();
    public static Object res2 = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (res1){
                System.out.println("获取到res1");
                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                    throw new RuntimeException();
                }
                synchronized (res2){
                    System.out.println("获取到res2");
                }
            }
        },"Thread1").start();

        new Thread(()->{
            synchronized (res2){
                System.out.println("获取到res2");

                try {
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                    throw new RuntimeException();
                }
                synchronized (res1){
                    System.out.println("获取到res1");
                }
            }
        },"Thread2").start();
    }
  1. 死锁产生的必要条件
    1. 互斥使用:资源一次只能由一个进程/线程独占使用
    2. 不可抢占:已分配给某进程的资源,不能被其他进程强行夺取
    3. 请求和保持:进程持有至少一个资源,同时等待获取其他被占用的资源
    4. 循环等待:存在一个进程等待的循环链
  2. 解决死锁的方法
    1. 设置超时时间:tryLock(timeout)
    2. 降低锁的粒度
    3. 避免锁嵌套:设计时避免在一个锁内获取另一个锁
    4. 专锁专用:为不同资源分配独立的锁
    5. 银行家算法:系统在分配资源前,先计算此次分配是否会导致系统进入不安全状态

六、Eureka实现原理

  1. 服务注册与发现
    当一个服务实例启动时,会向Eureka发送注册请求,将自己的信息注册到注册中心,Eureka Server会把这些信息保存到内存中,并提供REST接口供其他服务查询。服务消费者可以通过查询服务实例列表来获取服务提供者实例信息,从而实现服务发现
  2. 服务健康检查
    Eureka通过心跳机制来检测实例的健康状态,服务实例会定期向Eureka Server发送心跳,表明自己的状态,如果一段时间内没有收到某个实例的心跳,则标记为不可用,并从服务列表移除,下线实例
  3. 服务负载均衡
    Eureka客户端再调用其他服务时,会从本地缓存中获取服务的注册信息,如果本地没有,则会向Eureka Server发送查询请求,收到返回的服务实例列表后,客户端进行负载均衡然后调用

七、如何防止秒杀、抽奖、抢购中的羊毛党?

  1. 注册时增加手机号验证、实名认证
  2. 下单时增加动态图形验证码
  3. 判断账号的状态(新号)
  4. 提高消费门槛
  5. 设置一些规则,为用户做分类,优待忠诚用户
  6. 限流,自动扩容伸缩
  7. 设置黑名单
  8. 给用户做画像,建模(腾讯云;天御)

八、Spring boot读取配置文件的六种方式

  1. @Value("${value:}");在static和final上无法生效
  2. @ConfigurationProperties(prefix = "xxx");可以将配置文件映射到对象上,适合批量映射
  3. 通过注入Environment ,然后调用getProperty("xxxx")方法
  4. @PropertySources(value = "xxxx",encoding = "utf-8")获取外部配置文件

九、缓存穿透、缓存击穿、缓存雪崩

  1. 缓存穿透是指请求查询数据时,缓存中没有该数据,请求会去查询数据库,这样的话缓存就是去了意义,大量请求会导致服务不可用
    解决方案:
    1. 缓存空结果信息:可能会有大量空数据
    2. 布隆过滤器:具体实现见:一、
    3. 过滤非法的参数,拦截无效请求
  2. 缓存击穿:指热点数据在高并发场景下过期,大量请求直接请求数据库
    解决方案:
    1. 热点数据永不过期
    2. 当发现热点数据过期时,就加锁去更新缓存
    3. 定时任务刷新
  3. 缓存雪崩:是指大量的热点数据同时过期
    1. 设置热点数据永不过期
    2. 缓存分批过期,防止同一时间过期

十、本地消息表如何保持分布式一致性的?

  1. 第一步:事务执行
    业务服务在本地数据库执行业务操作(如创建订单)
    在同一个事务中,向本地消息表插入一条待发送的消息记录
    只有这两个操作都成功,事务才会提交
  2. 第二步:消息投递
    后台定时任务定期扫描本地消息表
    发现待发送的消息后,将其发送到消息队列(如RabbitMQ)
    发送成功后更新消息状态为"已发送"
  3. 第三步:消息消费
    消费者从消息队列获取消息
    处理业务逻辑(如扣减库存)
    处理成功后向消息队列确认
  4. 异常处理
    如果消息发送失败,会保留在本地消息表中等待下次重试
    如果消费失败,消息队列会重新投递
    设置最大重试次数,超过后进入死信队列人工处理

十一、什么雪花算法?

  1. 64bit的long类型
  2. 第一个bit代表 正负(0-正,1-负)
  3. 1-42bit 毫秒级时间戳
  4. 42bit - 52 bit (机房和机器ID),前五bit代表机房ID,后五bit代表机器ID
  5. 最后12bit (同一毫秒内产生的ID),序列号

十二、Mysql索引失效的场景

  1. 不满足最左匹配原则
    假设索引包含code、name、age,查询时需要按照顺序判断,否则不会使用全部索引
  2. 使用Select *
  3. order By:排序方向不一致;使用非索引列排序;查询字段和排序字段不是联合索引
  4. 字段类型不同
  5. like 语句左边包含%
  6. 列对比
  7. 使用or(8.0中or左右两边都有索引,会索引生效)
  8. 范围查询(范围查询回表较少会走索引,回表较多不走索引)
  9. 隐式转换

十三、Spring和Spring boot 代码不同之处

  1. 重名Bean

    1. Spring 是后面的Bean覆盖前面的
    2. Spring Boot启动会报错,可以通过配置文件
    java 复制代码
    		spring.main.allow-bean-definition-overriding= true
    		spring.main.profiles.active= default
  2. 循环依赖

    1. Spring支持循环依赖
    2. Spring boot不支持,可以通过配置保持和Spring一致
    java 复制代码
    spring.main.allow-circular-references=true
  3. @Value获取不到值

    1. Spring使用@Value获取不到值得时候,会把@Value里面得参数作为值
    2. Spring boot使用@Value获取不到值得时候会报错,可以使用@Value("${xxx:defaultValue}")配置
    3. 或者通过如下配置实现保持和Spring一致
java 复制代码
	@Configuration
public class AppConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        configurer.setIgnoreUnresolvablePlaceholders(true); // 关键设置
        // 其他设置...
        return configurer;
    }
}
  1. 动态代理类型
    1. Spring中类如果实现了接口则使用JDK动态代理,如果没有实现则使用CGLib动态代理
    2. Spring boot默认使用CGLib动态代理;也可以配置spring.aop.proxy-target-class=false配置和Spring一致

十四、如何使用Redis记录上亿用户连续登录天数

  1. 使用bitmap存储数据,存储30天的的key,然后根据用户ID映射到bitmap上,最终计算总天数只需要从最后一天往前推,找到对应用户的标志位进行判断即可

十五、 Tomcat默认线程数是多少?

  1. 默认线程数是200

十六、Mysql最左前缀原则一定需要最左列吗?

  1. 不一定
    1. 如果查询的字段可以走到索引覆盖,则可以使用索引

十七、CAP定理和BASE理论

  1. CAP定理
    1. C:一致性
    2. A:可用性
    3. P:分区容错性
  2. Base理论
    1. BA:基本可用:使用服务降级达到效果
    2. S:软状态:程序运行的中间态
    3. E:最终一致性:允许程序一段时间内状态不一致

十八、什么内存泄漏、内存溢出

  1. 内存泄漏:堆内存中存在内存空间无法释放,如数据库连接未关闭、Map/List中参在大量的对象未清理等
  2. 内存溢出:堆内存空间满了,如大对象/大数据集操作;内存配置不当;元空间不足,栈溢出(递归)

十九、Mysql隐式转换

  1. 当字符串与数字比较时,Mysql会将字符串转为数字
java 复制代码
SELECT '10' > 9; -- 结果为 1(true),因为 '10' 被转换为 10
  1. 当字符串与日期比较,会将字符串转换为日期
java 复制代码
SELECT '2023-10-01' > '2023-09-30'; -- 结果为 1(true),字符串被转换为日期
  1. 当数字与日期比较,会将数字转换为日期
java 复制代码
SELECT 20231001 > '2023-09-30'; -- 结果为 1(true),数字被转换为日期
  1. 当字符串和布尔值比较,会将布尔值转换为数字(true = 1,false = 0)
java 复制代码
SELECT '1' = TRUE; -- 结果为 1(true),因为 '1' 被转换为 1

隐式类型转换导致的问题

  1. 索引失效:如果字段是varchar类型,通过id = 1则会导致索引失效
  2. 数据丢失
  3. 意外的结果
  4. 性能问题:隐式转换也会导致性能的开销

二十、 MVCC多版本并发控制

  1. 实现原理通过undolog +read view控制
    undolog:存储历史版本数据链
    read view:存储当前事务ID信息
    具体参考MVCC多版本并发控制详解

二十一、服务降级和服务熔断对比

特性 服务降级 (Degradation) 服务降级 (Degradation)
核心目的 保核心、舍边缘:确保核心业务可用,主动放弃部分非关键功能或内容。 防雪崩、快恢复:防止不断失败调用拖垮系统,快速失败并自我修复。
触发条件 主动预测:通常由人工预设(如大促期间)、或系统资源(如CPU、线程池)达到阈值时触发。 到阈值时触发。 被动响应:由连续失败次数/失败率达到阈值时自动触发。
动作对象 功能维度:关闭或简化某个或某些非核心功能(如关闭商品评论、将推荐算法降级为热门榜单)。 服务维度:切断对某个故障下游服务的全部调用。
状态机制 无状态:降级开关只有"开"和"关"两种简单状态。 有状态机:包含"关闭"、"开启"、"半开" 三种状态,自动探测下游是否恢复。
思维角度 战略放弃:是一种设计理念和预案,着眼于整体系统稳定性。 战术防护:是一种容错机制,着眼于单个服务的故障隔离与恢复。
举例 餐厅人多时:老板主动宣布"今天不提供炒饭和甜品",以保证主菜能快速出餐。 厨房着火了:经理立刻拉下电闸(熔断),防止火势蔓延。等火扑灭后,再试探性地送电(半开)。

二十二、内存200M读取1G文件,并统计重复数据次数

  1. 解决办法哈希分片
    详细见后续的案例代码

二十三、防止用户重复下单

  1. 通过redis的setnx实现,通过用户ID+商品等关键因素进行判断
  2. 可以在进入下单详情页的时候生成一个唯一的订单ID,然后通过redis的setnx实现

二十四、String s= "s",和String s = new String("s")的区别?

  1. String s = "s" 最多创建1个对象,如果字符常量池中已有则直接引用
  2. String s = new String("s") 最多创建两个对象,如果字符串常量池中已有则不会创建"s",仅创建String对象

二十五、 MQ如何解决消息丢失问题

  1. 生产者:同步发送消息,阻塞线程,并增加事务回滚;异步+重试+人工补偿
  2. MQ服务:更改为同步刷盘机制,保证服务宕机不会导致消息丢失
  3. 消费者:通过自动重试机制,通知MQ消费失败,下次重新消费

二十六、MQ的重复消费?如何避免?如何做到幂等性

  1. 待续
相关推荐
沛沛老爹1 天前
Web开发者快速上手AI Agent:基于Function Calling的12306自动订票系统实战
java·人工智能·agent·web转型
Ljubim.te1 天前
inline介绍,宏定义的注意事项以及nullptr
c语言·开发语言·c++
CRUD酱1 天前
后端使用POI解析.xlsx文件(附源码)
java·后端
亓才孓1 天前
多态:编译时看左边,运行时看右边
java·开发语言
小白探索世界欧耶!~1 天前
用iframe实现单个系统页面在多个系统中复用
开发语言·前端·javascript·vue.js·经验分享·笔记·iframe
2501_941878741 天前
在奥克兰云原生实践中构建动态配置中心以支撑系统稳定演进的工程经验总结
开发语言·python
weixin_443297881 天前
Python打卡训练营第31天
开发语言·python
围炉聊科技1 天前
Vibe Kanban:Rust构建的AI编程代理编排平台
开发语言·rust·ai编程
2501_941802481 天前
从缓存更新到数据一致性的互联网工程语法实践与多语言探索
java·后端·spring