面试题

常见的集合?

Map接口和Collection接口是所有集合框架的父接口:

  1. Collection接口的子接口包括:Set接口和List接口
  2. Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
  3. Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
  4. List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

fail-fast 机制

fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常。我们知道,对于集合如list,map类,我们都可以通过迭代器来遍历,而Iterator其实只是一个接口,具体的实现还是要看具体的集合类中的内部类去实现Iterator并实现相关方法。

在ArrayList中,当调用list.iterator()时,其源码是:public    Iterator<E>   iterator(){  return   new Itr();  }

在ArrayList的内部类Itr中,有属性:int expectedModCount = ArrayList.this.modCount;

它是fail-fast判断的关键变量了,它初始值就为ArrayList中的modCount。modCount是抽象类AbstractList中的变量,用于记录集合操作过程中作的修改次数,默认为0,ArrayList 继承AbstractList。

每次调用next()方法,在实际访问元素前,都会调用checkForComodification方法,该方法源码如下:

final void checkForComodification() {
  if (modCount != expectedModCount)
  throw new ConcurrentModificationException();
}
在该段代码中,当modCount != expectedModCount时,就会抛出该异常。但是在一开始的时候,expectedModCount初始值默认等于modCount,为什么会出现modCount != expectedModCount,很明显expectedModCount在整个迭代过程除了一开始赋予初始值modCount外,并没有再发生改变,所以可能发生改变的就只有modCount,在前面关于ArrayList扩容机制的分析中,可以知道在ArrayList进行add,remove,clear等涉及到修改集合中的元素个数的操作时,modCount就会发生改变(modCount ++),所以当另一个线程(并发修改)或者同一个线程遍历过程中,调用相关方法使集合的个数发生改变,就会使modCount发生变化,这样在checkForComodification方法中就会抛出ConcurrentModificationException异常。
类似的,hashMap中发生的原理也是一样的。

ArrayList 和 Vector 的区别?

这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合,即存储在这两个集合中的元素位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引来取出某个元素,并且其中的数据是允许重复的,这是与 HashSet 之类的集合的最大不同处,HashSet 之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素。ArrayList 与 Vector 的区别主要包括两个方面:

  1. 同步性: Vector 是线程安全的,也就是说它的方法之间是线程同步(加了synchronized 关键字)的,而 ArrayList 是线程不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用 ArrayList,因为它不考虑线程安全的问题,所以效率会高一些;如果有多个线程会访问到集合,那最好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。
  2. 数据增长: ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个人超过了容量时,就需要增加 ArrayList 和 Vector 的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要去的一定的平衡。Vector 在数据满时(加载因子1)增长为原来的两倍(扩容增量:原容量的 2 倍),而 ArrayList 在数据量达到容量的一半时(加载因子 0.5)增长为原容量的 (0.5 倍 + 1) 个空间。

ArrayList和LinkedList的区别?

  1. LinkedList 实现了 List 和 Deque 接口,一般称为双向链表;ArrayList 实现了 List 接口,动态数组;
  2. LinkedList 在插入和删除数据时效率更高,ArrayList 在查找某个 index 的数据时效率更高;
  3. LinkedList 比 ArrayList 需要更多的内存;

Array 和 ArrayList 有什么区别?什么时候该应 Array 而不是 ArrayList 呢?

  1. Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
  2. Array 大小是固定的,ArrayList 的大小是动态变化的。
  3. ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。

对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

Arraylist 的动态扩容机制

当在 ArrayList 中增加一个对象时 Java 会去检查 Arraylist 以确保已存在的数组中有足够的容量来存储这个新对象(默认为 10,最大容量为 int 上限),如果没有足够容量就新建一个长度更长的数组(原来的1.5倍),旧的数组就会使用 Arrays.copyOf 方法被复制到新的数组中去,现有的数组引用指向了新的数组

Runnable接口和Callable接口的区别

Runnable接口中的run()方法的返回值是void,它只是纯粹地去执行run()方法中的代码而已;

Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果

多线程有什么用

  1. 发挥多核CPU的优势:如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。多线程可以充分利用CPU的。
  2. 防止阻塞:多条线程同时运行,一条线程的代码执行阻塞,也不会影响其它任务的执行。

