模拟面试问题解析

文章目录
- 1.项目中怎么解决接口防刷的问题
- 2.消息队列消费消息后,但是业务执行失败后怎么处理的
- 3.向消息队列发送消息后,怎么确定消息一定发送到消息队列中
- 4.Redis中数据容易丢失,不管设置什么持久化模式都容易丢失,这个容易丢失怎么解决的
- 5.支付的时候,刚好支付成功,订单还未修改状态,服务器死掉了,怎么解决的
- 6.支付的时候,微信服务器调用我们的服务器通知支付结果,怎么确定这个请求来自微信
- 7.项目中下单模块并发量多大
- 8.ConcurrentHashMap在1.7是分段锁,1.8是CAS和Synchronized,为什么这么改变
- 9.在你的理解中,什么是容器?
- 10.HashMap能不能维护成一个成员变量在一个单例的类中,为什么?
- 11.什么是线程安全问题
- 12.开发中用过什么设计模式?
- 13.责任链设计模式为了规避Java中那个机制?
- 14.大量的if else代码 怎么进行优化?
- 15.工厂模式和构建者模式有什么区别?
- 16.静态代理和装饰器模式有什么区别?
- 17.说一下在哪儿用到多线程?
- 18.你是怎么理解锁的?
- 19.线程是并行的还是并发的?
- 20.讲一下在哪里用到了单例,你觉得一个类在什么情况下使用单例?
- 21.下单成功后,前端怎么知道下单成功的?
- 22.消息队列消息乱序在哪儿遇见的?他为什么乱序?怎么解决?
- 23.平时做过代码优化吗?
1.项目中怎么解决接口防刷的问题

