开放性问题


一、Java基础

二、并发

1、是否用过synchronized

(1)回答思路

  • synchronized:可重入锁、非公平锁

  • 用于静态方法锁 class 类,用于非静态方法锁对象

  • 悲观锁、重量级锁

  • wait()notify()notifyAll() 必须要在 Synchronized 中使用

synchronized 实际工作中使用的场景较少,更多的是使用 redis 分布式锁。

(2)字典缓存

实际应用场景:字典、系统参数缓存,单机系统情况下,缓存到内存中。

当修改了参数时,清除全部缓存,并重新加载缓存

在清除缓存,并且还未重新加载时,如果其他线程来获取字典数据,则获取到null

为了解决这个问题,必须保证清除缓存和重新加载两个步骤的原子性,必须一起执行,所以修改字典的方法和获取字典的方法必须加锁。

(3)懒汉式单例-双重检查锁

单例模式

下面为懒汉式双重检查锁

public class DoubleChecked {
    private volatile static DoubleChecked instance;

    // 防止产生多个对象
    private DoubleChecked(){};

    public static DoubleChecked getInstance() {
        if (instance == null) {                         //Single Checked
            synchronized (DoubleChecked.class) {
                if (instance == null) {                 //Double Checked
                    instance = new DoubleChecked();
                }
            }
        }
        return instance ;
    }
}

(4)Spring循环依赖

Spring 源码解决循环依赖获取 Bean 的时候

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Quick check for existing instance without full singleton lock
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // Consistent creation of early reference within full singleton lock
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

(5)延伸问题

  • 锁升级
  • redis 分布式锁
  • volatile
  • Spring 循环依赖

2、聊聊redis分布式锁

3、是否使用过多线程(池)

(1)回答思路

实际工作中使用多线程一般是采用线程池的方式。

  • 七大参数、三大创建方法、四个拒绝策略
  • 一个项目中一个功能一个线程池

(2)大批量数据导出

客户每月数据量五万以上,每月初需导出上个月的数据,由于数据量大,导出时还得对数据进行处理,该处理步骤耗时较长。最大数据量峰值能达到接近10万。导出的时候,数据量超过4万会出现超时问题(30秒超时)。通过线程池批量处理的方式加快相应速度,每个线程处理1万条数据。能做到导出10万条数据时间在20到25秒之间。

所以采用线程池 + CompletableFuture 实现导出功能。

线程池参数设置:

  • 核心线程数:0,每月导一次,不需要线程长期存活
  • 最大线程数:9(CPU核心数 + 1,尽量跑满cpu)
  • 阻塞队列:队列长度128,为提高响应速度,不应缓存任务
  • 最大空闲时间:60(默认)
  • 拒绝策略:AbortPolicy
  • 线程工厂:默认
private final LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(128);

@Bean
public ThreadPoolExecutor threadPoolExecutor(){
    return new ThreadPoolExecutor(
        0,
        9,
        60, 
        TimeUnit.SECONDS,
        workQueue,
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy());
}
List<T> data = list(condition);

int step = 5000;

for (int i = 0; i < data.size(); i += step) {

    CompletableFuture<List> future = CompletableFuture.supplyAsync(() -> {
        // 处理数据
        for (int z = i; z < i + step; i ++) {

        }

        return list;
    }, threadPoolExecutor);

    futures.add(future);
}

List<T> resList = new ArrayList<>();
// 收集处理结果
futures.forEach(result -> {
    if(result != null) resList.addAll(result.join());
});

三、JVM

1、是否做过JVM调优

没,99%的项目不需要

四、数据库

1、是否做过sql优化

XX项目因后期数据量增大,做过一段时间的 sql 优化

基本思路:

  • 先检查有没有冗余的查询字段或者联表
  • EXPLAIN 关键字确保查询使用索引
  • 优化sql,或新增字段索引

