什么是多态

  • 多态是面向对象的一个重要的特征,是指父类中定义的属性和方法被子类继承后可以具有不同的数据类型或者表现出不同的行为。
  • 对面向对象来说,多态分为编译时多态和运行时多态。
  • 编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法。
  • 运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的重写,它是子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
  • 多态的作用就是把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码。

DHCP ARP

DHCP(动态主机配置协议)是一个局域网的网络协议。指的是由服务器控制一段IP地址范围,客户机登录服务器时就可以自动获得服务器分配的IP地址和子网掩码。

地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议


application.properties和application.yml文件的区别

  1. 内容格式比较:

    1. .properties文件,通过.来连接,通过=来赋值,结构上,没有分层的感觉,但比较直接。
    2. .yml文件,通过:来分层,结构上,有比较明显的层次感,最后key赋值的:后需要留一个空格
  2. 执行顺序

    1. 如果工程中同时存在application.properties文件和 application.yml文件,yml文件会先加载,而后加载的properties文件会覆盖yml文件。

application.properties与bootstrap.properties的区别

两者主要区别是加载顺序不同,bootstrap.properties在application.properties 之前加载,bootstrap.properties用于应用程序上下文的引导阶段


#{}和${}的区别

  • ${}是properties文件中的变量占位符,他可以作用于标签属性内部和sql内部,属于静态文本替换。
  • #{}是sql参数占位符,Mybatis会把SQL中的#{}替换为?号,在sql执行前会使用PreparedStatement的参数设置方法按序给sql的?号占位符设置参数值。


sleep() 和 wait() 有什么区别?

  1. sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁
  2. wait是Object类的方法,对此对象调用wait方法导致本线程释放对象锁,只有针对此对象发出notify方法(或notifyAll)后本线程才会苏醒进入运行状态。

缓存穿透、缓存击穿、缓存雪崩

缓存穿透

描述:访问一个缓存和数据库都不存在的 key,此时会直接打到数据库上,并且查不到数据,没法写缓存,所以下一次同样会打到数据库上。

此时,缓存起不到作用,请求每次都会走到数据库,流量大时数据库可能会被打挂。此时缓存就好像被“穿透”了一样,起不到任何作用。

解决方案:

1、接口校验。在正常业务流程中可能会存在少量访问不存在 key 的情况,但是一般不会出现大量的情况,所以这种场景最大的可能性是遭受了非法攻击。可以在最外层先做一层校验:用户鉴权、数据合法性校验等,例如商品查询中,商品的ID是正整数,则可以直接对非正整数直接过滤等等。

2、缓存空值。当访问缓存和DB都没有查询到值时,可以将空值写进缓存,但是设置较短的过期时间,该时间需要根据产品业务特性来设置。

3、布隆过滤器。使用布隆过滤器存储所有可能访问的 key,不存在的 key 直接被过滤,存在的 key 则再进一步查询缓存和数据库。


缓存击穿

描述:某一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。

解决方案:

1、加互斥锁。在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。

2、热点数据不过期。直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。


缓存雪崩

描述:大量的热点 key 设置了相同的过期时间,导在缓存在同一时刻全部失效,造成瞬时数据库请求量大、压力骤增,引起雪崩,甚至导致数据库被打挂。

缓存雪崩其实有点像“升级版的缓存击穿”,缓存击穿是一个热点 key,缓存雪崩是一组热点 key。

解决方案:

1、过期时间打散。既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。

2、热点数据不过期。该方式和缓存击穿一样,也是要着重考虑刷新的时间间隔和数据异常如何处理的情况。

3、加互斥锁。该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可。


HTTP 与 HTTPS 区别

  • HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
  • 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。
  • HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
  • http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
  • HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,要比较 HTTPS 比 HTTP 要更耗费服务器资源。

什么是反射,优缺点?

在运行状态中,对于任意一个类都能知道它的所有属性和方法,对于任意一个对象都能调用它的任意方法和属性,这种动态获取信息及调用对象方法的功能称为反射。

  • 优点:反射提高了程序的灵活性和扩展性,降低耦合性,提高自适应能力。 它允许程序创建和控制任何类的对象,无需提前硬编码目标类;

  • 缺点:

    • 性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。
    • 缺点是破坏了封装性以及泛型约束。

反射是框架的核心,Spring 大量使用反射。


线程池