2.消息队列消费消息后,但是业务执行失败后怎么处理的
- RocketMQ 中的机制
RocketMQ 中,消息消费结果的返回值有2个:ConsumeConcurrentlyStatus.CONSUME_SUCCESS 和 ConsumeConcurrentlyStatus.RECONSUME_LATER。
前者为消费成功,後者表示消费失败。消费失败后,RocketMQ会在特定时间间隔后进行重试,重试次数越多,时间间隔越长。
默认最多可以重试16次,每次重试的时间间隔为:
messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
-
RabbitMQ 中消息的确认
RabbitMQ 中,消息消费确认模式有如下3种: -
AcknowledgeMode.NONE:不确认
-
AcknowledgeMode.AUTO:自动确认
-
AcknowledgeMode.MANUAL:手动确认
在Spring Boot中配置消费确认模式的配置:
spring.rabbitmq.listener.simple.acknowledge-mode=manual
-
手动确认模式中,如果消息消费失败,可以选择是否将消息重新放入队列。
-
默认的确认模式为自动确认,如果消息消费的方法执行完成而未抛出异常,则会自动确认该消息消费成功;若抛出异常,则会自动确认消费失败,此时消息会重新回到队列中(按默认配置)。
-
消息消费失败后如果重新放入队列,RabbitMQ会立刻将消息重新推送给消费者,而没有延迟推送的机制。
此时很可能会出现一直失败并抛出异常的情况,浪费了资源,并导致日志爆满,并淹没最初几条有用的异常日志信息(Java中如果多次抛出同样的异常,打印日志时会省略部分异常堆栈)。因此为了方便查看完整的异常堆栈,进程错误排查,建议在RabbitMQ消息消费失败时,不要无限次的放回队列。此时有以下两种场景: -
手动确认模式下
消费失败时,不将其重新放入队列(确认重试也不会成功的情形),打印错误信息后,通知相关人员,人工介入处理。 -
自动确认模式下
在Spring Boot RabbitMQ中,可以通过以下几个配置参数,来调节消息消费失败时,消息重新投递的策略:
# 是否开启消费者重试(为false时关闭消费者重试)
spring.rabbitmq.listener.simple.retry.enabled=true
# 最大重试重新投递消息次数
spring.rabbitmq.listener.simple.retry.max-attempts=3
# 重试重新投递消息的间隔时间(单位毫秒)
spring.rabbitmq.listener.simple.retry.initial-interval=5000ms
#重试次数超过上面的设置之后,是否丢弃(消费者listener抛出异常,是否重回队列(默认true:重回队列,false:不重回队列(可结合死信交换机))
spring.rabbitmq.listener.simple.default-requeue-rejected=false
3.向消息队列发送消息后,怎么确定消息一定发送到消息队列中
在向消息队列发送消息后,可以采取以下措施来确保消息一定发送到消息队列中:
-
消息确认机制:大多数消息队列系统都支持消息确认机制,即在消息发送后,消息队列会返回一个确认信息给生产者。生产者可以通过等待确认信息或者接收到确认信息后,才认为消息已经成功发送到消息队列中。
-
持久化设置:确保消息队列的持久化设置已启用。消息队列通常提供了持久化的选项,将消息存储到持久化存储介质(如磁盘)上,以确保即使在消息队列重启或故障时,消息也不会丢失。
-
重试机制:在消息发送失败或者超时时,可以设置重试机制来重新发送消息。可以根据具体的业务需求和消息队列的特性来设置重试次数和间隔时间,确保消息最终被成功发送。
-
错误日志记录:对于发送失败的消息,可以将其记录到错误日志中,以便后续进行排查和处理。可以通过监控工具或者手动检查错误日志来发现发送失败的情况,并及时采取相应的措施。
-
监控与报警:建立监控系统,定期检查消息队列的运行状态和发送情况。如果发现消息发送异常或者消息队列不可用的情况,及时触发报警机制,通知相关人员进行处理。
通过以上措施,我们可以在一定程度上确保消息成功发送到消息队列中,并提供相应的监控、报警和处理机制,以保证消息的可靠性和稳定性。
4.Redis中数据容易丢失,不管设置什么持久化模式都容易丢失,这个容易丢失怎么解决的
Redis 中的数据容易丢失的主要原因是由于 Redis 的特性所致,例如内存数据库、异步持久化等。为了解决 Redis 数据丢失的问题,我们可以采取以下方法:
-
持久化:Redis 提供了两种持久化方式,即 RDB(快照)和 AOF(追加文件)。我们可以根据需求选择合适的持久化方式,并合理设置持久化的频率,以减少数据丢失的可能性。
-
RDB:通过定期生成快照将内存中的数据保存到磁盘上。可以通过设置自动触发快照生成的时间间隔来控制数据丢失的程度,但在发生故障时可能会丢失最后一次快照之后的数据。
-
AOF:将 Redis 执行的每个写命令追加到文件中。可以通过设置不同的 AOF 策略(如每秒同步、每条命令同步等)来控制数据丢失的程度,但相比 RDB,AOF 的持久化性能稍差。
-
-
主从复制:通过配置主从复制,将主节点的数据同步到一个或多个从节点上。从节点可以作为主节点的备份,当主节点发生故障时,可以将其中一个从节点切换为主节点,以保证服务的可用性和数据的完整性。
-
高可用性方案:使用 Redis Cluster 或 Sentinel 等高可用解决方案,将数据分散在多个节点上,实现数据的冗余备份和自动故障转移,从而提高系统的可用性和数据的安全性。
-
数据备份:定期对 Redis 数据进行备份,将备份文件存储在其他可靠的存储介质上,如磁盘、云存储等。在发生数据丢失时,可以通过备份文件进行数据恢复。
-
避免误操作:加强对 Redis 的权限控制,限制只有授权用户才能执行关键操作,避免误操作导致数据丢失。
综上所述,通过合理选择持久化方式、使用主从复制、实施高可用性方案、定期备份数据和加强权限控制等措施,可以有效降低 Redis 数据丢失的风险。同时,根据实际业务需求和系统可靠性要求,综合考虑以上方法来选择最适合的解决方案。
5.支付的时候,刚好支付成功,订单还未修改状态,服务器死掉了,怎么解决的
采用微信支付的时候,有的时候会碰到微信成功支付,但是订单的状态却没更改的问题;产生该问题的原因有以下几个方面:
- 1.服务器网络的问题(几率很小很小)
- 2.没有配置微信的异步回调函数
- 3.支付后改变订单状态的代码内部出bug
当用户支付成功后,如果微信服务器与我们服务器之间的网络断开,我们可以采取以下处理方案:
-
主动查询订单状态:我们可以定期向微信服务器发送订单查询请求,以获取最新的订单状态。可以使用微信支付接口提供的查询订单API来实现。如果网络断开后恢复,我们可以根据查询结果来更新订单状态。
-
异步通知机制:在用户支付成功时,我们可以通过微信支付接口配置异步通知URL,微信服务器会在支付状态发生变化时向该URL发送通知。即使网络断开,我们的服务器也可以在网络恢复后通过接口主动查询支付结果。
-
定时任务:我们可以设置一个定时任务,定期向微信服务器查询支付结果。如果网络断开,任务会在网络恢复后自动执行,以更新订单状态。
-
异常处理:针对网络断开的情况,我们可以编写异常处理逻辑,例如记录异常日志、发送报警通知等,以及及时通知相关人员进行处理。
综上所述,通过主动查询订单状态、异步通知机制、定时任务和异常处理等方案,我们可以在微信服务器与我们服务器之间的网络断开时,及时处理用户支付成功的情况。
6.支付的时候,微信服务器调用我们的服务器通知支付结果,怎么确定这个请求来自微信
为了确定支付结果通知请求确实来自微信服务器,我们可以使用微信支付接口提供的验证签名机制。微信支付会在每个支付结果通知中附加一个签名(sign),我们可以通过以下步骤验证签名的有效性:1. 获取通知参数:从支付结果通知中获取所有参数,包括商户订单号(out_trade_no)、支付金额(total_fee)、支付结果(result_code)等。2. 根据规则生成本地签名:使用我们在微信支付配置中设置的商户密钥以及通知参数,按照一定的规则生成本地签名。3. 对比本地签名和微信服务器返回的签名:将本地生成的签名与微信服务器返回的签名进行对比,如果两者一致,则说明该支付结果通知确实来自微信服务器。具体的签名验证规则可以参考微信支付官方文档,不同的开发语言和框架可能有不同的实现方式。在验证签名的过程中,我们还需注意对参数进行安全性校验,避免恶意篡改或伪造请求。通过验证签名机制,我们可以确保支付结果通知请求来自微信服务器,从而保证支付结果的准确性和安全性。
7.项目中下单模块并发量多大
项目中下单模块的并发量大小是一个需要根据具体业务场景和需求来确定的问题,没有一个固定的标准答案。并发量大小的确定需要考虑以下几个方面:
-
预估用户数量:首先需要预估项目的用户数量,包括同时在线用户数量和短时间内的用户访问峰值数量。这可以通过历史数据、市场调研以及业务需求来进行估算。
-
业务流程的并发程度:下单模块的并发量大小还与业务流程的并发程度相关。例如,如果下单模块需要调用其他服务进行库存检查、价格计算等操作,那么并发量大小还需考虑这些相关服务的并发能力。
-
系统架构和性能优化:项目的系统架构和性能优化措施也会影响下单模块的并发量。例如,使用分布式架构、缓存、负载均衡等技术手段可以提高系统的并发处理能力。
-
可扩展性考虑:在设计下单模块时,需要考虑其可扩展性,以便在未来根据实际需求进行水平扩展,以应对更大的并发量。
在实际情况中,可以通过压力测试和性能测试来评估系统在不同并发量下的性能表现,从而确定下单模块的并发量大小。这样可以根据实际情况对系统进行调优和容量规划,以保证系统的稳定性和性能满足业务需求。
8.ConcurrentHashMap在1.7是分段锁,1.8是CAS和Synchronized,为什么这么改变
在Java 8之前
ConcurrentHashMap 使用分段锁(Segment)来控制并发访问。每个段都是一个独立的哈希表,只包含总容量的一部分,每个段都有自己的锁,因此不同的线程可以同时访问不同的段。这种方法在高并发场景下表现良好,但由于锁的开销和段的数量限制,它的性能随着并发度的增加而下降。
在Java 8中
ConcurrentHashMap 采用了一种新的机制来实现更好的并发性能:它使用CAS(Compare and Swap)和synchronized来取代分段锁。这种实现方式被称为"数组+链表+红黑树",即每个桶内部采用链表或红黑树来存储键值对,当桶内键值对数量达到一定阈值时,链表会自动转化为红黑树,以提高查找效率。
在Java 8的 ConcurrentHashMap 中,整个哈希表被分成若干个段,每个段都被实现为一个数组,每个数组元素都是一个链表或红黑树。每个元素都是一个桶,通过哈希函数将键值对映射到桶中。每个桶内部都通过synchronized来实现同步,以保证线程安全性。而对于读操作,使用了无锁的CAS操作。
相比于分段锁,这种实现方式有以下优点:
减少锁竞争:由于每个桶内部采用了synchronized来实现同步,不同的线程可以同时访问不同的桶,从而减少了锁竞争。
更高的并发度:由于不再受限于固定数量的段,ConcurrentHashMap 可以根据需要动态调整大小,并支持更高的并发度。
更好的扩展性:由于不再需要维护多个段的锁,因此在扩展时可以更容易地添加或删除桶,而不需要重构整个数据结构。
更好的性能:使用CAS操作替代了分段锁,避免了分段锁中的自旋等待开销,提高了并发性能。
总结
总之,Java 8的 ConcurrentHashMap 放弃了分段锁,采用了CAS和synchronized的方式来实现更好的并发性能,从而能够更好地满足高并发场景下的需求。
9.在你的理解中,什么是容器?
在Java中,容器是一种数据结构,用于存储和组织其他对象。容器提供了一种方式来管理和访问这些对象,并提供了各种方法来增加、删除、搜索和遍历其中的元素。Java提供了多种容器类,每种容器类都有其特定的用途和功能。