在进行优化时,遇到过一个比较懵逼的问题,至今映像深刻,两张表在联表的情况下,两张表联表字段类型不同,Long 和 varchar 类型,导致查询不使用索引,字段类型不同不会使用索引。

(1)EXPLAIN结果说明

这里对explain的结果进行简单说明:

select_type:查询类型

  • SIMPLE 简单查询

  • PRIMARY 最外层查询

  • UNION union后续查询

  • SUBQUERY 子查询

type:查询数据时采用的方式

  • ALL 全表(性能最差)
  • index 基于索引的全表
  • range 范围 (< > in)
  • ref 非唯一索引单值查询
  • const 使用主键或者唯一索引等值查询

possible_keys:可能用到的索引

key:真正用到的索引

rows:预估扫描多少行记录

key_len:使用了索引的字节数

Extra:额外信息

  • Using where 索引回表
  • Using index 索引直接满足条件(覆盖索引)
  • Using filesort 需要排序
  • Using temprorary 使用到临时表

(2)索引失效场景

  • 最左前缀原则:联合索引,查询从索引的最左列开始,并且不跳过索引中的列,否则索引失效
  • or
  • 左模糊查询
  • 带有运算
  • 使用函数
  • 表或字段编码不一致
  • 字段数据类型不一致
  • in、not in、exists、not exists、<、>、<=、>=、between and等,根据数据分布决定
  • is null、is not null 也是根据数据分布决定

(3)索引优化技术

覆盖索引

只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。

explain的输出结果Extra字段为Using index时,能够触发索引覆盖。

索引下推

索引下推就是指在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数来提高查询效率。

2、索引分类

(1)按表列属性分类

  • 主键索引
  • 唯一索引
  • 普通索引
  • 联合索引
  • 全文索引

(2)按存储结构分类

  • 聚簇索引:一般为主键索引列,B+树子节点存储数据
  • 非聚簇索引(二级索引):其他索引列,B+树子节点存储主键和索引字段

五、RabbitMQ

1、异步发送短信

2、异步存储日志

目的:提高接口响应速度,解耦,日志与业务解耦,让业务代码专注于业务

六、ES

1、业务、接口日志存储

2、ELK日志,全链路追踪

3、开户行模糊查询

七、Spring源码

1、Ioc启动流程

Spring的IoC容器的启动过程,核心流程是将bean的配置项从不同渠道,包括XML、注解或者配置文件中读取和解析后,生成 BeanDefinition 的过程,在这过程中IoC容器会进行refresh操作,这个过程可以设置一些 BeanPostProcesser 的前置或后置操作,在执行完这些操作后,BeanDefinition 就会被注册到 BeanDefinitionRegistry 容器中。

整体IoC容器的启动过程分为3个阶段:定位—>加载–>注册

  • 定位:通过 ResourceLoader 来完成资源的定位,后续的Bean配置项透视通过 Resource 资源文件来读取和解析的。
  • 加载:ApplicationContext 容器调用 refresh() 方法,解析Bean配置信息将其解析成BeanDefinition,然后调用一系列容器、Bean级别的前置后置处理器,包括调用BeanFactoryPostProcessor 的前置操作。
  • 注册:BeanDefinition 实例注册,将生成的 BeanDefinition 放到容器的缓存池BeanDefinitionRegistry 中。
  • 调用已注册的 BeanFactoryPostProcessor
  • 注册 BeanPostProcessor
  • Bean 实例化
  • Bean 初始化

2、Bean的生命周期

3、SpringMVC执行流程

面试官你好,我叫***,学历是本科学历,至今已从事Java相关工作三年了,参与过多个项目,并在部分项目中担任过研发组长职位,并且在业余时间也会积极学习研究新知识,有撰写博客的习惯,至今个人博客已有260余篇文章。

我在上家已经工作了3年多了,个人感觉在这边技术提升到达了瓶颈,个人希望在职业生涯中获得更好的发展机会,离开当前工作是为了开阔视野,并寻求更好的学习机会。

八、网络协议

1、TCP三次握手四次挥手


  目录