wait和sleep

  1. wait是Object的方法;sleep是Thread静态方法
  2. wait是要配合synchronized配合使用的(一定放在synchronized里面),调用后线程阻塞,被阻塞的线程只能由notify或notifyAll方法唤醒
  3. sleep只是将线程睡眠,让其他线程先执行,等待时间到自然苏醒,重新回到可运行状态等待调度
  4. 最重要区别,sleep睡眠的时候,是不会释放已持有的锁的,而wait释放锁

Callable和Future?

Callable 和 Future 是比较有趣的一对组合。当我们需要获取线程的执行结果时,就需要用到它们。Callable用于产生结果,Future用于获取结果。

Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法去在线程池中执行Callable内的任务。由于Callable任务是并行的,必须等待它返回的结果。java.util.concurrent.Future对象解决了这个问题。

在线程池提交Callable任务后返回了一个Future对象,使用它可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法,等待Callable结束并获取它的执行结果。

代码示例

Callable 是一个接口,它只包含一个call()方法。Callable是一个返回结果并且可能抛出异常的任务。

为了便于理解,我们可以将Callable比作一个Runnable接口,而Callable的call()方法则类似于Runnable的run()方法。

什么是乐观锁和悲观锁?

悲观锁:悲观,认为其他线程会修改变量,Java在JDK1.5之前都是靠synchronized关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有共享变量的锁,都采用独占的方式来访问这些变量。独占锁其实就是一种悲观锁,所以可以说synchronized是悲观锁。

乐观锁:乐观,认为其他线程不一定会修改变量,乐观锁( Optimistic Locking)其实是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

Java中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思?

Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,Java中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。

JSP与SERVLET的区别。

jsp页面是在服务器端运行,其本质就是Servlet

JSP先编译成servlet然后再编译成class文件。JSP-----SERVLET-----JAVA文件---CLASS

jsp主要做视图层,侧重表现;servlet主要做控制层,侧重逻辑

Servlet生命周期

当Tomcat第一次访问Servlet的时候,首先加载servlet的class,实例化servlet,然后调用初始化方法 init() 初始化这个对象

当浏览器访问Servlet的时候,Servlet 会调用service()方法处理请求

当Servlet服务器正常关闭时,执行destroy方法,只执行一次

servlet什么时候加载(执行init()方法)?

(1) load-on-startup 的值小于0 或者 不配置, 则在第一次请求的时候执行初始化。

(2) load-on-startup 的值大于等于0,则在tomcat启动的时候就执行初始化。

ribbon和 feign 的区别?

1、Ribbon 和 feign 都是客户端的负载均衡的工具,Feign的底层就是通过Ribbon实现的,它是对Ribbon的进一步的封装,让Ribbon 更加好用。
2、Ribbon 使用HttpClient 或 RestTemplate 模拟http请求,步骤相当繁琐。而Feign采用接口+注解的方式 ,将需要调用的其他服务的方法定义成抽象方法即可, 不需要自己构建http请求。然后就像是调用自身工程的方法调用,而感觉不到是调用远程方法,使得编写 客户端变得非常容易。类似于 mybatis 的 @Mapper注解 。

Spring常用组件

服务的注册与发现(Eureka)
服务消费者(rest+ribbon)
服务消费者(Feign)
断路器(Hystrix)
断路器监控(Hystrix Dashboard)
断路器聚合监控(Hystrix Turbine)
路由网关(zuul)
分布式配置中心(Spring Cloud Config)
高可用的分布式配置中心(Spring Cloud Config)
消息总线(Spring Cloud Bus)
服务链路追踪(Spring Cloud Sleuth)
高可用的服务注册中心
docker部署spring cloud项目
服务注册(consul)

消息中间件

1.JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信

2.ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线,对JMS的支持较好

优点:老牌的消息队列,使用Java语言编写。对JMS支持最好,采用多线程并发,资源消耗比较大。如果你的主语言是Java,可以重点考虑。  

缺点:由于历史悠久,历史包袱较多,版本更新很缓慢,几个月才更新一次。集群模式需要依赖Zookeeper实现。目前的研究重心在下一代产品Apollo上

3.RocketMQ/Kafka   

优点:专为海量消息传递打造,主张使用拉模式,天然的集群、HA、负载均衡支持。话说还是那句话,适合不适合看你有没有那么大的量。   

缺点:所谓鱼和熊掌不可兼得,放弃了一些消息中间件的灵活性,使用的场景较窄,需关注你的业务模式是否契合

Kafka生态完善,其代码是用Scala语言写成,可靠性比RocketMQ低一些

4.RabbitMQ