docker本身也是一种容器化技术,为了降低虚拟机造成的物理主机资源浪费,提高物理主机的资源利用率,并能够提供像虚拟机一样良好的应用程序隔离运行环境,人们把这种轻量级的虚拟机,称为“容器”。
10.HashMap能不能维护成一个成员变量在一个单例的类中,为什么?
HashMap可以作为一个成员变量维护在一个单例类中。单例类是指只能实例化一次的类,通常用于表示全局唯一的对象。
HashMap作为一个成员变量可以提供一个全局的键值对存储结构,并且可以被单例类的所有方法共享和访问。这样可以方便地在单例类的任何方法中使用和修改HashMap中的数据。
然而,需要注意的是,在多线程环境下使用HashMap作为成员变量时,需要考虑线程安全性。由于HashMap不是线程安全的,可能会导致并发访问和修改HashMap时的竞态条件问题。为了确保线程安全,可以采取以下两种方式之一:
-
使用同步控制:在对HashMap进行读写操作时,使用synchronized关键字或者在适当的代码块中使用锁来同步访问。
-
使用线程安全的Map实现:例如,在Java中,可以使用ConcurrentHashMap来替代HashMap。ConcurrentHashMap是线程安全的,可以支持并发读写操作。
总之,HashMap可以作为一个成员变量维护在一个单例类中,但需要考虑线程安全性以及选择合适的并发控制方式。
11.什么是线程安全问题
就是多线程环境中 , 且存在数据共享 , 一个线程访问的共享 数据被其他线程修改了, 那么就发生了线程安全问题 , 整个访问过程中 , 无一共享的数据被其他线程修改了 就是线程安全的程序中如果使用成员变量, 且对成员变量进行数据修改 , 就存在数据共享问题, 也就是线程安全问题。

