面试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事务

实现方式

  • 编程式

  • 声明式

    • 注解@Transactional

      • 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,导致事务无法回滚