# 京东面试题

# 如何选择ArrayList,LinkedList 一面

参考答案

  • ArrayList数据结构是数组,LinkedList数据结构是链表。
  • 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  • 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
  • ArrayList支持动态扩容默认原容量的1.5倍,LinkedList是链表并且实现了栈和队列的相关方法,不存在扩容问题。

# ArrayList会如何进行扩容呢

参考答案

ArrayList 的内部实现,其实是用一个对象数组进行存放具体的值,然后用一种扩容的机制,进行数组的动态增长。 其扩容机制可以理解为,如果元素的个数,大于其容量,则把其容量扩展为原来容量的1.5倍。

# JDK中Arrays里sort方法默认是什么排序算法

参考答案

JDK1.8中的Arrays.sort()的排序方法就是使用的快排,当待排序数量大于286的时候,看连续升序和降序性好不好,好则归并,否则快速;当待排序数量小于286的时候,如果待排序数据量大于47的时候采用快速排序,小于47的时候使用插入排序。

# JVM中是怎么判断对象可回收的

参考答案

GC ROOT可达性分析

# JVM中垃圾收集有哪些算法,各自的特点

参考答案

  • 标记-清除
  • 标记-整理
  • 复制算法

# 线程池的创建参数和对线程行为的影响

请概述线程池的创建参数和对线程行为的影响,怎么样合理配置一个线程池的参数?

参考答案

线程池的创建参数

  • corePoolSize:核心线程数量。
  • maximumPoolSize:最大线程数量。
  • workQueue:阻塞队列,会对线程运行过程产生重大影响。

参数对线程行为的影响

  • 当前运行的线程数小于corePoolSize时,直接创建新线程处理任务,即使其他线程空闲。
  • 当corePoolSize  <= 当前线程数 <= maximunPoolSize ,只有当workQueue满的时候才创建新的线程处理任务。
  • 当corePoolSize = maximumPoolSize时,创建的线程池大小是固定的,如果有新任务提交,当workQueue没满的时候,将请求放入workQueue,等待线程空闲后去取出任务进行处理,如果满了,还有新任务提交,则通过拒绝策略参数来指定策略去处理任务。
  • workQueue是线程池中保存等待执行的任务的阻塞队列,当提交新任务到线程池后,线程池会根据当前线程池中正在运行的线程数量,决定该任务的处理方式,总共有3种:直接切换,用无界队列,用有界队列。

扩展阅读

如果要想降低系统资源的消耗(包括CPU的使用率,操作系统资源的消耗,上下文环境切换的开销等),可以设置较大的队列容量和较小的线程池容量,但这样也会降低线程处理任务的吞吐量。

如果提交的任务经常发生堵塞,那么可以考虑通过调用setMaximunPoolSize()方法来重新设定线程池容量。如果队列的容量设置的太小,通常需要将线程池的容量设置大一点,这样CPU的使用率会相对的高一鞋。但如果线程池的容量设置的过大,则在提交的任务数量太多的情况下,并发量会增加,那么线程之间的调度就是一个要考虑的问题,因为这样反而有可能降低处理任务的吞吐量。

# TCP的三次握手过程,为什么需要三次

参考答案

网络不稳定等外界原因会导致消息阻塞,因此需要三次即A再次发送确认收到B消息的握手过程。

# 查询优化的基本思路是什么

参考答案

  • 索引的优化
  • Explain执行计划
  • 慢SQL查询优化
  • SQL语句的优化
  • MySQL自身连接参数的优化,缓冲区大小设置等

# 索引优化的几点原则

参考答案

  • 负向条件查询不能使用索引。

    #负向条件查询
    select from order where status!=0 and status!=1
    #可以优化为 in 查询:
    select from order where id not in/not exists 
    
  • 前导模糊查询不能使用索引,而非前导模糊查询则可以。

    #前导模糊查询,不走索引
    select from order where desc like '%XX'; 
    #非前导模糊查询,正常索引
    select from order where desc like 'XX%'; 
    
  • 数据区分度不大的字段不宜使用索引,如:性别。

    select from user where sex=1
    

    原因:性别只有男,女,每次过滤掉的数据很少,不宜使用索引。

  • 在属性上进行计算不能命中索引,可优化为值计算。

    select from order where YEAR(date) < = '2017'#可以优化为值计算:
    select from order where date < = CURDATE()select from order where date < = '2017-01-01'
  • 如果业务大部分是单条查询,使用Hash索引性能更好,例如用户中心:

    select from user where uid=?
    select from user where login_name=?
    

    原因:B-Tree 索引的时间复杂度是 O(log(n));Hash 索引的时间复杂度是 O(1)

  • 复合索引最左前缀,并不是值 SQL 语句的 where 顺序要和复合索引一致 用户中心建立了(login_name, passwd)的复合索引

    #都能够命中索引
    select from user where login_name=? and passwd=?
    select from user where passwd=? and login_name=?
    #命中索引,满足复合索引最左前缀
    select from user where login_name=?
    #不能命中索引,不满足复合索引最左前缀
    select from user where passwd=?
    
  • 使用 ENUM 而不是字符串。

  • 如果明确知道只有一条结果返回,limit 1 能够提高效率。

    #只有一条结果返回
    select from user where login_name=?
    #可以优化为
    select from user where login_name=? limit 1
    

    原因:你知道只有一条结果,但数据库并不知道,明确告诉它,让它主动停止游标移动。

  • 强制类型转换会全表扫描。

    select from user where phone=13800001234
    