12.开发中用过什么设计模式?
单例模式、代理模式、责任链…


13.责任链设计模式为了规避Java中那个机制?
责任链设计模式的目的并不是为了规避Java中的某个机制,而是为了解耦发送者和接收者之间的关系,使得多个对象都有机会处理请求,从而避免将请求的发送者与接收者耦合在一起。
在Java中,常见的一种机制是使用if-else或者switch语句来判断不同的条件并执行相应的逻辑。这种方式会导致代码臃肿,可扩展性差,并且违背了开闭原则(对扩展开放,对修改关闭)。
责任链设计模式通过将请求发送者与接收者解耦,使得每个接收者都有机会处理请求,而不需要显式指定接收者。每个接收者都有一个后继者(successor),可以选择将请求传递给下一个接收者,也可以自行处理请求。
这种设计模式可以让系统更加灵活、可扩展,并且符合面向对象设计原则。它使得代码更易于维护和测试,并且提高了代码的可读性和可复用性。
因此,责任链设计模式并不是为了规避Java中的某个机制,而是一种常见的设计模式,用于解耦、可扩展和灵活处理请求。
14.大量的if else代码 怎么进行优化?
代码中如果if-else比较多,阅读起来比较困难,维护起来也比较困难,很容易出bug,接下来,本文将介绍优化if-else代码的八种方案。

