面试100题
ThreadLocal内存泄露
强引用
- new对象
- 不会被垃圾收集器回收,内存空间不足时,java虚拟机宁愿抛出OOM,使程序异常终止
弱引用
- 垃圾收集时,无论内存是否充足,都会被回收
ThreadLocal
- Thread对象维护一个ThreadLocalMap,key为弱引用的ThreadLocal实例,value为线程变量的副本
原因
stack指向heap的ThreadLocal强引用失效(被置为null)后该对象被回收,ThreadLocalMap的key被置为null,而value还存在强引用,必须等线程结束才回收
key为什么使用弱引用?
key使用强引用
- 当栈中强引用被置为null时,由于key的强引用,导致ThreadLoca对象无法回收
key使用弱引用(解决方案)
- 当key为null,在下一次ThreadLocalMap调用set()、get()、remove()方法时,value值被清除
- ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应的key就会导致内存泄露,而不是因为弱引用
正确的使用方法
- 每次使用完ThreadLocal都调用他的remove()方法清除数据
- 将ThreadLocal变量定义成private static,使得一直存在ThreadLocal的强引用,能保证任何时候都可以通过ThreadLocal的弱引用访问到Entry的value,进而清除掉
并发、并行、串行的区别
串行
- 时间上不可能重叠,顺序执行
并行
- 时间重叠,两个任务同一时刻互不干扰同时执行
并发
统一的时间点,只有一个任务运行,不同任务交替执行
三大特性
原子性
一系列操作中cpu不可以中途暂停(中断)后再调度,要么全部执行完,要么全部不做,例如转账操作、i++操作
- synchronized
可见性
多线程修改变量时,一个线程修改了变量值,其他线程可以立刻看到修改的值
- 总线Lock
- MESI
- volatile、synchronized、final
有序性
虚拟机编译代码时,对于单线程情况下,改变顺序后不会对最终结果造成影响的代码,可能进行指令重排
- synchronized保证代码顺序不会被修改
- volatile保证new对象的操作顺序不变
线程池
优点
降低资源消耗
- 提高线程利用率,降低创建和销毁线程的操作
提高响应速度
- 直接有线程可用,无须先创建再执行
提高线程的可管理性
- 避免无限度new,线程池可以对线程进行统一分配调优和监控
参数
corePoolSize
- 核心线程数,正常情况下创建工作的线程数,创建后不会消除,常驻线程
maxinumPoolSize
- 允许创建的最大线程数
keepAliveTime、unit
- 超出核心线程数之外的线程的空闲存活时间,unit是时间单位
workQueue
- 存放待执行的任务,当核心线程都被使用,新来的任务则放入队列,知道队列被放满但任务还在持续进入则开始创建新线程
Handler
- 任务拒绝策略,两种情况:(1)shutdown方法关闭线程池(2)达到了最大线程数
ThreadFactory
- 线程工厂,生产线程执行任务
处理流程
-
阻塞队列
为什么不适用普通队列?
- 当当前任务数量超过普通队列最大长度时,超出的任务只能被丢弃,而阻塞队列通过阻塞可以保留当前想要继续入队的任务
- 阻塞队列通过take方法保证任务队列中没有任务时阻塞核心线程,使得核心线程进入wait状态,维持核心线程的存活,并释放CPU资源
- 阻塞队列自带唤醒功能,不需要额外的处理
为什么任务先被添加到队列中,而不是直接创建线程?
- 创建新线程时,要获取全局锁,导致其他线程都得阻塞,影响整体效率;避免频繁的创建销毁线程
线程复用原理
- 定义:线程池中,同一线程可以从阻塞队列中不断获取新任务执行
- 线程池将线程和任务解耦,线程和任务相互独立,摆脱了之前通过Thread创建线程时的”一个线程对应一个任务“的限制
- 原理:任务的逻辑没有被写入到线程run方法中,而是利用线程去调用任务的run方法。线程池对Thread进行了封装,每次执行任务不会调用Thread.Start()来创建新线程,而是让每个线程去执行一个循环任务,这个循环任务不断检查是否有任务需要被执行,如果有,则调用该任务的run方法
Spring
是什么?
轻量级开源J2EE框架;容器框架,装javabean;中间层框架,例如将Struts和hibernate粘合在一起
轻量级控制反转(IOC)和面向切片(AOP)的容器框架
- 从大小和开销两方面而言spring都是轻量级的
- 通过控制反转IOC实现低耦合
- 面向切面编程,允许通过分离应用的业务逻辑与系统级服务进行内聚性开发
- 包含并管理应用对象(Bean)的配置和声明周期,容器
- 将简单的组件配置、组合成为复杂的应用,框架
AOP
- OOP允许定义从上到下的关系,但并不适合定义从左到右的关系,例如日志功能,会导致大量代码重复,不利于各个模块的重用
- AOP将程序中的交叉业务逻辑(比如安全、日志、事务)封装成一个切面,注入到目标对象(具体业务逻辑)中,从而在某个方法执行的前、后额外做一些事情,对该功能实现增强操作
IOC
容器概念
- 实质上是个map,在项目启动时读取配置文件中的bean节点(bean对象、@repository、@Service、@controller、@component),创建对象(反射)放到map中,id是对象名;使用时,扫描到@Autowire、@Resource等注解,xml节点的ref属性等,会通过依赖注入(DI)注入
控制反转
引入IOC前
- 若A依赖于B,则在A代码执行时,B何时被创建取决于A
引入IOC后
- 所有对象的控制权被交给IOC容器,当A需要使用对象B时,由ioc容器创建一个对象B交给A,A对B的获取从主动变为被动,即,控制反转
依赖注入
- 实现IOC的方法,在IOC容器运行期间,获取依赖对象的过程由自身管理变为IOC容器主动注入。动态的将某种依赖关系注入到对象中
BeanFactory和ApplicationContext区别(Bean容器)
ApplicationContext是BeanFactory的子接口。扩展了功能
- 集成,MessageSource,支持国际化
- 统一的资源文件访问方式
- 可以在监听器中注册bean事件
- 同时加载多个配置文件
- 载入多个(有继承关系)上下文,是得每一个上下文都专注于一个特定的层次,比如应用的web层
Bean的创建
BeanFactory延迟加载,使用时才对Bean对象进行加载实例化,这样无法提前返现一些Spring配置问题,例如Bean的某个属性没有注入
ApplicationContext在容器启动时创建所有的Bean,有利于检查所依赖的属性是否注入,确保程序需要某个实例bean时不需要等待
- 占用内存空间大,启动慢
创建方式
- BeanFactory通常以编程的方式创建,ApplicationContext还可以以声明的方式创建,如使用ContextLoader
后置处理器
- 都支持BeanPostProcessor、BeanFactoryPostProcessor
- BeanFactory需要手动注册,ApplicationContext自动注册
Spring Bean的生命周期
解析类,获取类本身的属性和Spring的属性,得到BeanDefinition
如果有多个构造方法,则要推断构造方法
确定好构造方法,实例化得到一个对象
对对象中加了@Autowired注解的属性进行属性填充
回调Aware方法,如BeanNameAware\BeanFactoryAware,注入Spring特有的属性
调用BeanPostProcessor的初始化前的方法
调用初始化方法
嗲用BeanPostProcessor的初始化后的方法,在这里会进行AOP
若当前创建的bean是单例则会吧bean放入单例池
使用Bean
Spring容器关闭时调用DisposableBean中的destory()方法
SpringBean支持的几种bean的作用域
singleton(默认,单例)
- 每个容器中只有一个bean的实例
- 由BeanFactory维护,生命周期与IOC一致
prototype(原型模式)
- 每次注入都会创建一个新的对象
request
- 一个HTTP请求创建一个实例对象
session
- 每个session中有一个bean的实例
application
- 在ServletContext的生命周期中复用的一个单例对象,可以跨Spring容器
websocket
- websocket生命周期中复用的单例对象
global-session
- 全局作用域
Spring框架中的单例Bean是线程安全的吗
不安全
- 框架没有对bean进行多线程的封装处理,如果bean是有状态的(存储数据),则应该修改bean的作用域,从singleton到prototype;或者使用ThreadLocal存储数据;或者加锁synchronized
Spring框架中用到了哪些设计模式
简单工厂
- BeanFactory的getBean
工厂方法
- FactoryBean对象,最后返回bean.getObject(),不一定返回该bean对象
单例模式
- 一个类仅有一个实例,并提供一个全局访问点
适配器模式
- 每个Controller对应一个适配器实现类,代替controller执行其方法
动态代理
- AOP织入时会为原对象生成一个动态代理对象,直接返回增强后的对象
装饰器模式
- 功能增加,Wrapper或者Decorator结尾,先获取obj,再进行增强
观察者模式
- 一种一对多的依赖关系,多个观察者对象同时坚挺某一个主题对象,当主题对象状态变化时会通知所有观察者对象更新自己。常用于listener的实现
策略模式
- 定义一系列算法,把他们一个个封装起来,并使得他们可以相互替换,如支付方式的实现,用户选择支付方式,后端返回支付后的状态,不同的支付方式被实现一个共同的支付接口
Spring事务
实现方式
编程式
声明式
-
- Spring扫描到该注解后,会生成一个该类的代理对象,getbean()获取的也是代理对象,当执行有注释的方法时,会先将自动提交设置为false,再执行,若执行无误则提交,否则回滚(默认遇到RuntimeException和Error时回滚)
-
隔离级别
数据库隔离级别+默认级别
read uncommitted(未提交读)
read committed(提交读、不可重复读)
repeatable read(可重复读)
- 确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新
serializable(可串行化)
- 事务顺序执行
默认级别指不同的数据库有不同的默认级别,如Mysql默认rr
当Spring和数据配置的隔离级别冲突时,以spring设置为准,如果spring设置的隔离级别数据库不支持,则以数据库设置为准
传播机制
方法A是一个事务方法,当A调用了B,如果B采用了以下传播类型
REQUIRED(默认)
- A存在一个事务,则B加入A,否则B新建一个事务执行
SUPPORTS
- A存在事务,则B加入A,否则B以非事务方式运行
MANDATORY
- A存在事务,则B加入A,否则B抛出异常
REQUIRES_NEW
- B新建一个事务,A的事务被挂起,AB事务独立运行
NOT_SUPPORTED
- 以非事务方式运行,如果当前存在事务,则挂起
NEVER
- 不使用事务,如果当前存在事务,抛出异常
NESTED
- 如果A存在事务,则B作为嵌套子事务执行,否则B新建一个事务。当A回滚,B回滚,当B回滚,A不一定回滚,因为A可以catchB的异常
什么时候会失效
- 发生自调用,Spring事务通过代理对象执行(AOP)如果在代码中有this.fuc()这种调用方式显然调用的是Bean对象本身
- @TransActional注解加在了非public方法上
- 数据库本身不支持Spring事务,如Myisam数据库
- 没有加入Spring容器,没有@Service、@Compount等注解
- 异常被catch,导致事务无法回滚