Java高级面试题及答案

List和Set比较,各自的子类比较

对比一:Arraylist与LinkedList的比较前端

一、ArrayList是实现了基于动态数组的数据结构,由于地址连续,一旦数据存储好了,查询操做效率会比较高(在内存里是连着放的)。
算法

二、由于地址连续, ArrayList要移动数据,因此插入和删除操做效率比较低。 spring

三、LinkedList基于链表的数据结构,地址是任意的,因此在开辟内存空间的时候不须要等一个连续的地址,对于新增和删除操做add和remove,LinedList比较占优点。数据库

四、由于LinkedList要移动指针,因此查询操做性能比较低。编程

适用场景分析:后端

当须要对数据进行对此访问的状况下选用ArrayList,当须要对数据进行屡次增长删除修改时采用LinkedList。数组

对比二:ArrayList与Vector的比较缓存

一、Vector的方法都是同步的,是线程安全的,而ArrayList的方法不是,因为线程的同步必然要影响性能。所以,ArrayList的性能比Vector好。
二、当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增长50%的大小,这样。ArrayList就有利于节约内存空间。安全

三、大多数状况不使用Vector,由于性能很差,可是它支持线程的同步,即某一时刻只有一个线程可以写Vector,避免多线程同时写而引发的不一致性。bash

四、Vector能够设置增加因子,而ArrayList不能够。

适用场景分析:

一、Vector是线程同步的,因此它也是线程安全的,而ArrayList是线程异步的,是不安全的。若是不考虑到线程的安全因素,通常用ArrayList效率比较高。

二、若是集合中的元素的数目大于目前集合数组的长度时,在集合中使用数据量比较大的数据,用Vector有必定的优点。

对比三:HashSet与TreeSet的比较

1.TreeSet 是二叉树实现的,Treeset中的数据是自动排好序的,不容许放入null值 。

2.HashSet 是哈希表实现的,HashSet中的数据是无序的,能够放入null,但只能放入一个null,二者中的值都不能重复,就如数据库中惟一约束 。

3.HashSet要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码做为标识的,而具备相同内容的String对象,hashcode是同样,因此放入的内容不能重复。可是同一个类的对象能够放入不一样的实例。

适用场景分析:

HashSet是基于Hash算法实现的,其性能一般都优于TreeSet。咱们一般都应该使用HashSet,在咱们须要排序的功能时,咱们才使用TreeSet。


HashMap和ConcurrentHashMap的区别

一、HashMap不是线程安全的,而ConcurrentHashMap是线程安全的。

二、ConcurrentHashMap采用锁分段技术,将整个Hash桶进行了分段segment,也就是将这个大的数组分红了几个小的片断segment,并且每一个小的片断segment上面都有锁存在,那么在插入元素的时候就须要先找到应该插入到哪个片断segment,而后再在这个片断上面进行插入,并且这里还须要获取segment锁。

三、ConcurrentHashMap让锁的粒度更精细一些,并发性能更好。


JVM的内存结构

根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

一、Java虚拟机栈:

线程私有;每一个方法在执行的时候会建立一个栈帧,存储了局部变量表,操做数栈,动态链接,方法返回地址等;每一个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。

二、堆:

线程共享;被全部线程共享的一块内存区域,在虚拟机启动时建立,用于存放对象实例。

三、方法区:

线程共享;被全部线程共享的一块内存区域;用于存储已被虚拟机加载的类信息,常量,静态变量等。

四、程序计数器:

线程私有;是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。

五、本地方法栈:

线程私有;主要为虚拟机使用到的Native方法服务。


强引用,软引用和弱引用的区别

强引用:

只有这个引用被释放以后,对象才会被释放掉,只要引用存在,垃圾回收器永远不会回收,这是最多见的New出来的对象。

软引用:

内存溢出以前经过代码回收的引用。软引用主要用户实现相似缓存的功能,在内存足够的状况下直接经过软引用取值,无需从繁忙的真实来源查询数据,提高速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。

弱引用:

第二次垃圾回收时回收的引用,短期内经过弱引用取对应的数据,能够取到,当执行过第二次垃圾回收时,将返回null。弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,能够经过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。


springmvc的核心是什么,请求的流程是怎么处理的,控制反转怎么实现的

核心:

控制反转和面向切面

请求处理流程:

一、首先用户发送请求到前端控制器,前端控制器根据请求信息(如URL)来决定选择哪个页面控制器进行处理并把请求委托给它,即之前的控制器的控制逻辑部分;

二、页面控制器接收到请求后,进行功能处理,首先须要收集和绑定请求参数到一个对象,并进行验证,而后将命令对象委托给业务对象进行处理;处理完毕后返回一个ModelAndView(模型数据和逻辑视图名);

三、前端控制器收回控制权,而后根据返回的逻辑视图名,选择相应的视图进行渲染,并把模型数据传入以便视图渲染;

四、前端控制器再次收回控制权,将响应返回给用户。

控制反转如何实现:

咱们每次使用spring框架都要配置xml文件,这个xml配置了bean的id和class。

spring中默认的bean为单实例模式,经过bean的class引用反射机制能够建立这个实例。