线程池的创建有两种方式,《阿里巴巴Java开发手册》中强制线程池不允许使用Executor去创建,而是通过ThreadPoolExecutor的方式创建。

  • 方式一:通过ThreadPoolExecutor构造方法来实现
  • 方式二:通过Executor框架的工具类Executors来实现,可以通过该工具类创建三种类型的ThreadPoolExecutor:
    1. FixedThreadPool:
      1. 该方法返回一个固定线程数量的线程池。
      2. 该线程池中的线程数量始终不变。
      3. 当有一个新任务提交时,线程池中如果有线程空闲,则,立即执行。
      4. 如果没有,就把新的任务暂存在一个任务队列中,待有线程空闲时,再来处理队列中的任务。
    1. SingleThreadExecutor:
      1. 该方法返回只有一个线程的线程池。
      2. 当有一个新任务提交时,任务会被保存在任务队列中,等到线程空闲,再来处理队列中的任务
    1. CachedThreadPool:
      1. 该方法可以返回一个可根据实际情况调整线程数量的线程池。
      2. 线程池中的线程数量不确定,但是如果有空闲线程可以复用,会优先复用可复用的线程。
      3. 如果所有线程都在运行,又有新的任务提交,则会创建新的线程处理任务。
      4. 所有线程在当前任务执行完毕后,都会返回线程池进行复用。

Jdk1.7与Jdk1.8中HashMap区别

  1. 最重要的一点是底层结构不一样,1.7是数组+链表,1.8则是数组+链表+红黑树结构;
  2. jdk1.7中当哈希表为空时,会先调用inflateTable()初始化一个数组;而1.8则是直接调用resize()扩容;
  3. 插入键值对的put方法的区别,1.8中会将节点插入到链表尾部,而1.7中是采用头插;
  4. jdk1.7中的hash函数对哈希值的计算直接使用key的hashCode值,而1.8中则是采用key的hashCode异或上key的hashCode进行无符号右移16位的结果,避免了只靠低位数据来计算哈希时导致的冲突,计算结果由高低位结合决定,使元素分布更均匀;
  5. 扩容时1.8会保持原链表的顺序,而1.7会颠倒链表的顺序;而且1.8是在元素插入后检测是否需要扩容,1.7则是在元素插入前;
  6. jdk1.8是扩容时通过hash&cap==0将链表分散,无需改变hash值,而1.7是通过更新hashSeed来修改hash值达到分散的目的;
  7. 扩容策略:1.7中是只要不小于阈值就直接扩容2倍;而1.8的扩容策略会更优化,当数组容量未达到64时,以2倍进行扩容,超过64之后若桶中元素个数不小于7就将链表转换为红黑树,但如果红黑树中的元素个数小于6就会还原为链表,当红黑树中元素不小于32的时候才会再次扩容。

可重入锁说一下

可重入锁又叫做递归锁,可重入 就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。而锁的操作粒度是”线程”,而不是调用,同一个线程再次进入同步代码的时候,可以使用自己已经获取到的锁,这就是可重入锁。


说一下Executor框架

  • Executor框架是在Java5中引入的,可以通过该框架来控制线程的启动,执行,关闭,简化并发编程。
  • Executor框架把任务提交和执行解耦,要执行任务的人只需要把任务描述清楚提交即可,任务的执行提交人不需要去关心。
  • 通过Executor框架来启动线程比使用Thread更好,更易管理,效率高,避免this逃逸问题。
  • Executor的实现还提供了对生命周期的支持,以及统计信息收集,应用程序管理机制和性能监视等机制。

什么是粘包,怎么解决?

粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。

解决:

  • 发送方:对于发送方造成的粘包问题,可以通过关闭Nagle算法来解决,使用TCP_NODELAY选项来关闭算法。

  • 接收方:接收方没有办法来处理粘包现象,只能将问题交给应用层来处理。

  • 应用层:

    • 循环处理,应用程序从接收缓存中读取分组时,读完一条数据,就应该循环读取下一条数据,直到所有数据都被处理完成。
    • 格式化数据:每条数据有固定的格式(开始符,结束符),这种方法简单易行,但是选择开始符和结束符时一定要确保每条数据的内部不包含开始符和结束符。
    • 发送长度:发送每条数据时,将数据的长度一并发送,例如规定数据的前4位是数据的长度,应用层在处理时可以根据长度来判断每个分组的开始和结束位置