优化方案一:提前return,去除不必要的else
如果if-else代码块包含return语句,可以考虑通过提前return,把多余else干掉,使代码更加优雅。
优化前:
if(condition){//doSomething
}else{return ;
}
优化后:
if(!condition){return ;
}
//doSomething
优化方案二:使用条件三目运算符
使用条件三目运算符可以简化某些if-else,使代码更加简洁,更具有可读性。
优化前:
int price ;
if(condition){price = 80;
}else{price = 100;
}
优化后:
int price = condition?80:100;
优化方案三:使用枚举
在某些时候,使用枚举也可以优化if-else逻辑分支,按个人理解,它也可以看做一种表驱动方法。
优化前:
String OrderStatusDes;
if(orderStatus==0){OrderStatusDes ="订单未支付";
}else if(OrderStatus==1){OrderStatusDes ="订单已支付";
}else if(OrderStatus==2){OrderStatusDes ="已发货";
}
...优化后:先定义一个枚举```java
public enum OrderStatusEnum {UN_PAID(0,"订单未支付"),PAIDED(1,"订单已支付"),SENDED(2,"已发货"),;private int index;private String desc;public int getIndex() {return index;}public String getDesc() {return desc;}OrderStatusEnum(int index, String desc){this.index = index;this.desc =desc;}OrderStatusEnum of(int orderStatus) {for (OrderStatusEnum temp : OrderStatusEnum.values()) {if (temp.getIndex() == orderStatus) {return temp;}}return null;}
}
有了枚举之后,以上if-else逻辑分支,可以优化为一行代码
String OrderStatusDes = OrderStatusEnum.0f(orderStatus).getDesc()
优化方案四:合并条件表达式
如果有一系列条件返回一样的结果,可以将它们合并为一个条件表达式,让逻辑更加清晰。
优化前
double getVipDiscount() {if(age<18){return 0.8;}if("深圳".equals(city)){return 0.8;}if(isStudent){return 0.8;}//do somethig}
优化后
double getVipDiscount(){if(age<18|| "深圳".equals(city)||isStudent){return 0.8;}//doSomthing}
优化方案五:使用 Optional
有时候if-else比较多,是因为非空判断导致的,这时候你可以使用java8的Optional进行优化。
优化前:
String str = "jay@huaxiao";
if (str != null) {System.out.println(str);
} else {System.out.println("Null");
}
优化后:
Optional<String> strOptional = Optional.of("jay@huaxiao");
strOptional.ifPresentOrElse(System.out::println, () -> System.out.println("Null"));
优化方案六:表驱动法
表驱动法,又称之为表驱动、表驱动方法。表驱动方法是一种使你可以在表中查找信息,而不必用很多的逻辑语句(if或Case)来把它们找出来的方法。以下的demo,把map抽象成表,在map中查找信息,而省去不必要的逻辑语句。
优化前:
if (param.equals(value1)) {doAction1(someParams);
} else if (param.equals(value2)) {doAction2(someParams);
} else if (param.equals(value3)) {doAction3(someParams);
}
// ...
优化后:
Map<?, Function<?> action> actionMappings = new HashMap<>(); // 这里泛型 ? 是为方便演示,实际可替换为你需要的类型// 初始化
actionMappings.put(value1, (someParams) -> { doAction1(someParams)});
actionMappings.put(value2, (someParams) -> { doAction2(someParams)});
actionMappings.put(value3, (someParams) -> { doAction3(someParams)});// 省略多余逻辑语句
actionMappings.get(param).apply(someParams);
优化方案七:优化逻辑结构,让正常流程走主干
优化前:
public double getAdjustedCapital(){if(_capital <= 0.0 ){return 0.0;}if(_intRate > 0 && _duration >0){return (_income / _duration) *ADJ_FACTOR;}return 0.0;
}
优化后:
public double getAdjustedCapital(){if(_capital <= 0.0 ){return 0.0;}if(_intRate <= 0 || _duration <= 0){return 0.0;}return (_income / _duration) *ADJ_FACTOR;
}
将条件反转使异常情况先退出,让正常流程维持在主干流程,可以让代码结构更加清晰。
优化方案八:策略模式+工厂方法消除if else
假设需求为,根据不同勋章类型,处理相对应的勋章服务,优化前有以下代码:
String medalType = "guest";if ("guest".equals(medalType)) {System.out.println("嘉宾勋章");} else if ("vip".equals(medalType)) {System.out.println("会员勋章");} else if ("guard".equals(medalType)) {System.out.println("展示守护勋章");}...
首先,我们把每个条件逻辑代码块,抽象成一个公共的接口,可以得出以下代码:
//勋章接口
public interface IMedalService {void showMedal();
}
我们根据每个逻辑条件,定义相对应的策略实现类,可得以下代码:
//守护勋章策略实现类
public class GuardMedalServiceImpl implements IMedalService {@Overridepublic void showMedal() {System.out.println("展示守护勋章");}
}
//嘉宾勋章策略实现类
public class GuestMedalServiceImpl implements IMedalService {@Overridepublic void showMedal() {System.out.println("嘉宾勋章");}
}
//VIP勋章策略实现类
public class VipMedalServiceImpl implements IMedalService {@Overridepublic void showMedal() {System.out.println("会员勋章");}
}
接下来,我们再定义策略工厂类,用来管理这些勋章实现策略类,如下:
//勋章服务工产类
public class MedalServicesFactory {private static final Map<String, IMedalService> map = new HashMap<>();static {map.put("guard", new GuardMedalServiceImpl());map.put("vip", new VipMedalServiceImpl());map.put("guest", new GuestMedalServiceImpl());}public static IMedalService getMedalService(String medalType) {return map.get(medalType);}
}
使用了策略+工厂模式之后,代码变得简洁多了,如下:
public class Test {public static void main(String[] args) {String medalType = "guest";IMedalService medalService = MedalServicesFactory.getMedalService(medalType);medalService.showMedal();}
}
15.工厂模式和构建者模式有什么区别?
一、建造者模式是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
二、建造者模式与工厂模式的区别:
工厂模式一般都是创建一个产品,注重的是把这个产品创建出来就行,只要创建出来,不关心这个产品的组成部分。从代码上看,工厂模式就是一个方法,用这个方法就能生产出产品。
建造者模式也是创建一个产品,但是不仅要把这个产品创建出来,还要关系这个产品的组成细节,组成过程。从代码上看,建造者模式在建造产品时,这个产品有很多方法,建造者模式会根据这些相同方法但是不同执行顺序建造出不同组成细节的产品。
总结,工厂模式关心整体,建造者模式关心细节。
16.静态代理和装饰器模式有什么区别?

17.说一下在哪儿用到多线程?
在项目开发中,多线程可以应用于以下几个方面:
-
并发处理:当需要同时处理多个任务或请求时,多线程可以提高系统的并发能力。例如,在Web应用程序中,可以使用多线程来处理多个并发的HTTP请求,提高处理效率。
-
后台任务:某些耗时的任务,如数据备份、数据清理、定时任务等,可以使用多线程来异步执行,以避免阻塞主线程的运行。这样可以提高系统的响应性和用户体验。
-
并行计算:对于需要大量计算或处理的任务,可以将其分解为多个子任务,并使用多线程同时执行这些子任务,以加快任务的完成速度。这在科学计算、图像处理、数据分析等领域常见。
-
IO密集型操作:当应用程序需要进行大量的IO操作(如读写文件、网络通信等)时,单线程处理可能导致阻塞,降低系统的响应性。使用多线程可以在等待IO操作完成的同时继续处理其他任务,提高整体的效率。
-
GUI应用程序:在图形界面应用程序中,使用多线程可以确保界面的响应性。例如,将耗时的计算或IO操作放在单独的线程中执行,以避免阻塞主线程,从而保持界面的流畅性。
需要注意的是,多线程编程需要考虑线程安全、资源竞争、死锁等并发编程的问题。合理地使用锁、同步机制、线程池等技术可以确保多线程的正确性和性能。此外,开发人员还应当根据具体的应用场景和需求,评估使用多线程带来的收益和开销,并进行合理的设计和优化。
18.你是怎么理解锁的?

19.线程是并行的还是并发的?
线程在运行时可能是并行也可能是并发,具体取决于操作系统和处理器的支持情况。下面分别介绍并发和并行的概念和特点:
并发:指多个线程在同一时间段内同时执行(虽然实际上只有一个处理器在工作),它们之间相互协作、互相影响,以完成给定任务。并发可以提高系统的性能和资源利用率,并且使程序更加灵活。
并行:指多个线程在同一时刻同时执行,各自独立运行,没有互相干扰或者同步的需求。并行需要多核处理器的支持,在多核处理器中,每个核心都有独立的处理能力,因此可以同时执行多个线程。
总之,如果一个计算机只有一个处理器,在同一时间只能执行一个线程,这时候即使有多个线程,它们也只能并发执行。而如果该计算机有多个处理器或是单个处理器支持超线程技术。
20.讲一下在哪里用到了单例,你觉得一个类在什么情况下使用单例?
保证一个类只有一个实例,并且提供一个访问该全局访问点
哪些地方用到了单例模式?
1. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
2. 应用程序的日志应用,一般都是单例模式实现,只有一个实例去操作才好,否则内容不好追加显示。
3. 多线程的线程池的设计一般也是采用单例模式,因为线程池要方便对池中的线程进行控制
4. Windows的(任务管理器)就是很典型的单例模式,他不能打开俩个
5. windows的(回收站)也是典型的单例应用。在整个系统运行过程中,回收站只维护一个实例。

21.下单成功后,前端怎么知道下单成功的?
在下单成功后,前端可以通过以下几种方式来获取下单成功的信息:
-
后端返回成功响应:在下单请求被后端服务器成功处理后,后端可以返回一个成功的响应给前端,通常是一个带有成功状态码和相关数据的JSON对象。前端可以通过解析该响应来获取下单成功的信息。
-
接收后端推送:后端可以通过实时通信机制(如WebSocket)向前端推送下单成功的消息。前端通过建立与后端的长连接,监听并处理服务器主动推送的消息,从而获取下单成功的信息。
-
轮询查询订单状态:前端可以定时向后端发送请求查询订单状态,以判断是否下单成功。后端在接收到查询请求后,根据订单状态进行响应。前端可以通过轮询的方式不断发送查询请求,直到获取到下单成功的状态或者超过一定的时间限制。
-
使用消息队列:在下单成功后,后端可以将下单成功的消息发布到消息队列中。前端可以订阅该消息队列,并在接收到消息时得知下单成功的信息。
需要注意的是,以上方法可以根据具体的业务需求和技术栈来选择和实现。同时,前端在获取下单成功的信息后,可能需要进行相应的界面更新或者跳转到订单详情页面等操作,以提供用户良好的交互体验。
22.消息队列消息乱序在哪儿遇见的?他为什么乱序?怎么解决?
消息队列中消息乱序可能出现在以下几个场景中:
-
生产者发送消息的顺序与消费者接收消息的顺序不一致:由于网络延迟或者消息队列本身的负载情况,消息可能会在队列中排队等待被消费者处理。如果消费者处理消息的速度较慢,那么后面发送的消息可能会在队列中等待,导致消费顺序与发送顺序不一致。
-
多个消费者并行消费消息:如果多个消费者同时从同一个队列中获取消息并进行处理,由于消费者之间的处理速度不同,可能会导致消息的处理顺序乱序。
-
消息队列的分区和分片:某些消息队列系统会对消息进行分区和分片存储,不同的分区或分片可能由不同的节点负责处理。在这种情况下,由于消息被分散存储和处理,可能会导致消息乱序。
解决消息乱序问题的方法包括:
-
使用消息排序字段:在消息中添加一个排序字段,例如序号或时间戳,消费者按照该字段进行排序后再进行处理。
-
单一消费者模式:只允许一个消费者从队列中获取消息并进行处理,确保顺序性。
-
消息分区:将消息按照某种规则进行分区存储,确保相同分区的消息按照顺序进行处理。
-
消息分组和分流:将消息按照某种规则进行分组或者分流,确保同一组或者流的消息被同一个消费者处理。
-
使用有序消息队列:一些消息队列系统提供了有序消息的支持,可以保证消息的顺序性。
总之,解决消息乱序问题需要根据具体情况选择合适的方法,包括添加排序字段、限制消费者数量、消息分区等。选择合适的解决方案可以确保消息队列中的消息按照正确的顺序进行处理。
23.平时做过代码优化吗?

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
