Java中级面试题

张开发
2026/4/13 15:56:52 15 分钟阅读

分享文章

Java中级面试题
1.假如有两个线程共同操作数据库以乐观锁的角度考虑怎么确保不会发生并发问题PS考点是CAS比较并替换。CAS中有三个值内存中的值新值旧值。假如内存中的值是2000要进行--操作A,B两个线程分别从主内存中拉去数据当A线程进行--操作新值变成了1999旧值与主内存中的值一致将新值替换掉主内存中的值此时主内存值为1999。当B线程进行--操作新值也是1999比较主内存值1999与旧值2000不一致拉去主内存值在--此时旧值变成了1999新值变成了1998……依次类推AB线程共同操作共享资源数据也不会出现并发问题。CAS 的 ABA 问题如何解决CAS中的ABA问题指的是并发环境下一个线程将变量A更新为B以此同时另一个线程将A更新C又改为A第一个线程CAS发现当前值任然为A于是操作成功但实际上这个值已经被改变过了。追加版本号每次给共享变量加一个递增版本号2.synchronized的原理在JDK1.6以前sync是重量级锁。重量级锁有一个等待队列想要抢占锁的线程都进入等待队列中当线程A获得到共享资源其他线程进入阻塞状态当线程A释放锁时其他线程被唤醒。这个过程很消耗操作系统因此sync的效率很低。在JDK1.6之后sync应用了锁升级。起初是无状态的jvm启动4s后开启偏向锁偏向当前正在执行的线程当某个线程来竞争要判断当前锁是否可重新偏向如果不能就升级为轻量级锁当自璇线程一直尝试获得锁无法获取到锁就会升级为重量级锁。3.线程池的原理线程池顾名思义就是存放线程的池子用的时候从里边拿不用的时候放入池中避免了多次创建与销毁线程。线程池中有几个重要的参数核心线程数、最大线程数、线程等待时间、阻塞队列、拒绝策略。线程池初始化的时候线程池中的默认线程数是0当有任务第一次访问的时候开始创建线程到核心线程数如果接着有任务线程都被占用那么任务会放入阻塞队列如果阻塞队列塞满那么会看核心线程数是否小于最大线程数如果是那么接着创建线程来执行任务当核心线程数最大线程数且所有线程都占用且阻塞队列塞满会触发拒绝策略。4. 三种线程池的适用场景fixedThreadPool固定数量线程核心线程数最大线程数阻塞队列是int最大值适用于cpu计算密集的情况cpu是调度线程的cpu密集可以理解为程序一直在计算因为cpu密集再去创建线程已经顾不上了所以固定数量的就足够了。cachedThreadPool可扩容数量线程池核心线程数0最大线程数为int的最大值用到多少线程创建多少。阻塞队列是0适用于io密集的场景io密集就会产生io阻塞那么这是cpu就有空闲这是就可以创建线程以保证cpu尽可能多的使用。singleThreadExecutor核心线程数最大线程数1 单个线程但是有阻塞队列单线程池只有一个线程那为什么不直接创建直接创建的线程用完会销毁用线程池避免了资源消耗而且当遇到异常时不会丢失任务。5.Redis缓存击穿、穿透、雪崩击穿数据库中有缓存中没有的数据。解决办法设置热点数据永不过期加互斥锁穿透缓存和数据库中都没有的数据。解决办法将value的值赋为null在接口层增加校验布隆过滤器快速检索一个元素是否在集合中,不存在的一定能检索到雪崩同一时间大量数据请求数据库导致数据库压力大而宕机。解决办法过期时间设置随机避免同一时间多个缓存同时过期设置热点数据永不过期使用锁或队列的方式保证不会有大量线程对数据库一次性进行读写6.Redis有哪些数据结构Sting List Hash Set Sorted Set7.为什么Redis最常用的数据类型是String因为String类型的数据结构简单存储空间占据小。我们知道redis缓存中的数据是要存到内存中的而内存的空间毕竟有有限的所以能用String时尽量用String8.Redis的持久化方式Redis的持久化方式有2种RDB和AOFRDB是快照文件的形式将数据持久化BESAVE命令fork子进程生成RDB文件。AOF是把所有的写命令记录到日志中重跑日志文件就能还原数据。9.Redis的集群模式为了redis的高可用现在都会给redis做备份多启动一台redis形成主从架构。主服务器将数据复制到从服务器有2种方式完全重同步和部分重同步如果是第一次会完全重同步如果是网络中断等会部分重同步。复制的流程从服务器给主服务发起复制数据请求主服务器将RDB文件发给从服务器从服务器拿到文件后先清空自己再加载RDB文件主服务会把发送文件后修改的命令用buffer记录从服务器加载完RDB之后将buffer发送到从服务器这样就保证了主从服务器的数据最终一致性。10.Redis的实战场景短信验证码接口幂等统计次数分布式锁缓存数据11.Redis过期键的删除策论有哪些定时删除通过使用定时器来删除保证过期键尽可能的删除并释放过期键占用的内存。对内存友好对CPU不友好。惰性删除获取键时对键进行过期检测不会在删除其他无关过期键花费CPU对CPU友好对内存不友好定期删除定时删除和惰性删除的一种折中策略每隔一段时间执行一次删除过期键操作12. 在开发中如何保证线程安全的①使用线程安全的集合CopyOnWriteArrayListMap(CurrentHashMap)原子类(Atomic)②一次可以使用多少个线程可以考虑CountDownLatch③使用synchronized或者Lock进行加锁出来CopyOnWriteArrayList的底层是基于数组的每次修改都会复制出来一个数组在这个新数组上修改修改不影响读修改好之后将原数组替换实现了“读写分离”。CurrentHashMap的底层是syncCAS红黑树,首先CurrentHashMap是基于HashMap的HashMap是数组链表的形式通过key的hashcode方法计算出hashcode值来判断在哪个位置如果这个位置有值了就会调用equals方法如果值相等替换如果不等就追加到链表后。CurrentHashMap增加了同步的操作利用CASSynchronized保证线程安全13. 模拟一个场景现在有50个任务要等这50个任务执行完再执行下一个方法怎么设计首先处理50个任务肯定要用到多线程。可以利用CountDownLatch和线程池结合处理。CountDownLatch是基于AQS的会将创建CountDownLatch的入参传递给statecountDown()就是利用CAS将state-1await()是让头结点等待state0时释放所有的线程14. 接口权限控制首次登录的时候会生成一个token前端拿到token放到请求头中以后每次的请求都需要携带token后端判断前端带过来的与数据库或者缓存中的token是否一致一致的话代表登录过了。我们利用spring的aop切面统一执行判断token记得过滤掉登录接口。15.RocketMQ如何保证保证消息不丢失从3个层面保证消息不丢失首先是生产者生产者利用同步刷盘和定时任务补偿来 保证消息不丢失。同步刷盘即生产者发送消息到mq,mq会把消息写入磁盘再返回结果。如果消息发送失败会由定时任务重新拉取发送。比如短信平台是利用mq的重发机制失败了再重发一次如果再失败写入重发表等待定时任务的扫描根据规则来重发。其次是mqmq要保证集群高可用然后利用同步刷盘异步复制的方式这种方式既能保证消息的可靠性又能避免性能降低。异步复制是指从主节点复制数据到从节点是异步的不会阻塞整个流程。最后是消费者可以利用rocketMQ的重试机制默认重复16次当消息消费失败返回related消息会自动重试如果16次都失败就需要补偿机制补偿机制根据业务情况而定。16.HTTP和MQ的区别MQ主要用于异步通信HTTP是基于请求/响应的MQ是长链接HTTP每次需要重新建立链接MQ的速度比http快17.mq和http的优缺点mq异步通信解耦好但是增加了系统复杂度不能保证消息的完全不丢失和顺序http简单易用成熟稳定但它是同步通信阻塞18.如何保证MQ的顺序首先发消息的时候需要保证顺序的消息要发到同一个mq队列里因为一个队列只能被一个消费者消费其次消费者不能并发消费。如何保证消息发到同一个队列呢rocketmq有自带的消息队列选择器在发送消息的时候实现这个组件重写方法在方法里可以写自己的逻辑比如用消息的流水号的哈希值对队列数取余就跟分表的逻辑是一样的短信平台不需要保证消息的顺序性但是有些业务需要比如订单业务创建订单下单支付 这几步的操作不能乱所以这个时候可以用订单号的哈希值对消息队列的队列数取余选择性发到一个队列中这样就能保证顺序性19.MQ消息堆积怎么办首先要排查消费者是否有问题如果有问题定位问题在哪解决问题如果消息被快速消费评估时间根据业务情况看是否需要加速消费如果不需要那就结束了如果需要并且队列数消费者机器那么增加消费者服务器队列数消费者机器需要用备用mq来消费堆积的消息20.有过JVM调优吗没有不过我们一般系统优化的思路是这样的首先会看数据库方面看看索引是否合理然后我们会看代码层面比如某个接口相应时间长会看看有没有慢sql、多层循环看是否还有优化空间最后会考虑扩容不过扩容也是运维来做。21.接口限流的时候请求扎堆怎么处理限流只是控制总量不能打散请求。解决办法加抖动、随机延迟利用滑动窗口普通限流5s最多访问5次那么第5s和第6s的这5次都没有被拦截相当于10次但加了滑动窗口每过1s往右滑动一格那么就避免了上述问题请求进来丢队列后台多线程慢慢消费确保数据库只承受固定压力限流改成漏桶算法22.遇到线上问题怎么排查首先看本地或者测试环境能否复现复现的话就直接修改代码。如果不能查看生成日志定位问题

更多文章