面试时间: 3月11日 远程面试 面试时长:20分钟
面试的问题:
目录
2.你们为什么要用ssm框架?为什么不用springboot?对于springboot的理解
9.什么是阻塞队列,什么情况下发生阻塞使用,都有哪些阻塞队列
13.synchronizedMap,Map,concurrentHashMap的区别
14.hashmap的数据结构,hashmap为什么要引入红黑树
下面是我整理的各个题的答案
1.自我介绍
2.你们为什么要用ssm框架?为什么不用springboot?对于springboot的理解
使用ssm框架的原因:
这个题回答的时候应该从三个方面来回答,分别回答springmvc,spring,mybatis的好处
- Spring的IOC,AOP技术,IOC解耦,是的代码复用,将创建对象的权利,对象的生命周期统一交给spring来管理,使得代码复用,可维护性提升,AOP面向切面编程,spring还实现了事务,日志等。对其他框架具有良好的集成。
- Springmvc ,相对于structs2和其他mvc框架来说,其他框架是类拦截,同时是filter入口的,而springmvc是方法拦截,采用servlet入口,与spring无缝对接。对于开发者而言,springmvc更加轻量和低入门
- Mybatis的sql可以由开发者去掌控和调优,相对于hibernate等orm框架来说,更加直观。
为什么不使用springboot?你对springboot的理解[当时学到ssm框架,想对其有一个应用]
Ssm是web的应用框架,涵盖整个应用层。Springboot可以看做一个启动,配置,快速开发的辅助框架,本身针对的是微服务
Springboot使用main方法启动web应用,可以快速部署发布web服务,将原有的xml配置,简化为java配置,springboot结构和ssm有一定区别,spring采用的是默认配置
SpringBoot的注解
@SpringBootApplication注解:声明当前类为一个sprngboot的入口类。而一个Springboot项目内有且只有一个这个注解存在
@RestController声明我们创建的类是一个访问控制器,可以使用url来请求
3.mybatis和hibernate的区别
两者的相同点:
- hibernate与mybatis都是可以通过SqlsessionFactoryBuider来生成sqlSessionFactory,然后由sqlsessionfactory来生成sqlsession
- hibernate和mybatis都支持事务
两者的不同点:
Myabtis可以更为细致的进行sql优化,hibernate无法直接维持sql,mybatis相对于hibernate来说,更容易进行掌握[我觉得最好不要说mybatis的学习门槛低]
4.项目中搜索功能用了搜索引擎了吗,是什么
项目中对于搜索模块使用的是分词器,对于前端传入数据进行分词,然后分别使用分词得到的结果[结果是每一个单词]对数据库进行查询。将每个查询的结果放入set中[底层使用的还是like]
Like时索引失效,全表扫描,数据量大时是噩梦
我知道的搜索引擎有
Nutch,Solr,Elasticsearch,Lucene
Nutch:Apache顶级开源项目,包含网络爬虫和搜索引擎(基于Lucene)的系统(桶百度,谷歌),Hadoop因他而生
Solr:Lucene下的子项目,基于Lucene构建的独立的企业级的开源搜索平台,一个服务。它提供了xml/JSON/http的api供外界访问,还有web管理界面
ElasticSearch;基于Lucene的企业级分布式搜索平台,它对外提供restful-web接口,让程序员可以轻松,方便使用搜索平台,而不需要了解Lucene
5.什么是微服务架构,有什么了解
微服务就是体积小,服务于一个或者一组相对较小的独立的功能单元,是用户可以感知的最小功能集。举个例子就是多个tomcat,然后每个tomcat完成一件独立的事情。
单体架构所有的模块全都耦合在一起,代码量大,维护困难,微服务每一个模块就相当于一个单独的项目,代码量明显减少,遇到的问题相对来说也比较好解决。
6.多线程怎么创建
多线程创建一共有三种方法:
- 继承Thread类
- 子类覆盖父类中的run方法,将线程运行的代码存放在run中
- 建立子类对象的同时线程而被创建
- 通过调用start方法来开启线程
- 实现Runnable接口
- 子类覆盖接口中的run方法
- 通过Thread类创建线程,并将实现了Runnable接口的子类传递给Thread的构造函数
- 实现callable<T>接口
Runnable接口中的run()方法的返回值是void,它做的事情是只纯粹地去执行run方法中的代码;Callable接口中的call()方法是有返回值类型的,是一个泛型和Future,FutureTask配合可以用来获取异步执行的结果
FutureTask f=new FutureTask(实现callable接口的类);
Thread thread=new Thread(f);
f.start();
FutureTask表示一个异步运算的任务,FutureTask里面可以传入一个callable的具体实现类。可以对这个异步运算的结果进行等待获取,判断是否已经完成,任务的取消等操作。当然futureTask也是Runnable接口的实现类
7.知道线程池吗?线程池怎么构建
https://www.cnblogs.com/dolphin0520/p/3932921.html
从四方面答:
1. 线程池的好处
2. 线程池的原理
3. 线程池的内部参数
4. 线程池的种类
5. 线程池的构建方法
线程池的好处:解决了线程的生命周期开销问题和资源不足问题。[通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上,当请求到来是,减少了创建线程的开销,这样降低了线程池的资源消耗,提高了响应的速度,同时也让线程同一管理分配]
线程池的原理:当一个新的线程创建的请求来临时,
1. 如果当前运行的线程少于线程核心池的数量,则创建新线程执行任务[需要全局锁]
2. 如果运行的线程多于核心线程数量,则将任务加入阻塞队列
3. 如果阻塞队列已满,则创建新线程来处理任务[这时候创建的是非核心线程]
4. 如果创建新线程时,超过当前线程池所允许的最大线程数量,则请求将会被拒绝。[这时候会调用饱和策略来处理]
线程池的内部参数[创建线程池需要初始化的参数]
1. 线程池中核心线程数的最大值
2. 线程池中的最大线程数量 最大线程数量=核心线程最大值+非核心线程的最大值
3. 阻塞队列 用于保存等待线程的任务,阻塞队列有三种,ArrayBlockingQueue基于数组的一个阻塞队列FIFO LinkedBlockingQueue基于链表的阻塞队列,长度可以无限,所以吞入量高于ArrayLinkedBlockingQueue SynchronousQueue只有一个线程,如果需要put操作必须等到take操作执行完PriorityBlockingQueue:具有自定义优先级
4. 优先级策略 当队列和线程池满了,采取策略,默认是AboutPolicy(抛出异常) [其他的策略有CallerRunPolcicy:使用调用者所在线程来处理任务,DiscardOldestPolicy:丢弃队列里最近一个任务,并执行当前任务,DiscardPolicy:不处理,丢弃掉]
5. 线程保持活动的时间
6.线程工厂,用来创建线程
线程池的种类:
1. 可缓存线程池CacheThreadPool
特点:线程数无限制 有空闲线程则复用空闲线程,若无空闲线程则创建新线程,在一定程度上减少频繁创建/销毁线程的次数,减少系统开销
创建方法ExecutorService cacheThreadPool=Excutors.newCacheThreadPool();
2. 定长线程池
特点:可控制线程的最大并发数(同时执行的线程数)‘,超出的线程会在队列中进行等待
创建方法:
ExcutorService fixedThreadPool=Excutors.newFixedThreadPool(int num);
3. ScheduledThreadPool预先安排线程池
支持定时及周期性任务执行
4. SingleThreadExcutor() 单线程化的线程池
有且仅有一个工作线程执行任务
8.什么情况下发生死锁
多线程同时竞争资源且不释放拥有的资源时发生死锁
死锁产生的四个特定条件:
1. 互斥条件 进程对于所分配的资源具有排他性,一个资源只能由一个进程占用,直到释放
2. 请求和保持:一个进程请求被占用资源而发生阻塞,对已经获得的资源保持不放
3. 不剥夺:任何一个资源在没有被该进程释放之前,任何其他请求都无法对他剥夺占用
4循环等待 当发生死锁时,锁等待的进程必定会形成一个环路,类似于死循环
9.什么是阻塞队列,什么情况下发生阻塞使用,都有哪些阻塞队列
阻塞队列是当用户请求创建线程池,线程池中的核心线程数量已经饱和,这时候会把该任务放入阻塞队列。[一个队列,遵循先进先出]
阻塞队列有:ArrayBolckingQueue底层数据结构是一个数组,等待的任务以数组的形式存储
LinkedBlockingQueue底层是一个链表,对于容量没有线程,一般情况下使用的都是该阻塞队列
SynchrounsBlockingQueue是一个同步的阻塞队列,只允许存在一个线程,当一个线程需要put操作时,前一个线程必须被删除(使用take操作)
PriorityBlockingQueue可以定义优先级的阻塞队列
10.什么是悲观锁和乐观锁,实现的原理是什么
悲观锁认为对于同一个数据库的并发操作,一定会发生修改,哪怕是没有发生修改,也认为有可能被修改,因此对于同一个数据的并发操作,悲观锁采用加锁的方式,悲观的认为,不加锁的方式一定会出现问题,当其他线程想要访问数据库时,都需要阻塞挂起等待,可以依靠数据库实现。例如synchronized
悲观锁的实现原理是加锁(读锁,写锁,行锁)
乐观锁认为同一个数据的并发操作时不会发生修改的,乐观的认为,并发操作时没有事情的
乐观锁实现的原理是
1. CAS(compare and swap)
CAS算法
CAS算法存在着三个参数,内存值V,旧的预期值A,以及要更新的值B。当今切当内存值和预期值相等的时候,才会将内存值修改为B,否则什么也不做,直接返回false,并告诉预期值
2. version方式 [ MVCC方式]一般是在数据库表中加上一个数据库版本version字段,表示数据被修改的次数,当数据库被修改时,version值会加一。当线程A要更新数据时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,知道更新成功
核心sql update table set x=x+1,version=version+1 where id=#{id} and version =#{version}
11.怎样唤醒一个线程
Notify()唤醒在此对象监视器上等待的单个线程
notifyAll()唤醒在此对象监视器上等待的所有线程
Condition可以唤醒线程await,singal方法
1. notify()方法无法指定,选择是任意性的。唤醒在此对象监视器上等待的单个线程,如果每个线程都在此对象上等待,则会唤醒其中一个线程
2. 一般的做法是修改一个特定标识,然后notifyAll,被唤醒的线程查看该标识是否指定自己处理,是就运行下去,不是就继续wait
3.对每一个线程就wait(加object),要唤醒哪一个就notify(Object)
12.线程之间共享数据的方法是什么
https://www.cnblogs.com/lemingyin/p/8878513.html
1. 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个对象有那个共享数据,例如买票系统就可以这么做
2. 如果每个线程执行的代码不同,这时候需要使用不同的Runnable对象,有如下三种方式来实现Runnable独享之间的数据共享
方式1 :将共享数据封装在另外一个对象中,然后将这个对象逐一传递给Runnable对象,每个线程对共享数据的操作方法也分配到那个对象上去完成
方式2将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对各个操作的互斥和通信
方式3上面两种方式的组合 将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象上去完成,对象作为外部类中的成员变量或者方法中的局部变量,每个线程的Runnable对象作为外部类或局部内部类
13.synchronizedMap,Map,concurrentHashMap的区别
Map是一个结构,hashmap是他的实现类,不具备线程安全性
Hashmap不安全的原因:
1. 多线程使用put方法添加元素,假设正好存在两个put的key放生了碰撞(hash值计算的bucket一样),那么根据hashmap的实现,这两个key会添加到数组的同一个位置,这样最终就会发生其中一个线程的put数据被覆盖
2. 多个线程同时检测到元素个数超过数组大小*loadFactor,这样就会发生多个线程对数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋值给table,导致其他线程的数据丢失(之前put的数据丢书)
ConcurrentHashMap jdk1.8之前使用分段锁的协议,jdk1.8之后采用CAS算法
SynchronizedMap类对象,在synchronizedMap类中使用了synchronized同步关键字来保证对Map的操作是线程安全的
14.hashmap的数据结构,hashmap为什么要引入红黑树
数组+链表+红黑树
Hashmap为什么要引入红黑树:
红黑树的特点决定,可以自动的使用二分法进行定位,性能比较好
当元素大于8个时,使用红黑树,查询成本低(元素小于8个使用链表,新增成本低)
红黑树的平均查找长度是log(n),长度为8,查找复杂度log(8)=3,
链表的长度是(n/2),当长度为8,查找时间复杂度为8/2=4
选择6和8的原因:
中间有个差值7可以防止链表和树之间频繁的转换,假设一下,如果涉及链表个数超过8则链表转换成树结构,链表个数小于8则转换成成链表,如果一个hashmap不停地插入,删除元素,链表个数在8左右徘徊,频繁的发生数转链表,链表转树,效率会很低
15.websocket和http协议的区别
1. http协议是用在应用层的协议,他是基于tcp协议的,http协议建立连接必须要有三次握手才能发送信息
http连接分为短连接,长连接,短连接是每次请求都要三次握手才能发送自己的信息,即每一次request对应一个response。长连接是指在一定的期限内保持TCP连接不断开,服务端与客户端通信,必须要有客户端发起然后服务端返回键结果,客户端是主动的,服务器是被动的
2. websocket是为了解决客户端发起多个http请求到服务器资源浏览器必须经过长时间的轮询问题产生的,在websocket协议下,客户端和服务端可以同时发送协议。
建立了websocket协议之后服务器不必在浏览器发送request后才发送数据,这时候服务器已经有主动权向什么时候发就什么时候发送,这种发送不必带head信息,这种方式不仅能降低服务器的压力,而且也减少了部分多余的信息
16.tcp一共几次握手?tcp的三次握手是怎样来实现的
三次握手,举例说明:A方发送数据,B方接受之后可以确认A方能正常发送,B方可以正常接受
第二次B发送数据给A可以确定自己能正常发送,正常接受
第三次A发送数据给B,B可以确定自己能正常发送
https://baijiahao.baidu.com/s?id=1618114723935605183&wfr=spider&for=pc
给面试官回答不应该回答的那么笼统,具体应该细说
TCP建立连接通过三次握手
TCP传输数据
TCP断开连接通过四次回收
第一次握手:
客户端发送syn包(seq=x)到服务器,并进入SYN_SENT状态
第二次握手
服务器收到syn包,确认客户端的syn,它发送一个ACK=x+1,同时自己也发送一个syn包(seq=y) 此时服务器进入SYN_RECV状态
第三次握手
客户端收到syn+ack包,向服务器发送ack=y+1,此包发送完毕之后,服务器和客户端同时进入establelished状态,完成三次握手
握手过程中传送的包不包含数据,三次握手完毕后,服务端与客户端正式传送数据,tcp连接完成后,由一方主动关闭数据,tcp连接都将一直被保持下去
传输数据过程:
a. 超时重传 超时重传机制用来保证TCP传输的可靠性,每次发送数据包时,发送的数据报都有seq号,接受端收到数据后,会回复ack进行确认,表示收到数据,如果发送方没有收到对应的ACK回复,就会认为报文丢失,会重传这个数据包
b. 快速重传 接受数据一方发现有数据包丢掉了,就会发送ack报文告诉发送端重传丢失的报文,如果发送端连续收到标号相同的ACK包,则会触发客户端重新传输数据。快速重传和超时重传的区别在于快速重传接受数据一方发现有数据包丢掉了,就会发送ack报文告诉发送方没收到数据,触发重新发送,而超时发送是发送放一直等待,直到时间足够,才触发
c流量控制tcp滑动窗流量控制,tcp头里面有一个字段叫window(advertisd-window),这个字段时接受端告诉发送方自己还有多少缓冲区可以接受数据,于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。滑动窗口是提高tcp传输速率的一种方式
d.拥塞控制 使用滑动窗口来做流浪控制,流量控制只关注发送端和接收端自身的状况,而没有考虑整个网络的通信状况,拥塞窗口则是基于整个网络来考虑的。假如某一时刻网络上的延时突然增加,那么tcp对这个事情做出的应对有重传数据,但是重传数据会导致网络的负担更重,于是会导致更大的延时以及更多的丢包。这样就会进入恶性循环。这样会拖垮整个网络
拥塞策略算法主要包括:慢启动,拥塞避免,拥塞发生,快速恢复
四次挥手断开连接:
场景打电话的时候:
A:我说完了,我要挂电话了
B:我知道了
B(说完自己的话后):我也说完了
A:知道了
挂电话
详细解释
1. 客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送
2. 客户端B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1
3. 服务器B关闭客户端A的连接,发送一个FIN给客户端A
4. 客户端A发回ACK报文确认,并将序号设置为收到序号加1
TCP采用四次挥手关闭连接为什么建立协议是三次握手
这是因为服务端的LISTEN状态下的socket当收到syn报文的连接请求后,可以报ack和syn放在同一个报文里面发送。当时当关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发给你,但未必你所有的数据全部发送诶对方了,所以你可以未必马上会关闭socket,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以ACK和FIN报文多数情况下都是分开发送的
17知道futureTask吗。说一下
https://blog.youkuaiyun.com/xiaomingdetianxia/article/details/77774637
Future就是对于具体的Runnable或者Callable任务的执行结果进行了取消,查询查询是否完成,获取结果等操作。必要时可以通过get方法获取执行结果,阻塞知道任务返回结果,java.util.concurrent包下
Future接口中声明了5个方法
- cancel:取消任务,如果取消成功则返回true,如果取消失败则返回false
- iscancelled() 任务是够被取消成功,如果在任务正常完成前被取消,则返回true()
- isDone()方法表示任务是否完成,若任务完成,则返回true
- get()获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕后返回
- get(long timeout,TimeUnit unit)用来获取执行结构,如果在执行时间内还没有获取到执行结果,就直接返回null
也就是说Future提供了三种功能
- 判断任务是否完成
- 取消任务(中断任务)
- 获取执行结果
因为future只是一个接口,所以是无法直接来创建对象使用的,因此就有了FutureTask
FutureTask实现了RunnableFuture接口,RunnableFuture接口继承了future接口和runnable接口[java中一个接口可以继承多个接口]
FutureTask提供了两个构造器,可以传入future,也可以传入runnable,如果传入的是runnable,会被Excutors.callable转换为callable类型
FutuereTask是future接口的一个唯一实现类