# 为什么需要代理模式 二面

参考答案

在请求之前或者请求之后进行一些额外的工作,又不想业务类做一些不相关的处理,因此需要代理类来完成,包括对业务做一些增强处理等等

# 讲讲静态代理模式的优缺点

参考答案

静态代理虽然效率较高,但是代理对象和被代理对象都实现的一个接口,而这些代理类中的代码几乎是一致的。这在大型系统中将会产生很大的维护问题。

# mybatis二级缓存基于哪种设计模式

mybatis中在配置二级缓存的时候,可以配置缓存淘汰策略,缓存大小、失效时间、是否加同步控制、是否进行同步控制等附加功能,请问基于什么设计模式实现的

参考答案

# 说出几个在JDK库中使用的设计模式

参考答案

创建型模式:

  • 抽象工厂模式(AbstractFactory),创建一组有关联的对象实例。

    • java.sql.DriverManager#getConnection()
    • java.sql.Connection#createStatement()
    • java.sql.Statement#executeQuery()
    • java.util.Calendar#getInstance()
  • 建造者模式(Builder),主要用来简化一个复杂的对象的创建。

    • java.lang.StringBuilder#append()
    • java.lang.StringBuffer#append()
    • java.sql.PreparedStatement
  • 工厂方法模式(FactoryMethod),简单来说,按照需求返回一个类型的实例。

    • java.lang.Proxy#newProxyInstance()
    • java.lang.Object#toString()
    • java.lang.Class#newInstance()
    • java.lang.reflect.Array#newInstance()
    • java.lang.Boolean#valueOf(String)
    • java.lang.Class#forName()
  • 原型模式(Prototype),使用自己的实例创建另一个实例(或把已有实例的值拷贝过去)。

    • java.lang.Object#clone()
  • 单例模式 (Singleton),只允许一个实例。

    • java.lang.Runtime#getRuntime()

结构型模式:

  • 适配器模式(Adapter),使不兼容的接口相互兼容。

    • java.io.InputStreamReader(InputStream)
    • java.io.OutputStreamWriter(OutputStream)
    • java.util.Arrays#asList()
  • 组合模式(Composite),一致地对待组合对象和独立对象。 — org.w3c.dom

  • 外观模式(Façade),封装一组交互类,一致地对外提供接口。

    • java.util.logging包
  • 装饰模式(Decorator),为类添加新的功能;防止类继承带来的过度增长。

    • java.io包
    • java.util.Collections#synchronizedList(List)
  • 代理模式(Proxy),透明调用被代理对象,无须知道复杂实现细节。

    • java.lang.reflect.Proxy
    • RMI

行为型模式:

  • 职责链模式(Chain of responsibility),把一个对象在一个链接传递直到被处理。

    • java.util.logging.Logger#log()
    • javax.servlet.Filter#doFilter()
  • 命令模式(Command),把一个或一些命令封装到一个对象中。

    • java.lang.Runnable
  • 解释器模式(Interpreter),用一组类代表某一规则。

    • java.util.regex.Pattern
  • 迭代器模式(Iterator Pattern),用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。

    • java.util.Iterator
  • 中介者模式(Mediator),定义一个中介对象来封装一系列对象之间的交互,使原有对象之间耦合松散。

    • java.util.Timer
    • java.util.concurrent.Executor#execute(Runnable command)
  • 备忘录模式(Memento),保持对象状态,需要时可恢复

    • java.util.Date
    • java.io.Serializable
  • 观察者模式(Observer),通知对象状态改变。

    • java.util.Observer
    • java.util.Observable
    • java.util.EventListener 的所有子类
  • 策略模式(Strategy),提供不同的算法。

    • java.util.Comparator#compare()
    • javax.servlet.http.HttpServlet
    • javax.servlet.Filter#doFilter()
    • ThreadPoolExecutor中的四种拒绝策略
  • 模板方法模式(Template Method),定义算法的结构,子类只实现不同的部分(钩子方法)

    • java.io.InputStream 的所有非抽象方法
    • java.io.OutputStream 的所有非抽象方法
    • java.io.Reader 的所有非抽象方法
    • java.io.Writer 的所有非抽象方法
    • java.util.AbstractList 的所有非抽象方法
    • java.util.AbstractSet 的所有非抽象方法
    • java.util.AbstractMap 的所有非抽象方法
    • javax.servlet.http.HttpServlet#do开头的方法