分页和分段

  1. 分页储存管理方式
    在该方式中,将用户程序地址空间分为若干固定大小的区域,称为“页”或“页面”。典型的页面大小为1KB。相应的,也将内存空间分为若干个物理块或页框(frame),页和块的大小相同。这样可将用户程序的任一页放入任一物理块中,实现了离散分配。

  1. 分段储存管理方式
    这是为了满足用户要求而形成的一种储存管理方式。它把用户程序的地址空间分为若干个大小不同的段,每段可定义一组相对完整的信息。在储存器分配时,以段为单位,这些段在内存中可以不相邻接,所以也同样实现了离散分配。
  2. 两者的比较
  • 对程序员透明:分页透明,但是分段需要程序员显式划分每个段。
  • 地址空间的维度:分页是一维地址空间,分段是二维的
  • 大小是否可以改变:页的大小不可变,段的大小可以动态改变
  • 出现的原因:分页主要用于实现虚拟内存,从而获得更大的地址空间;分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护

Mybatis 5

Q1:Mybatis 的优缺点?

优点

相比 JDBC 减少了大量代码量,减少冗余代码。

使用灵活,SQL 语句写在 XML 里,从程序代码中彻底分离,降低了耦合度,便于管理。

提供 XML 标签,支持编写动态 SQL 语句。

提供映射标签,支持对象与数据库的 ORM 字段映射关系。

缺点

SQL 语句编写工作量较大,尤其是字段和关联表多时。

SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。


Q2:Mybatis 的 XML 文件有哪些标签属性?

select、insert、update、delete 标签分别对应查询、添加、更新、删除操作。

parameterType 属性表示参数的数据类型,包括基本数据类型和对应的包装类型、String 和 Java Bean 类型,当有多个参数时可以使用 #{argn} 的形式表示第 n 个参数。除了基本数据类型都要以全限定类名的形式指定参数类型。

resultType 表示返回的结果类型,包括基本数据类型和对应的包装类型、String 和 Java Bean 类型。还可以使用把返回结果封装为复杂类型的 resultMap 。


Q3:Mybatis 的一级缓存是什么?

一级缓存是 SqlSession 级别,默认开启且不能关闭。

操作数据库时需要创建 SqlSession 对象,对象中用一个 HashMap 存储缓存数据,不同 SqlSession 之间缓存数据区域互不影响。

一级缓存的作用域是 SqlSession 范围的,在同一个 SqlSession 中执行两次相同的 SQL 语句时,第一次执行完毕会将结果保存在缓存中,第二次查询直接从缓存中获取。

如果 SqlSession 执行了 DML 操作(insert、update、delete),Mybatis 必须将缓存清空保证数据有效性。


Q4:Mybatis 的二级缓存是什么?

二级缓存是Mapper 级别,默认关闭。

使用二级缓存时多个 SqlSession 使用同一个 Mapper 的 SQL 语句操作数据库,得到的数据会存在二级缓存区,同样使用 HashMap 进行数据存储,相比于一级缓存,二级缓存范围更大,多个 SqlSession 可以共用二级缓存,作用域是 Mapper 的同一个 namespace,不同 SqlSession 两次执行相同的 namespace 下的 SQL 语句,参数也相等,则第一次执行成功后会将数据保存在二级缓存中,第二次可直接从二级缓存中取出数据。

要使用二级缓存,需要在全局配置文件中配置 ,再在对应的映射文件中配置一个 标签。


Q5:Mybatis #{} 和 ${} 的区别?

使用 ${} 相当于使用字符串拼接,存在 SQL 注入的风险。

使用 #{} 相当于使用占位符,可以防止 SQL 注入,不支持使用占位符的地方就只能使用 ${} ,典型情况就是动态参数。


线程池七大参数

一、corePoolSize 线程池核心线程大小

线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。任务提交到线程池后,首先会检查当前线程数是否达到了corePoolSize,如果没有达到的话,则会创建一个新线程来处理这个任务。

二、maximumPoolSize 线程池最大线程数量

当前线程数达到corePoolSize后,如果继续有任务被提交到线程池,会将任务缓存到工作队列(后面会介绍)中。如果队列也已满,则会去创建一个新线程来出来这个处理。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。

三、keepAliveTime 空闲线程存活时间

一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定

四、unit 空闲线程存活时间单位

keepAliveTime的计量单位

五、workQueue 工作队列

新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:

①ArrayBlockingQueue

基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。

②LinkedBlockingQuene

基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize(很难达到Interger.MAX这个数),因此使用该工作队列时,参数maxPoolSize其实是不起作用的。

③SynchronousQuene

一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。

④PriorityBlockingQueue

具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

六、threadFactory 线程工厂

创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等

七、handler 拒绝策略

当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的。