优点:生态丰富,使用者众,有很多人在前面踩坑。AMQP协议的领导实现,支持多种场景。淘宝的MySQL集群内部有使用它进行通讯,OpenStack开源云平台的通信组件,最先在金融行业得到运用。

缺点:RabbitMQ使用Erlang编写的,上手难度大,RabbitMQ在高可用方面做的一般

消息中间件的组成

Broker:消息服务器,作为server提供消息核心服务

Producer:消息生产者,业务的发起方,负责生产消息传输给broker

Consumer:消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理

Topic:主题,发布订阅模式下的消息统一汇集地,不同生产者向topic发送消息,由MQ服务器分发到不同的订阅者,实现消息的广播

Queue:队列,PTP模式下,特定生产者向特定queue发送消息,消费者订阅特定的queue完成指定消息的接收

Message:消息体,根据不同通信协议定义的固定格式进行编码的数据包,来封装业务数据,实现消息的传输

消息中间件模式分类

1.点对点模式:使用queue作为通信载体,消息生产者生产消息发送到queue中,然后消息消费者从queue中取出并且消费消息。 消息被消费以后,queue中不再存储,所以消息消费者不可能消费到已经被消费的消息。 Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。

2.发布/订阅模式(广播):使用topic作为通信载。消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。

3.路由模式 Routing:生产者发送消息到交换机并指定一个路由 key,消费者队列绑定到交换机时要制定路由 key(key 匹配就能接受消息,key 不匹配就不能接受消息),例如:我们可以把路由 key 设置为 insert ,那么消费者队列 key 指定包含 insert 才可以接收消息,消费者队列 key 定义为 update 或者 delete 就不能接收消息。很好的控制了更新,插入和删除的操作

4.通配符模式 Topics:此模式实在路由 key 模式的基础上,使用了通配符来管理消费者接收消息

点对点和发布订阅的区别

queue实现了负载均衡,一条消息只能被一个消费者接收,当没有消费者可用时,这个消息会被保存直到有一个可用的消费者,一个queue可以有很多消费者,他们之间实现了负载均衡, 所以Queue实现了一个可靠的负载均衡。
topic实现了发布和订阅,当你发布一个消息,所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到一个消息的拷贝,发布到topic的消息会被所有订阅者消费

消息中间件的优势

1.解耦:传统模式系统间耦合性太强;使用消息中间件后,交互系统之间没有直接的调用关系,只是通过消息传输,故系统侵入性不强,耦合度低

2.异步:一些非必要的业务逻辑以同步的方式运行,太耗费时间;使用消息中间件后,将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快相应速度。例如原来的一套逻辑,完成支付可能涉及先修改订单状态、计算会员积分、通知物流配送几个逻辑才能完成;通过MQ架构设计,就可将紧急重要(需要立刻响应)的业务放到该调用方法中,响应要求不高的使用消息队列,放到MQ队列中,供消费者处理

3.削峰:传统模式并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常;使用MQ架构设计,系统慢慢的按照数据库能处理的并发量,从消息队列中拉取消息

消息中间件的缺点

系统可用性降低:在运行正常的系统加入消息队列,那消息队列挂了,系统也就不可用了,系统可用性会降低

系统复杂性增加:加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。因此,需要考虑的东西更多,复杂性增大。

ActiveMQ服务器宕机怎么办

这得从ActiveMQ的储存机制说起。在通常的情况下,非持久化消息是存储在内存中的,持久化消息是存储在文件中的,它们的最大限制在配置文件的<systemUsage>节点中配置。但是,在非持久化消息堆积到一定程度,内存告急的时候,ActiveMQ会将内存中的非持久化消息写入临时文件中,以腾出内存。虽然都保存到了文件里,但它和持久化消息的区别是,重启后持久化消息会从文件中恢复,非持久化的临时文件会直接删除。解决方案就是尽量不要用非持久化消息,非要用的话,将临时文件限制尽可能的调大

发送持久化消息非常慢

默认的情况下,非持久化的消息是异步发送的,持久化的消息是同步发送的,遇到慢一点的硬盘,发送消息的速度是无法忍受的。但是在开启事务的情况下,消息都是异步发送的,效率会有2个数量级的提升。所以在发送持久化消息时,请务必开启事务模式。其实发送非持久化消息时也建议开启事务,因为根本不会影响性能。

原文地址:https://www.cnblogs.com/bushishucai/p/11313194.html