# 请简单谈谈Redis 持久化机制

参考答案

  • RDB

    RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。RDB是Redis默认的持久化方式,会在对应的目录下生产一个dump.rdb文件,重启会通过加载dump.rdb文件恢复数据。

  • AOF

    AOF持久化是以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,文件中可以看到详细的操作记录。她的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

# Redis的缓存失效策略

参考答案

当缓存需要被清理时(比如空间占用已经接近临界值了),需要使用某种淘汰算法来决定清理掉哪些数据。常用的淘汰算法有下面几种:

  • FIFO:First In First Out,先进先出。判断被存储的时间,离目前最远的数据优先被淘汰。
  • LRU:Least Recently Used,最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。
  • LFU:Least Frequently Used,最不经常使用。在一段时间内,数据被使用次数最少的,优先被淘汰。

# 如何使用Redis实现分布式锁

参考答案

分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。

Redis的分布式锁

public class RedisTool {
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
}
  • 第一个为key,我们使用key来当锁,因为key是唯一的。

  • 第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。

  • 第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;

  • 第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。

  • 第五个为time,与第四个参数相呼应,代表key的过期时间。

总的来说,执行上面的set()方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。

# 什么是缓存雪崩,如何解决

参考答案

如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。 造成缓存雪崩的常见业务场景:

  • 流量突增
  • 缓存集中失效
  • 线程同步调用
  • 程序bug
  • 服务器故障
  • 数据库压力

解决方案:

# MySQL读写分离

参考答案

有没有做MySQL读写分离?如何实现mysql的读写分离?MySQL主从复制原理?

  • MySQL读写分离

    • 通过MyCat配置schema.xml,添加writeHost 和readHost 相关配置。
  • MySQL主从复制原理

    • 第一步:master在每个事务更新数据完成之前,将该操作记录串行地写入到binlog文件中。
    • 第二步:salve开启一个I/O Thread,该线程在master打开一个普通连接,主要工作是binlog dump process。如果读取的进度已经跟上了master,就进入睡眠状态并等待master产生新的事件。I/O线程最终的目的是将这些事件写入到中继日志中。
    • 第三步:SQL Thread会读取中继日志,并顺序执行该日志中的SQL事件,从而与主数据库中的数据保持一致。
  • MySQL支持的复制类型

  • 基于语句的复制:在主服务器上执行的SQL语句,在从服务器上执行同样的语句。MySQL默认采用基于语句的复制,效率比较高。一旦发现没法精确复制时,会自动选着基于行的复制。

  • 基于行的复制:把改变的内容复制过去,而不是把命令在从服务器上执行一遍。 从mysql5.0开始支持。

  • 混合类型的复制: 默认采用基于语句的复制,一旦发现基于语句的无法精确的复制时,就会采用基于行的复制。

# 单例模式的双重检查模式不安全实现的原因

参考答案

重排序的问题

# 请概述AQS

参考答案

AQS(Abstract Queue Synchronized),异步抽象队列

# 死锁与活锁的区别,死锁与饥饿的区别

参考答案

  • 死锁: 是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。死锁发生的四个条件如下,

    • 互斥条件
    • 请求和保持条件
    • 不剥夺条件
    • 环路等待条件
  • 活锁:是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。

  • 饥饿:是指如果线程T1占用了资源R,线程T2又请求封锁R,于是T2等待。T3也请求资源R,当T1释放了R上的封锁后,系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后,系统又批准了T4的请求......,T2可能永远等待。(ReentrantLock可以设置公平锁)

# 说几条你遵循的多线程最佳实践

参考答案

  • 给你的线程起个有意义的名字。
  • 多用同步类少用wait 和 notify。
  • 多用并发集合少用同步集合。

# 什么是cap理论,和BASE理论

参考答案

cap理论 一致性(Consistency)、可用性(Availability)、分区容忍性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。

  • 一致性:在分布式系统中的所有数据备份,在同一时刻是否同样的值。
  • 可用性:在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。
  • 分区容忍性:以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

BASE理论 核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

  • Basically Available(基本可用)
  • Soft state(软状态)
  • Eventually consistent(最终一致性)

# 说说你了解的几种分布式事务

参考答案

  • 2PC协议
  • 3PC协议
  • 补偿事务(TCC),针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。
  • MQ事务消息,实现了最终一致性,不需要依赖本地数据库事务。
  • LCN实现,服务消费方加注解 @TxcTransaction。

# 你认为自身的优势在哪 三面

参考答案

略......

# 如何同一个似乎总是不能按时完成工作的员工一起工作

参考答案

略......

# 你怎样在计划中运用新技术

参考答案

略......

# 为什么离职四面

参考答案

略......

# 有没有更好的机会

参考答案

略......

# 会不会选择京东,选择京东原因是什么

参考答案

略......