简单的面试题
SpringMVC工作原理
(1)用户发送请求至前端控制器DispatcherServlet;
(2) DispatcherServlet收到请求后,调用HandlerMapping处理器映射器, 请求获取Handle;
(3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦 截器返回给DispatcherServlet;
(4)DispatcherServlet 调用 HandlerAdapter处理器适配器;
(5)HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制 器);
(6)Handler执行完成返回ModelAndView;
(7)HandlerAdapter将Handler执行结果ModelAndView返回给 DispatcherServlet;
(8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行 解析;
(9)ViewResolver解析后返回具体View;
(10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
(11)DispatcherServlet响应用户。
Spring事务的理解?
Spring支持编程式事务管理以及声明式事务管理两种方式
\1. 编程式事务管理
编程式事务管理是使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
\2. 声明式事务管理
- 声明式事务,在应用启动类上添加@EnableTransactionManagement注解,@Transactional事务处理
声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
Redis的缓存击穿及解决方案
缓存击穿是指存在一个热点数据Key, 有请求不断来访问这个Key,这么多请求在同一段时间内访问这个热点数据, 当这个 Key 失效时间到了的时候, 持续的这么多请求直接发送到数据库上了, 这样的话就实现了缓存击穿
如何解决?
设置热点数据永不过期或者加上互斥锁就搞定了
线程池的核心参数
1.corePoolSize(核心线程数):
(1)核心线程会一直存在,即使没有任务执行;
(2)当线程数小于核心线程数的时候,即使有空闲线程,也会一直创建线程直到达到核心线程数;
\2. maximumPoolSize(最大线程数):
(2)线程池里允许存在的最大线程数量。当任务队列已满,且线程数量大于等于核心线程数时,会创建新的线程执行任务。
\3. keepAliveTime(线程空闲时间):
(1)当线程空闲时间达到keepAliveTime时,线程会退出(关闭)
\4. unit: 和keepAliveTime配合使用, 时间单位, 可以为秒, 分钟:
\5. allowCoreThreadTimeout(允许核心线程超时, 默认为false, jdk1.6后加的新特性)
\6. workQueue(阻塞队列):
用来存放待执行的任务,如果队列还没满就等待使用核心线程,如果已经满了,还有任务进来,就创建新的线程
7.threadFactory:
线程创建的工厂,新的线程都是由ThreadFactory创建的,系统默认使用的是
Executors.defaultThreadFactory创建的,用它创建出来的线程的优先级都是一样
的,并且他都不是守护线程。我们也可以使用自定义的线程创建工厂,并对相关的值进
行修改
8.handler:
任务拒绝策略,如果已经满了(队列,最大线程数)就执行拒绝策略
HashMap和Hashtable的区别?
Hashtable是线程安全,而HashMap则非线程安全,
Hashtable的实现方法里面都添加了synchronized关键字来确保线程同步,因此相对而言HashMap性能会高一些
在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合
HashMap可以使用null作为key,而Hashtable则不允许null作为key
HashMap是对Map接口的实现,HashTable实现了Map接口和Dictionary抽象类。
HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。
Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模
HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取摸。
Java集合类框架的基本接口有哪些?
Set:不包含重复元素。 List:有顺序的,并且可以包含重复元素。 Map:可以把键(key)映射到值(value)的对象,键不能重复。
什么是死锁(deadlock)?
所谓死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。死锁产生的4个必要条件: 互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。 不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。
JDK1.6 以后对 Synchronized 做了哪些优化?
JDK1.6 之前是重量级锁,他加锁底层是通过系统的mutex相关指令实现,会有用户态和内核态之间的切换,十分消耗性能。
JDK1.6之后对synchronized底层做了优化,引入了偏向锁、轻量级锁、在JVM层面实现加锁的逻辑,不依赖底层操作系统,就没有状态切换的消耗。同时引入自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
所以在对象头的Mark word 锁主要存在四种状态依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。同时锁只能升级不能降级。
创建线程有哪几种方式?
1)继承Thread类创建线程2)实现Runnable接口创建线程3)使用Callable和Future创建线程4)使用线程池例如用Executor框架
什么是守护线程?
① 用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 ②守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,③比如垃圾回收线程就是一个守护线程
线程的生命周期
新建:就是刚使用new方法,new出来的线程;
就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;
线程池的类型
\1. newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小.
\2. newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
\3. newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序 按照任务的提交顺序执行。
\4. newSingleThreadScheduleExecutor: 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期执行.
\5. newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行, 线城池数量不固定.
\6. newWorkStealingPool: 创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不传并行级别数,将默认为当前系统的CPU个数.
synchronized关键字 底层原理 具体实现
实现原理: JVM 通过进入、退出 对象监视器(Monitor) 来实现对方法、同步代码块的同步,而对象监视器的本质依赖于底层操作系统的 互斥锁(Mutex Lock) 实现。
具体实现:是在编译之后在同步方法调用前加入一个monitor.enter指令,在退出方法和异常处插入monitor.exit的指令。对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程monitor.exit之后才能尝试继续获取锁。
ConcurrentHashMap并发能力为什么好于Hashtable
①Hashtable是通过对hash表整体进行锁定,是阻塞式的,当一个线程占有这个锁时,其他线程必须阻塞等待其释放锁
而ConcurrentHashMap是如下实现:
②jdk1.6的实现:ConcurrentHashMap是采用Segment分段锁的方式,它并没有对整个数据结构进行锁定,而是局部锁定,
③jdk1.8的实现: 采用一种乐观锁CAS算法来实现同步问题,但其底层还是“数组+链表->红黑树”的实现
JVM内存划分
程序计数器:
每个线程都有它自己的程序计数器,程序计数器他会存储当前线程正在执行的方法JVM地址。
java虚拟机栈:
每个线程创建的时候都会创建一个虚拟机栈,它的内部保存着一个个的栈帧对应着一次次的方法调用。
堆:
他是java内存管理的核心区域,用来存放java对象实例,几乎所有创建的java对象实例都会被直接分配到堆上,对被所有的线程共享。
方法区:
这也是被线程共享的一块区域,用来存储元数据。
运行时常量池:
他是方法区的一部分
本地方法栈:
他和java虚拟机栈是非常相似的,支持对本地方法调用,也是每个线程都会创建一个
Java类加载过程
1.加载
●通过一个类的全限定名获取该类的二进制流。
●将该二进制流中的静态存储结构转化为【方法去运行时数据结构】。
●在内存中生成该类的【Class对象】,作为该类的数据访问入口。
2.验证
确保Class文件的字节流中的信息不回危害到虚拟机在该阶段主要完成以下四种验证:
文件格式验证,元数据验证,字节码验证,符号引用验证
3.准备
准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
4.解析
该阶段主要完成符号弓|用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。
5.初始化
初始化是类加载的最后一步, 前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中的定义的java程序代码。
6.使用
7.卸载