JVM
内存分区
程序计数器和本地方法栈 略
栈:基本数据类型、对象的引用
堆:对象实例、数组
方法区:已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等
多线程
线程生命周期
- 新建:被new出来
- 就绪:调用了start()后
- 运行:开始执行run()
- 阻塞:wait()、同步锁、sleep()
- 死亡:调用stop()、线程抛出异常或错误、run()执行完正常结束
sleep 与 wait 区别
- 对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于Object 类中的。
- sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
- 在调用 sleep()方法的过程中,线程不会释放对象锁。
- 而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
线程安全问题
线程安全问题指的是在某一线程从开始访问到结束访问某一数据期间,该数据被其他的线程所修改,那么对于当前线程而言,该线程就发生了线程安全问题,表现形式为数据的缺失,数据不一致等。
解决思路:
1)尽量不使用共享变量,将不必要的共享变量变成局部变量来使用。
2)使用synchronized关键字同步代码块,或者使用jdk包中提供的Lock为操作进行加锁。
3)使用ThreadLocal为每一个线程建立一个变量的副本,各个线程间独立操作,互不影响。
ThreadLocal:是一个类似HashMap的数据结构,只能保存一个k-v键值对,线程之间互不干扰。用完记得调用remove()方法,防止内存泄漏。
乐观锁和悲观锁
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁。
悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。 java中的悲观锁就是Synchronized。
synchronized和Lock的区别
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
设计模式
设计模式面试题——知乎
使用设计模式是为了可重用代码、让代码更有条理、性能更佳。
单例
由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
//饿汉式
public class Demo1 {
// 类初始化时,会立即加载该对象,线程安全,调用效率高
private static Demo1 demo1 = new Demo1();
private Demo1() {
System.out.println("私有Demo1构造参数初始化");
}
public static Demo1 getInstance() {
return demo1;
}
public static void main(String[] args) {
Demo1 s1 = Demo1.getInstance();
Demo1 s2 = Demo1.getInstance();
System.out.println(s1 == s2);
}
}
//懒汉式
public class Demo2 {
//类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
private static Demo2 demo2;
private Demo2() {
System.out.println("私有Demo2构造参数初始化");
}
public synchronized static Demo2 getInstance() {
if (demo2 == null) {
demo2 = new Demo2();
}
return demo2;
}
public static void main(String[] args) {
Demo2 s1 = Demo2.getInstance();
Demo2 s2 = Demo2.getInstance();
System.out.println(s1 == s2);
}
}
工厂模式
- 工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。
- 利用工厂模式可以降低程序的耦合性,为后期的维护修改提供了很大的便利。
将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
代理模式
通过代理控制对象的访问,可以在这个对象调用方法之前、调用方法之后去处理/添加新的功能。
代理在原有代码乃至原业务流程都不修改的情况下,直接在业务流程中切入新代码,增加新功能。
Spring
Spring AOP
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。SpringCloud
服务划分
将微服务划分为外部服务和内部服务
外部服务:以终端划分的外部服务,如api-admin、api-pc、api-applets、api-app、api-h5,提供对外(用户)的API 接口数据 。
外部服务提供Feign服务通信调用内部服务获取数据。
内部服务:如订单服务、用户服务、商品服务、内容服务等内部服务,提供增删改查(搜索)等基本数据库操作微服务方案
服务注册和发现&配置中心 Nacos
- 路由网关 Gateway
- 分布式 http feign
- 熔断和降级 Sentinel
- 消息中间件 RabbitMQ
SQL
中间件
Redis
- redis是单线程,不需要各种锁的性能消耗。
Redis是一个key-value存储系统,支持的数据类型,如:String、list、set、zset、hash。
RabbitMQ
应用场景
功能解耦、流量削峰、异步处理
MQ与多线程实现异步的区别
多线程实现异步会消耗cpu资源,当单机配置达到瓶颈时就会影响到核心业务线程,发生cpu竞争问题
而MQ实现异步是完全解耦的,可以在多个服务器上异步处理不同的业务,因此在分布式开发中经常使用。
如何避免消息堆积
批量获取消息,减少网络传输次数
使用场景抢购活动,削峰填谷,防止系统崩塌。
- 延迟信息处理,比如 10 分钟之后给下单未付款的用户发送邮件提醒。
- 解耦系统,对于新增的功能可以单独写模块扩展,比如用户确认评价之后,新增了给用户返积分的功能,这个时候不用在业务代码里添加新增积分的功能,只需要把新增积分的接口订阅确认评价的消息队列即可,后面再添加任何功能只需要订阅对应的消息队列即可。
防重复提交
比如订单付款业务,保证客户端与服务端的交易一致性,避免多次扣款。通过业务逻辑进行处理:
1、查询订单支付状态
2、如果判断状态已经支付,直接返回结果
3、如果判断状态未支付,支付扣款并保存流水并返回支付结果
问题:保证幂等的方案是分成两步的,第2步依赖第1步的结果,无法保证原子性
因此高并发情况下可能出现的情况:在第一次请求第2步订单状态还没有修改为已支付状态第二次请求就已经到来查询,就会出现第二次请求查询时状态还是未支付状态,进行了重复支付,破坏了幂等性
可以通过分布式锁来解决,实现支付时先查询redis中是否存在订单id的key,如果不存在则可以添加订单id的key获取锁并进行查询订单支付状态,进行支付等操作,支付完成后删除redis中订单id的key进行释放锁。
这样使用redis实现分布式锁,这次订单请求完成前,下次订单请求只能等待获取锁。