所以,spring框架经过反射替咱们建立好了实例而且替咱们维护他们。

A须要引用B类,spring框架就会经过xml把B实例的引用传给了A的成员变量。


BIO、NIO和AIO的区别

Java BIO : 同步并阻塞,服务器实现模式为一个链接一个线程,即客户端有链接请求时服务器端就须要启动一个线程进行处理,若是这个链接不作任何事情会形成没必要要的线程开销,固然能够经过线程池机制改善。

Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的链接请求都会注册到多路复用器上,多路复用器轮询到链接有I/O请求时才启动一个线程进行处理。

Java AIO: 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

NIO比BIO的改善之处是把一些无效的链接挡在了启动线程以前,减小了这部分资源的浪费(由于咱们都知道每建立一个线程,就要为这个线程分配必定的内存空间)

AIO比NIO的进一步改善之处是将一些暂时可能无效的请求挡在了启动线程以前,好比在NIO的处理方式中,当一个请求来的话,开启线程进行处理,但这个请求所须要的资源尚未就绪,此时必须等待后端的应用资源,这时线程就被阻塞了。

适用场景分析:

BIO方式适用于链接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4之前的惟一选择,但程序直观简单易理解,如以前在Apache中使用。

NIO方式适用于链接数目多且链接比较短(轻操做)的架构,好比聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持,如在 Nginx,Netty中使用。

AIO方式使用于链接数目多且链接比较长(重操做)的架构,好比相册服务器,充分调用OS参与并发操做,编程比较复杂,JDK7开始支持,在成长中,Netty曾经使用过,后来放弃。


为何要用线程池

那先要明白什么是线程池

线程池是指在初始化一个多线程应用程序过程当中建立一个线程集合,而后在须要执行新的任务时重用这些线程而不是新建一个线程。

使用线程池的好处

一、线程池改进了一个应用程序的响应时间。因为线程池中的线程已经准备好且等待被分配任务,应用程序能够直接拿来使用而不用新建一个线程。

二、线程池节省了CLR 为每一个短生存周期任务建立一个完整的线程的开销并能够在任务完成后回收资源。

三、线程池根据当前在系统中运行的进程来优化线程时间片。

四、线程池容许咱们开启多个任务而不用为每一个线程设置属性。

五、线程池容许咱们为正在执行的任务的程序参数传递一个包含状态信息的对象引用。

六、线程池能够用来解决处理一个特定请求最大线程数量限制问题。


悲观锁和乐观锁的区别,怎么实现

悲观锁:一段执行逻辑加上悲观锁,不一样线程同时执行时,只能有一个线程执行,其余的线程在入口处等待,直到锁被释放。

乐观锁:一段执行逻辑加上乐观锁,不一样线程同时执行时,能够同时进入执行,在最后更新数据的时候要检查这些数据是否被其余线程修改了(版本和执行初是否相同),没有修改则进行更新,不然放弃本次操做。

悲观锁的实现:

begin;/begin work;/start transaction; (三者选一就能够)
//1.查询出商品信息
select status from t_goods where id=1 for update;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit;/commit work;
复制代码

乐观锁的实现:

1.查询出商品信息
select (status,status,version) from t_goods where id=#{id}
2.根据商品信息生成订单
3.修改商品status为2
update t_goods 
set status=2,version=version+1
where id=#{id} and version=#{version};
复制代码


什么是线程死锁?死锁如何产生?如何避免线程死锁?

死锁的介绍:

线程死锁是指因为两个或者多个线程互相持有对方所须要的资源,致使这些线程处于等待状态,没法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其余线程将不能进入该代码块。当线程互相持有对方所须要的资源时,会互相等待对方释放资源,若是线程都不主动释放所占有的资源,将产生死锁。

死锁的产生的一些特定条件:

一、互斥条件:进程对于所分配到的资源具备排它性,即一个资源只能被一个进程占用,直到被该进程释放 。

二、请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已得到的资源保持不放。

三、不剥夺条件:任何一个资源在没被该进程释放以前,任何其余进程都没法对他剥夺占用。

四、循环等待条件:当发生死锁时,所等待的进程一定会造成一个环路(相似于死循环),形成永久阻塞。

如何避免:

一、加锁顺序:

当多个线程须要相同的一些锁,可是按照不一样的顺序加锁,死锁就很容易发生。若是能确保全部的线程都是按照相同的顺序得到锁,那么死锁就不会发生。固然这种方式须要你事先知道全部可能会用到的锁,然而总有些时候是没法预知的。

二、加锁时限:

加上一个超时时间,若一个线程没有在给定的时限内成功得到全部须要的锁,则会进行回退并释放全部已经得到的锁,而后等待一段随机的时间再重试。可是若是有很是多的线程同一时间去竞争同一批资源,就算有超时和回退机制,仍是可能会致使这些线程重复地尝试但却始终得不到锁。

三、死锁检测:

死锁检测即每当一个线程得到了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此以外,每当有线程请求锁,也须要记录在这个数据结构中。死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁而且锁超时也不可行的场景。


这一篇先总结这些,欢迎关注个人公众号

相关文章
相关标签/搜索