java面试宝典:2018最常见的面试题:

Spring 概述依赖注入Spring beansSpring注解Spring数据访问Spring面向切面编程(AOP)Spring MVC

 

Spring 概述

 

1. 什么是spring?


 

Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。

 

2. 使用Spring框架的好处是什么?


 

  • 轻量:Spring 是轻量的,基本的版本大约2MB
  • 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们
  • 面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开
  • 容器:Spring 包含并管理应用中对象的生命周期和配置
  • MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品
  • 事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)
  • 异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常


 

3.  Spring由哪些模块组成?


 

以下是Spring 框架的基本模块:

  • Core module
  • Bean module
  • Context module
  • Expression Language module
  • JDBC module
  • ORM module
  • OXM module
  • Java Messaging Service(JMS) module
  • Transaction module
  • Web module
  • Web-Servlet module
  • Web-Struts module
  • Web-Portlet module

4. 核心容器(应用上下文) 模块


 

这是基本的Spring模块,提供spring 框架的基础功能,BeanFactory 是 任何以spring为基础的应用的核心。Spring 框架建立在此模块之上,它使Spring成为一个容器。

 

5. BeanFactory – BeanFactory 实现举例


 

Bean 工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从正真的应用代码中分离。最常用的BeanFactory 实现是XmlBeanFactory 类。

 

6. XMLBeanFactory 


 

最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory ,它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。

 

7. 解释AOP模块


 

AOP模块用于发给我们的Spring应用做面向切面的开发, 很多支持由AOP联盟提供,这样就确保了Spring和其他AOP框架的共通性。这个模块将元数据编程引入Spring。

 

8. 解释JDBC抽象和DAO模块


 

通过使用JDBC抽象和DAO模块,保证数据库代码的简洁,并能避免数据库资源错误关闭导致的问题,它在各种不同的数据库的错误信息之上,提供了一个统一的异常访问层。它还利用Spring的AOP 模块给Spring应用中的对象提供事务管理服务。

 

9. 解释对象/关系映射集成模块


 

Spring 通过提供ORM  ,Object Relational Mapping(对象关系映射)模块,支持我们在直接JDBC之上使用一个对象/关系映射映射(ORM(对象关系映射))工具,Spring 支持集成主流的ORM框架,如Hiberate,JDO和 iBATIS SQL Maps。Spring的事务管理同样支持以上所有ORM框架及JDBC。

 

10.  解释WEB 模块


 

Spring的WEB模块是构建在application context 模块基础之上,提供一个适合web应用的上下文。这个模块也包括支持多种面向web的任务,如透明地处理多个文件上传请求和程序级请求参数的绑定到你的业务对象。它也有对Jakarta Struts的支持。

 

12.  Spring配置文件


 

Spring配置文件是个XML 文件,这个文件包含了类信息,描述了如何配置它们,以及如何相互调用。

 

13.  什么是Spring IOC(控制反转) 容器?


 

Spring IOC(控制反转) 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。

 

14.  IOC(控制反转)的优点是什么?


 

IOC(控制反转Inversion of Control) 或 依赖注入(DI,         Dependency Injection把应用的代码量降到最低。它使应用容易测试,单元测试不再需要单例和JNDI查找机制。最小的代价和最小的侵入性使松散耦合得以实现。IOC容器支持加载服务时的饿汉式初始化和懒加载。

 

15. ApplicationContext(要获取bean必须先获取到ApplicationContext对象)通常的实现是什么?


 

  • FileSystemXmlApplicationContext 此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
  • ClassPathXmlApplicationContext此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。
  • WebXmlApplicationContext此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。


 

16. Bean 工厂和 Application contexts  有什么区别?


 

Application contexts提供一种方法处理文本消息,一个通常的做法是加载文件资源(比如镜像),它们可以向注册为监听器的bean发布事件。另外,在容器或容器内的对象上执行的那些不得不由bean工厂以程序化方式处理的操作,可以在Application contexts中以声明的方式处理。Application contexts实现了MessageSource接口,该接口的实现以可插拔的方式提供获取本地化消息的方法。

 

17. 一个Spring的应用看起来象什么?


 

  • 一个定义了一些功能的接口
  • 这实现包括属性,它的Setter , getter 方法和函数等
  • Spring AOP
  • Spring 的XML 配置文件
  • 使用以上功能的客户端程序

 

 

依赖注入

 

18. 什么是Spring的依赖注入?


 

依赖注入,是IOC的一个方面,是个通常的概念,它有多种解释。这概念是说你不用创建对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和服务,但是要在配置文件里描述哪些组件需要哪些服务,之后一个容器(IOC容器)负责把他们组装起来。

 

19.  有哪些不同类型的(依赖注入)方式?


 

  • 构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
  • Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。


 

20. 哪种依赖注入方式你建议使用,构造器注入,还是 Setter方法注入?


 

你两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。

 

 

Spring Beans

 

21.什么是Spring beans?


 

Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中<bean/> 的形式定义。


 

Spring 框架定义的beans都是单件beans。在bean tag中有个属性”singleton”,如果它被赋为TRUE,bean 就是单件,否则就是一个 prototype bean。默认是TRUE,所以所有在Spring框架中的beans 缺省都是单件。点击这里一图Spring Bean的生命周期。

 

22. 一个 Spring Bean 定义 包含什么?


 

一个Spring Bean 的定义包含容器必知的所有配置元数据,包括如何创建一个bean,它的生命周期详情及它的依赖。

 

23. 如何给Spring 容器提供配置元数据?


 

这里有三种重要的方法给Spring 容器提供配置元数据。

XML配置文件。

基于注解的配置。

基于java的配置。

 

24. 你怎样定义类的作用域?


 

当定义一个<bean> 在Spring里,我们还能给这个bean声明一个作用域。它可以通过bean 定义中的scope属性来定义。如,当Spring要在需要的时候每次生产一个新的bean实例,bean的scope属性被指定为prototype。另一方面,一个bean每次使用的时候必须返回同一个实例,这个bean的scope 属性 必须设为 singleton。

 

25. 解释Spring支持的几种bean的作用域


 

Spring框架支持以下五种bean的作用域:

  • singleton : bean在每个Spring ioc 容器中只有一个实例。
  • prototype一个bean的定义可以有多个实例。
  • request每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • session在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • global-session在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

缺省的Spring bean 的作用域是Singleton。

 

26. Spring框架中的单例bean是线程安全的吗?


 

不,Spring框架中的单例bean不是线程安全的。

 

27. 解释Spring框架中bean的生命周期


 

  • Spring容器 从XML 文件中读取bean的定义,并实例化bean。
  • Spring根据bean的定义填充所有的属性。
  • 如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。
  • 如果Bean 实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。
  • 如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们。
  • 如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。
  • 如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将被调用。
  • 如果bean实现了 DisposableBean,它将调用destroy()方法。


 

点击这里一图Spring Bean的生命周期。


 

28.  哪些是重要的bean生命周期方法? 你能重载它们吗?


 

有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown  它是在容器卸载类的时候被调用。

The bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。

 

29. 什么是Spring的内部bean?


 

当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean,为了定义inner bean,在Spring 的 基于XML的 配置元数据中,可以在 <property/>或 <constructor-arg/> 元素内使用<bean/> 元素,内部bean通常是匿名的,它们的Scope一般是prototype。

 

30. 在 Spring中如何注入一个java集合?


 

Spring提供以下几种集合的配置元素:

  • <list>类型用于注入一列值,允许有相同的值。
  • <set> 类型用于注入一组值,不允许有相同的值。
  • <map> 类型用于注入一组键值对,键和值都可以为任意类型。
  • <props>类型用于注入一组键值对,键和值都只能为String类型。


 

31. 什么是bean装配?


 

装配,或bean 装配是指在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,如何通过依赖注入来把它们装配到一起。

 

32. 什么是bean的自动装配?


 

Spring 容器能够自动装配相互合作的bean,这意味着容器不需要<constructor-arg>和<property>配置,能通过Bean工厂自动处理bean之间的协作。

 

33. 解释不同方式的自动装配


 

有五种自动装配的方式,可以用来指导Spring容器用自动装配方式来进行依赖注入

  • no默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
  • byName通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。
  • byType通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
  • constructor这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
  • autodetect首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。


 

34.自动装配有哪些局限性?


 

自动装配的局限性是:

  • 重写:你仍需用 <constructor-arg>和 <property> 配置来定义依赖,意味着总要重写自动装配。
  • 基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。
  • 模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。


 

35. 你可以在Spring中注入一个null 和一个空字符串吗?


 

可以。

 

 

Spring注解

 

36. 什么是基于Java的Spring注解配置? 给一些注解的例子


 

基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。


 

以@Configuration 注解为例,它用来标记类可以当做一个bean的定义,被Spring IOC容器使用。另一个例子是@Bean注解,它表示此方法将要返回一个对象,作为一个bean注册进Spring应用上下文。点击这里学习JAVA几大元注解。

 

37. 什么是基于注解的容器配置?


 

相对于XML文件,注解型的配置依赖于通过字节码元数据装配组件,而非尖括号的声明。

开发者通过在相应的类,方法或属性上使用注解的方式,直接组件类中进行配置,而不是使用xml表述bean的装配关系。

 

38. 怎样开启注解装配?


 

注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在Spring配置文件中配置 <context:annotation-config/>元素。

 

39. @Required  注解


 

这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出BeanInitializationException。

 

40. @Autowired 注解


 

@Autowired 注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和@Required一样,修饰setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。

 

41. @Qualifier 注解


 

当有多个相同类型的bean却只有一个需要自动装配时,将@Qualifier 注解和@Autowire 注解结合使用以消除这种混淆,指定需要装配的确切的bean。点击这里学习更多常用注解。

 

 

Spring数据访问

 

42.在Spring框架中如何更有效地使用JDBC?


 

使用SpringJDBC 框架,资源管理和错误处理的代价都会被减轻。所以开发者只需写statements 和 queries从数据存取数据,JDBC也可以在Spring框架提供的模板类的帮助下更有效地被使用,这个模板叫JdbcTemplate (例子见这里here)

 

43. JdbcTemplate


 

JdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。

 

44. Spring对DAO的支持


 

Spring对数据访问对象(DAO)的支持旨在简化它和数据访问技术如JDBC,Hibernate or JDO 结合使用。这使我们可以方便切换持久层。编码时也不用担心会捕获每种技术特有的异常。

 

45. 使用Spring通过什么方式访问Hibernate?


 

在Spring中有两种方式访问Hibernate:

  • 控制反转  Hibernate Template和 Callback
  • 继承 HibernateDAOSupport提供一个AOP 拦截器


 

46. Spring支持的ORM


 

Spring支持以下ORM:

  • Hibernate
  • iBatis
  • JPA (Java Persistence API)
  • TopLink
  • JDO (Java Data Objects)
  • OJB


 

47.如何通过HibernateDaoSupport将Spring和Hibernate结合起来?


 

用Spring的 SessionFactory 调用 LocalSessionFactory。集成过程分三步:

  • 配置the Hibernate SessionFactory
  • 继承HibernateDaoSupport实现一个DAO
  • 在AOP支持的事务中装配


 

48. Spring支持的事务管理类型


 

Spring支持两种类型的事务管理:

  • 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
  • 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。


 

49. Spring框架的事务管理有哪些优点?


 

  • 它为不同的事务API  如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式。
  • 它为编程式事务管理提供了一套简单的API而不是一些复杂的事务API如
  • 它支持声明式事务管理。
  • 它和Spring各种数据访问抽象层很好得集成。


 

50. 你更倾向用那种事务管理类型?


 

大多数Spring框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。

 

 

Spring面向切面编程(AOP)

 

51.  解释AOP


 

面向切面的编程,或AOP, 是一种编程技术,允许程序模块化横向切割关注点,或横切典型的责任划分,如日志和事务管理。

 

52. Aspect 切面


 

AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。

 

52. 在Spring AOP 中,关注点和横切关注的区别是什么?


 

关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。


 

横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。

 

54. 连接点


 

连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring AOP的位置。

 

55. 通知


 

通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。


 

Spring切面可以应用五种类型的通知:

  • before前置通知,在一个方法执行前被调用
  • after在方法执行之后调用的通知,无论方法执行是否成功
  • after-returning仅当方法成功完成后执行的通知
  • after-throwing在方法抛出异常退出时执行的通知
  • around在方法执行之前和之后调用的通知


 

56. 切点


 

切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。

 

57. 什么是引入?


 

引入允许我们在已存在的类中增加新的方法和属性。

 

58. 什么是目标对象?


 

被一个或者多个切面所通知的对象。它通常是一个代理对象。也指被通知(advised)对象。

 

59. 什么是代理?


 

代理是通知目标对象后创建的对象。从客户端的角度看,代理对象和目标对象是一样的。

 

60. 有几种不同类型的自动代理?


 

BeanNameAutoProxyCreator

DefaultAdvisorAutoProxyCreator

Metadata autoproxying

 

61. 什么是织入。什么是织入应用的不同点?


 

织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程。

织入可以在编译时,加载时,或运行时完成。

 

62. 解释基于XML Schema方式的切面实现


 

在这种情况下,切面由常规类以及基于XML的配置实现。

 

63. 解释基于注解的切面实现


 

在这种情况下(基于@AspectJ的实现),涉及到的切面声明的风格与带有java5标注的普通java类一致。


 

 

Spring 的MVC

 

64. 什么是Spring的MVC框架?


 

Spring 配备构建Web 应用的全功能MVC框架。Spring可以很便捷地和其他MVC框架集成,如Struts,Spring 的MVC框架用控制反转把业务对象和控制逻辑清晰地隔离。它也允许以声明的方式把请求参数和业务对象绑定。

 

65. DispatcherServlet


 

Spring的MVC框架是围绕DispatcherServlet来设计的,它用来处理所有的HTTP请求和响应。

 

66. WebApplicationContext


 

WebApplicationContext 继承了ApplicationContext  并增加了一些WEB应用必备的特有功能,它不同于一般的ApplicationContext ,因为它能处理主题,并找到被关联的servlet。

 

67. 什么是Spring MVC框架的控制器?


 

控制器提供一个访问应用程序的行为,此行为通常通过服务接口实现。控制器解析用户输入并将其转换为一个由视图呈现给用户的模型。Spring用一个非常抽象的方式实现了一个控制层,允许用户创建多种用途的控制器。

 

68. @Controller 注解


 

该注解表明该类扮演控制器的角色,Spring不需要你继承任何其他控制器基类或引用Servlet API。

 

69. @RequestMapping 注解


 

该注解是用来映射一个URL到一个类或一个特定的方处理法上。

往期干货推荐

1. Spring Boot 2.x 来临,本文将带你起飞

2. Spring Boot 2.x 新特性总结及迁移指南

3. Spring Boot 2.x 启动全过程源码分析(上)

4. Spring Boot 整合 Thymeleaf 模板引擎

5. Spring Boot 核心配置文件详解

 

 

面试题二:

一、自我介绍,以及介绍最近一个项目

简单介绍一下自己,姓名,籍贯,毕业以来工作多少年,在哪些公司工作过。一语带过即可。然后介绍一下最近做的一个项目,包含整体架构设计,涉及前后端框架,缓存,中间件以及数据库等。

二、String特性。StringBuffer 和 StringBuilder 区别

String a = "str"; String b = new String("str");问 a == b , a.equals(b) 的值是true还是false? 

 

这里涉及到 == 与 equals 的区别:
 

 

==:比较引用类型比较的是地址值是否相同

equals:比较引用类型默认也是比较地址值是否相同,而String类重写了equals()方法,比较的是内容是否相同。


 

String a = "str"; ,"str" 存到方法区的字符串常量池。而String b = new String("str");,new String() 存到堆中,在指向常量池的"str"。用 == 时,a指向的是字符串常量池地址,而b指向的是 new String() 堆中存放地址。所以必然false。而String 的equals 是比较内容。所以是 true。
 

关于String要知道它是被final修饰的不可变类。频繁修改最好用 StringBuffer 或 StringBuilder。二者的区别,StringBuilder效率高,线程不安全。StringBuffer线程安全。

三、ArrayList 和 LinkedList 原理、区别以及底层数据结构

  • ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 
  • 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 
  • 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 

知道这些还不够,最好看一遍源码。了解一下存在这些区别的原因。以第三点为例,为什么新增删除 ArrayList占优势呢?看了源码就会了解到。 ArrayList 底层结构是数组,首先数组是不可变的,新增要检查数组大小是否满足,不满足需要扩容,扩容就需要新建数组,然后将原数组copy到新数组,再制空原数组,在新数组新增。删除时,ArrayList会将后面部分的元素依次往上挪一个位置(就是copy)。其他方法不再描述,具体需要先熟悉一下源码。有时候会问到怎么保证ArrayList 线程安全。用Collections.synchronizedList()或改用 Vector。原理是方法前加入了synchronize关键字实现加锁。点击这里查看BATJ必考的Java面试题。

四、HashMap、HashTable、ConcurrentHashMap 原理、源码、数据结构,线程是否安全

HashMap是数组加链表结构。通过key的hashcode 找到数组角标。再遍历链表。
 

HashTable和HashMap区别,属于线程安全,key value 不能为 null。
 

ConcurrentHashMap也是线程安全,结构可理解成 分裂数组+HashTable。效率比HashTable高,因为HashTable是整个Map加锁,ConcurrentHashMap是把锁加到 各个分裂数组上。

五、Lock 和 synchronize 实现原理与区别。简述乐观锁悲观锁。分布式锁实现方式

synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。synchronized在锁定时如果方法块抛出异常,JVM 会自动将锁释放掉,不会因为出了异常没有释放锁造成线程死锁。但是 Lock 的话就享受不到 JVM 带来自动的功能,出现异常时必须在 finally 将锁释放掉,否则将会引起死锁。在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize。 点击这里查看BATJ必考的Java面试题。

分布式锁实现方式:基于数据库实现,基于缓存(Redis,memcached,tair)实现,基于Zookeeper实现。

六、SpringMVC流程。常用注解。Spring的IOC和AOP

SpringMVC流程:
 

  1. 用户发送请求至前端控制器DispatcherServlet。
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器。
  3. 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  4. DispatcherServlet调用HandlerAdapter处理器适配器。
  5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
  6. Controller执行完成返回ModelAndView。
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
  9. ViewReslover解析后返回具体View。
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
  11. DispatcherServlet响应用户。

七、微服务架构Spring Boot ,Spring Cloud

1.微服务的优势

2.Spring Cloud 五大神兽:(要知道实现原理,Eureka 是重点)

  • 服务发现——Netflix Eureka
  • 客服端负载均衡——Netflix Ribbon
  • 断路器——Netflix Hystrix
  • 服务网关——Netflix Zuul
  • 分布式配置——Spring Cloud Config

八、多线程与高并发

    多线程要了解线程常用方法。线程池的实现方式。如何避免死锁。生命周期。并发包常用类原理和应用场景:计数器CountdownLatch,栅栏CyclicBarrier,信号量semaphore,交换。点击这里查看BATJ必考的Java面试题。

九、事务相关

项目中事务处理方案。

4种事务特性:
 

  • 原子性 (atomicity):强调事务的不可分割. 
  • 一致性 (consistency):事务的执行的前后数据的完整性保持一致. 
  • 隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰 
  • 持久性(durability) :事务一旦结束,数据就持久到数据库

5种隔离级别:

  • DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别
  • 未提交读(read uncommited) :脏读,不可重复读,虚读都有可能发生 
  • 已提交读 (read commited):避免脏读。但是不可重复读和虚读有可能发生 
  • 可重复读 (repeatable read) :避免脏读和不可重复读.但是虚读有可能发生
  • 串行化的 (serializable) :避免以上所有读问题

7种传播行为:

    保证同一个事务中 

  • PROPAGATION_REQUIRED 支持当前事务,如果不存在 就新建一个(默认) 
  • PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务 
  • PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常 

   保证没有在同一个事务中 
 

  • PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务 
  • PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务 
  • PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常 
  • PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行

更多请参考博客:www.javastack.cn

十、常用那些设计模式,说出原理,已经框架中用到哪些。单例的几种写法

23种设计模式至少要了解Spring框架用到的一些设计模式。bean单例 beanfactory工厂 AOP动态代理等。
 

单例有饿汉式,懒汉式,静态内部类等方式。这些方式并非真正安全,可以用反射拿到,用枚举可以避免。

十一、常用中间件RabbitMQ,kafka等,原理、区别、优缺点

中间件的应用场景。

RabbitMQ是一个AMQP实现,传统的messaging queue系统实现,基于Erlang。老牌MQ产品了。AMQP协议更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量还在其次。
 

Kafka是linkedin开源的MQ系统,主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输,0.8开始支持复制,不支持事务,适合产生大量数据的互联网服务的数据收集业务。

十二、缓存:Redis原理,数据结构,集群几种方式。和memcache区别

十三、HTTP协议,RPC协议,Socket协议

十四、Nginx实现负载均衡的几种方式

轮询,随机,哈希,加权轮询,加权随机,最小连接数等。
 

十五、java1.8新特性

十六、关于jvm调优

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JVM的类加载机制是什么?有哪些实现方式?

类加载机制:

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法去内,然后在堆区创建一个java.lang.Class对象,用来封装在方法区内的数据结构。类的加载最终是在堆区内的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

类加载有三种方式:

1)命令行启动应用时候由JVM初始化加载

2)通过Class.forName()方法动态加载

3)通过ClassLoader.loadClass()方法动态加载

JVM的常见垃圾回收算法?

1)标记-清楚算法:前后线标记处所有需要回收的对象,在标记完成后统一回收有被标记的对象。

2)复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当一块内存用完了,将其存在另外一块上面,然后再把已使用过的内存空间一次清理掉。

3)标记-整理算法:标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所一端移动,然后直接清理掉端边界以外的内存。

4)分代收集算法:一般是把Java堆分为新生代和老年代,根据各个年代的特点采用最适当的收集算法。新生代都发现有大批对象死去,选用复制算法。老年代中因为对象存活率高,必须使用“标记-清理”或“标记-整理”算法来进行回收。

JVM调优的常见命令行工具有哪些?JVM常见的调优参数有哪些?

(1)JVM调优的常见命令工具包括:

1)jps命令用于查询正在运行的JVM进程,

2)jstat可以实时显示本地或远程JVM进程中类装载、内存、垃圾收集、JIT编译等数据

3)jinfo用于查询当前运行这的JVM属性和参数的值。

4)jmap用于显示当前Java堆和永久代的详细信息

5)jhat用于分析使用jmap生成的dump文件,是JDK自带的工具

6)jstack用于生成当前JVM的所有线程快照,线程快照是虚拟机每一条线程正在执行的方法,目的是定位线程出现长时间停顿的原因。

(2)JVM常见的调优参数包括:

-Xmx

  指定java程序的最大堆内存, 使用java -Xmx5000M -version判断当前系统能分配的最大堆内存

-Xms

  指定最小堆内存, 通常设置成跟最大堆内存一样,减少GC

-Xmn

  设置年轻代大小。整个堆大小=年轻代大小 + 年老代大小。所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

-Xss

  指定线程的最大栈空间, 此参数决定了java函数调用的深度, 值越大调用深度越深, 若值太小则容易出栈溢出错误(StackOverflowError)

-XX:PermSize

  指定方法区(永久区)的初始值,默认是物理内存的1/64, 在Java8永久区移除, 代之的是元数据区, 由-XX:MetaspaceSize指定

-XX:MaxPermSize

  指定方法区的最大值, 默认是物理内存的1/4, 在java8中由-XX:MaxMetaspaceSize指定元数据区的大小

-XX:NewRatio=n

  年老代与年轻代的比值,-XX:NewRatio=2, 表示年老代与年轻代的比值为2:1

-XX:SurvivorRatio=n

  Eden区与Survivor区的大小比值,-XX:SurvivorRatio=8表示Eden区与Survivor区的大小比值是8:1:1,因为Survivor区有两个(from, to)

ConcurrentHashMap加锁机制是什么,详细说一下?

HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

G1收集器简介?以及它的内存划分怎么样的?

(1)简介:

Garbage-First(G1,垃圾优先)收集器是服务类型的收集器,目标是多处理器机器、大内存机器。它高度符合垃圾收集暂停时间的目标,同时实现高吞吐量。Oracle JDK 7 update 4 以及更新发布版完全支持G1垃圾收集器

(2)G1的内存划分方式:

它是将堆内存被划分为多个大小相等的 heap 区,每个heap区都是逻辑上连续的一段内存(virtual memory). 其中一部分区域被当成老一代收集器相同的角色(eden, survivor, old), 但每个角色的区域个数都不是固定的。这在内存使用上提供了更多的灵活性

在重写equals方法时,需要遵循哪些约定,具体介绍一下?

重写equals方法时需要遵循通用约定:自反性、对称性、传递性、一致性.、非空性

1)自反性

对于任何非null的引用值x,x.equals(x)必须返回true。---这一点基本上不会有啥问题

2)对称性

对于任何非null的引用值x和y,当且仅当x.equals(y)为true时,y.equals(x)也为true。

3)传递性

对于任何非null的引用值x、y、z。如果x.equals(y)==true,y.equals(z)==true,那么x.equals(z)==true。

4) 一致性

对于任何非null的引用值x和y,只要equals的比较操作在对象所用的信息没有被修改,那么多次调用x.eqals(y)就会一致性地返回true,或者一致性的返回false。

5)非空性

所有比较的对象都不能为空。

Synchronized优化后的锁机制简单介绍一下,包括自旋锁、偏向锁、轻量级锁、重量级锁?

自旋锁:

线程自旋说白了就是让cup在做无用功,比如:可以执行几次for循环,可以执行几条空的汇编指令,目的是占着CPU不放,等待获取锁的机会。如果旋的时间过长会影响整体性能,时间过短又达不到延迟阻塞的目的。

偏向锁

偏向锁就是一旦线程第一次获得了监视对象,之后让监视对象“偏向”这个线程,之后的多次调用则可以避免CAS操作,

说白了就是置个变量,如果发现为true则无需再走各种加锁/解锁流程。

轻量级锁:

轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;

重量级锁

重量锁在JVM中又叫对象监视器(Monitor),它很像C中的Mutex,除了具备Mutex(0|1)互斥的功能,它还负责实现了Semaphore(信号量)的功能,也就是说它至少包含一个竞争锁的队列,和一个信号阻塞队列(wait队列),前者负责做互斥,后一个用于做线程同步。

偏向锁、轻量级锁、重量级锁的对比

 

Redis和Memcache区别对比?如何选择这两个技术?

区别:

1) Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等。

2)Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。

3)虚拟内存--Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘

4)过期策略--memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10

5)分布式--设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从

6)存储数据安全--memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化)

7)灾难恢复--memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复

8)Redis支持数据的备份,即master-slave模式的数据备份。

选型:

若是简单的存取key-value这样的数据用memcache好一些

若是要支持数据持久化,多数据类型(如集合、散列之类的),用列表类型做队列之类的高级应用,就用redis

Redis的持久化机制是什么?各自的优缺点?

redis提供两种持久化机制RDB和AOF机制。

1)RDB持久化方式:

是指用数据集快照的方式记录redis数据库的所有键值对。

优点:

  1.只有一个文件dump.rdb,方便持久化。

  2.容灾性好,一个文件可以保存到安全的磁盘。

  3.性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。

  4.相对于数据集大时,比AOF的启动效率更高。

缺点:

  1.数据安全性低。

2)AOF持久化方式:

是指所有的命令行记录以redis命令请求协议的格式保存为aof文件。

优点:

  1.数据安全,aof持久化可以配置appendfsync属性,有always,每进行一次命令操作就记录到aof文件中一次。

  2.通过append模式写文件,即使中途服务器宕机,可以通过redis-check-aof工具解决数据一致性问题。

  3.AOF机制的rewrite模式。

缺点:

  1.文件会比RDB形式的文件大。

  2.数据集大的时候,比rdb启动效率低。

Mysql的数据库表锁、行锁、页级锁?

表级,直接锁定整张表,在你锁定期间,其它进程无法对该表进行写操作。如果你是写锁,则其它进程则读也不允许

行级,,仅对指定的记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。

页级,表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。

数据库的四大特征,数据库的隔离级别?

数据库的四大特征:

(1)原子性(Atomicity)

原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。

(2)一致性(Consistency)

一个事务执行之前和执行之后都必须处于一致性状态。

(3)隔离性(Isolation)

隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

4)持久性(Durability)

持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的。

 数据库的隔离级别:

1)Serializable (串行化):可避免脏读、不可重复读、幻读的发生。2)Repeatable read (可重复读):可避免脏读、不可重复读的发生。3)Read committed (读已提交):可避免脏读的发生。4)Read uncommitted (读未提交):最低级别,任何情况都无法保证。

set集合从原理上如何保证不重复

1)在往set中添加元素时,如果指定元素不存在,则添加成功。也就是说,如果set中不存在(e==null ? e1==null : e.queals(e1))的元素e1,则e1能添加到set中。

2)具体来讲:当向HashSet中添加元素的时候,首先计算元素的hashcode值,然后用这个(元素的hashcode)%(HashMap集合的大小)+1计算出这个元素的存储位置,如果这个位置位空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。

HashMap和HashTable的主要区别是什么?,两者底层实现的数据结构是什么?

HashMap和HashTable的区别:

二者都实现了Map 接口,是将惟一键映射到特定的值上;主要区别在于:

1)HashMap 没有排序,允许一个null 键和多个null 值,而Hashtable 不允许;

2)HashMap 把Hashtable 的contains 方法去掉了,改成containsvalue 和

containsKey,因为contains 方法容易让人引起误解;

3)Hashtable 继承自Dictionary 类,HashMap 是Java1.2 引进的Map 接口的实现;

4)Hashtable 的方法是Synchronize 的,而HashMap 不是,在多个线程访问Hashtable 时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。Hashtable 和HashMap 采用的hash/rehash 算法大致一样,所以性能不会有很大的差异。

HashMap和HashTable的底层实现数据结构:

HashMap和Hashtable的底层实现都是数组+链表结构实现的

HashMap何时扩容,扩容的算法是什么?

HashMap何时扩容:

当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值---即当前数组的长度乘以加载因子的值的时候,就要自动扩容

扩容的算法是什么:

扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组

Java的虚拟机JVM的两个内存:栈内存和堆内存的区别是什么?

Java把内存划分成两种:一种是栈内存,一种是堆内存。两者的区别是:

1)栈内存:在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。 当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。

2)堆内存:堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。

Java中对异常是如何进行分类的?

异常整体分类:

1)Java异常结构中定义有Throwable类。 Exception和Error为其子类。

2)其中Exception表示由于网络故障、文件损坏、设备错误、用户输入非法情况导致的异常;

3)而Error标识Java运行时环境出现的错误,例如:JVM内存耗尽。

数据库设计中常讲的三范式是指什么?

1)第一范式1NF(域的原子性)

如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式

2)第二范式2NF(表中除主键外的字段都完全依赖主键)

第二范式是在第一范式基础上建立的。第二范式有两个重点:(1)表中必须有主键;(2)其他非主属性必须完全依赖主键,不能只依赖主键的一部分(主要针对联合主键而言)。

3)第三范式3NF(表中除主键外的字段都完全直接依赖,不能是传递依赖)

不能是传递依赖,即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。第二范式和第三范式区分的关键点:2NF:非主键列是否完全依赖于主键,还是依赖于主键的一部分;3NF:非主键列是直接依赖于主键,还是直接依赖于非主键列。

Java中的线程池共有几种?

Java四种线程池

第一种:newCachedThreadPool

  创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。

第二种:newFixedThreadPool

  创建一个指定工作线程数量的线程池

第三种:newScheduledThreadPool

创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

第四种:newSingleThreadExecutor

  创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。

volatile和synchronized区别

volatile和synchronized简介:

在Java中,为了保证多线程读写数据时保证数据的一致性,可以采用两种方式:

  1)使用synchronized关键字

  2)使用volatile关键字:用一句话概括volatile,它能够使变量在值发生改变时能尽快地让其他线程知道。

两者的区别:

1)volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.

2)volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.

3)volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性.

4)volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.

Spring的特性

1.方便解耦,简化开发

通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。

2.AOP编程的支持

通过Spring提供的AOP功能,方便进行面向切面的编程。

3.声明事物的支持

在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。

4.方便程序的测试

可以用非容器依赖的编程方式进行几乎所有的测试工作。例如:Spring对Junit4支持,可以通过注解方便的测试Spring程序。

5.方便集成各种优秀框架

Spring不排斥各种优秀的开源框架,相反,Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts,Hibernate、Hessian、Quartz)等的直接支持。

6.降低Java EE API的使用难度

Spring对很多难用的Java EE API(如JDBC,JavaMail,远程调用等)提供了一个薄薄的封装层,通过Spring的简易封装,这些Java EE API的使用难度大为降低。

spring aop的应用场景:

AOP用来封装横切关注点,具体可以在下面的场景中使用

Authentication 权限

Caching 缓存

Context passing 内容传递

Error handling 错误处理

Lazy loading 懒加载

Debugging 调试

logging, tracing, profiling and monitoring 记录跟踪 优化 校准

Performance optimization 性能优化

Persistence 持久化

Resource pooling 资源池

Synchronization 同步

Transactions 事务

Mybaits中#和$区别

1)${}是Properties文件中的变量占位符,它可以用于标签属性值和sql内部,属于静态文本替换,比如${driver}会被静态替换为com.mysql.jdbc.Driver。

2)#{}是sql的参数占位符,Mybatis会将sql中的#{}替换为?号,在sql执行前会使用PreparedStatement的参数设置方法,按序给sql的?号占位符设置参数值,比如ps.setInt(0, parameterValue),#{item.name}的取值方式为使用反射从参数对象中获取item对象的name属性值,相当于param.getItem().getName()。

排序都有哪几种方法?请列举。用JAVA 实现一个快速排序。

排序的方法有:

插入排序(直接插入排序、希尔排序),交换排序(冒泡排序、快速排序),选择排序(直接选择排序、堆排序),归并排序,分配排序(箱排序、基数排序);

快速排序的伪代码:

//使用快速排序方法对a[ 0 :n- 1 ]排序

从a[ 0 :n- 1 ]中选择一个元素作为middle,该元素为支点;

把余下的元素分割为两段left 和right,使得left 中的元素都小于等于支点,

而right 中的元素都大于等于支点;

递归地使用快速排序方法对left 进行排序;

递归地使用快速排序方法对right 进行排序;

所得结果为left + middle + right。

快速排序的Java代码实现如下:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

10 个 MyBatis 常见面试题

菜鸟架构 6月19日

↑ ↑ ↑点击上方 “Java面经” 关注我们↑ ↑ ↑ 

 

1. #{}和${}的区别是什么?

 

#{}是预编译处理,${}是字符串替换。

Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;

Mybatis在处理${}时,就是把${}替换成变量的值。

使用#{}可以有效的防止SQL注入,提高系统安全性。


 

2. 通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?

 

Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MappedStatement对象。
 

 

Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。

 

Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。


 

3. Mybatis是如何进行分页的?分页插件的原理是什么?

 

Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
 

 

分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。


 

4. Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?

 

第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe,Mybatis一样可以正常工作。

 

有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。


 

5. Xml映射文件中,除了常见的select|insert|update|delete标签之外,还有哪些标签?

 

注:这道题出自京东面试官。 

还有很多其他的标签,加上动态sql的9个标签,trim|where|set|foreach|if|choose|when|otherwise|bind等,其中为sql片段标签,通过标签引入sql片段,为不支持自增的主键生成策略标签。

 

6. 简述Mybatis的插件运行原理,以及如何编写一个插件
 

Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,还需要在配置文件中配置你编写的插件。


 

7. 一级、二级缓存

 

1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空。

 

2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。要开启二级缓存,你需要在你的 SQL 映射文件中添加一行:<cache/>


 

3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。


 

8. Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?

 

Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。

 

它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。


 

9. Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?

 

虽然Mybatis解析Xml映射文件是按照顺序解析的,但是,被引用的B标签依然可以定义在任何地方,Mybatis都可以正确识别。

 

原理是,Mybatis解析A标签,发现A标签引用了B标签,但是B标签尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已经存在,A标签也就可以正常解析完成了。


 

10. 简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?

 

Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象,其每个子元素会被解析为ParameterMapping对象。<resultMap>标签会被解析为ResultMap对象,其每个子元素会被解析为ResultMapping对象。每一个<select>、<insert>、<update>、<delete>标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql对象。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

史上最全Java面试题,带全部答案!

菜鸟架构 6月6日

↑ ↑ ↑点击上方 “Java面经” 关注我们↑ ↑ ↑ 


 

今天要谈的主题是关于求职,求职是在每个技术人员的生涯中都要经历多次。对于我们大部分人而言,在进入自己心仪的公司之前少不了准备工作,有一份全面细致面试题将帮助我们减少许多麻烦。在跳槽季来临之前,特地做这个系列的文章,一方面帮助自己巩固下基础,另一方面也希望帮助想要换工作的朋友。


面试题三:

相关概念

面向对象的三个特征

封装,继承,多态,这个应该是人人皆知,有时候也会加上抽象。

多态的好处

允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用)。主要有以下优点:

  1. 可替换性:多态对已存在代码具有可替换性
  2. 可扩充性:增加新的子类不影响已经存在的类结构
  3. 接口性:多态是超类通过方法签名,向子类提供一个公共接口,由子类来完善或者重写它来实现的。
  4. 灵活性
  5. 简化性

代码中如何实现多态

实现多态主要有以下三种方式:
1. 接口实现 
2. 继承父类重写方法 
3. 同一类中进行方法重载

虚拟机是如何实现多态的

动态绑定技术(dynamic binding),执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法。

接口的意义

接口的意义用三个词就可以概括:规范,扩展,回调。

抽象类的意义

抽象类的意义可以用三句话来概括:

  1. 为其他子类提供一个公共的类型
  2. 封装子类中重复定义的内容
  3. 定义抽象方法,子类虽然有不同的实现,但是定义时一致的

接口和抽象类的区别

比较

抽象类

接口

默认方法

抽象类可以有默认的方法实现

java 8之前,接口中不存在方法的实现.

实现方式

子类使用extends关键字来继承抽象类.如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现.

子类使用implements来实现接口,需要提供接口中所有声明的实现.

构造器

抽象类中可以有构造器,

接口中不能

和正常类区别

抽象类不能被实例化

接口则是完全不同的类型

访问修饰符

抽象方法可以有public,protected和default等修饰

接口默认是public,不能使用其他修饰符

多继承

一个子类只能存在一个父类

一个子类可以存在多个接口

添加新方法

想抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码

如果往接口中添加新方法,则子类中需要实现该方法.

 

父类的静态方法能否被子类重写

不能。重写只适用于实例方法,不能用于静态方法,而子类当中含有和父类相同签名的静态方法,我们一般称之为隐藏。

什么是不可变对象

不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。

静态变量和实例变量的区别?

静态变量存储在方法区,属于类所有。实例变量存储在堆当中,其引用存在当前线程栈。

能否创建一个包含可变对象的不可变对象?

当然可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝。最常见的例子就是对象中包含一个日期对象的引用。

java 创建对象的几种方式

  1. 采用new
  2. 通过反射
  3. 采用clone
  4. 通过序列化机制

前2者都需要显式地调用构造方法。造成耦合性最高的恰好是第一种,因此你发现无论什么框架,只要涉及到解耦必先减少new的使用。

switch中能否使用string做参数

在idk 1.7之前,switch只能支持byte, short, char, int或者其对应的封装类以及Enum类型。从idk 1.7之后switch开始支持String。

switch能否作用在byte, long上?

可以用在byte上,但是不能用在long上。

String s1=”ab”, String s2=”a”+”b”, String s3=”a”, String s4=”b”, s5=s3+s4请问s5==s2返回什么?

返回false。在编译过程中,编译器会将s2直接优化为”ab”,会将其放置在常量池当中,s5则是被创建在堆区,相当于s5=new String(“ab”);

你对String对象的intern()熟悉么?

intern()方法会首先从常量池中查找是否存在该常量值,如果常量池中不存在则现在常量池中创建,如果已经存在则直接返回。
比如 
String s1=”aa”; 
String s2=s1.intern(); 
System.out.print(s1==s2);//返回true

Object中有哪些公共方法?

  1. equals()
  2. clone()
  3. getClass()
  4. notify(),notifyAll(),wait()
  5. toString

 

java当中的四种引用

强引用,软引用,弱引用,虚引用。不同的引用类型主要体现在GC上:

  1. 强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。
  2. 软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。
  3. 弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。
  4. 虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。

 

更多了解参见深入对象引用:

http://blog.csdn.net/dd864140130/article/details/49885811

WeakReference与SoftReference的区别?

这点在四种引用类型中已经做了解释,这里简单说明一下即可: 
虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。

为什么要有不同的引用类型

不像C语言,我们可以控制内存的申请和释放,在Java中有时候我们需要适当的控制对象被回收的时机,因此就诞生了不同的引用类型,可以说不同的引用类型实则是对GC回收时机不可控的妥协。有以下几个使用场景可以充分的说明:

  1. 利用软引用和弱引用解决OOM问题:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题.
  2. 通过软引用实现Java对象的高速缓存:比如我们创建了一Person的类,如果每次需要查询一个人的信息,哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这将引起大量Person对象的消耗,并且由于这些对象的生命周期相对较短,会引起多次GC影响性能。此时,通过软引用和 HashMap 的结合可以构建高速缓存,提供性能。

java中==和eqauls()的区别,equals()和`hashcode的区别

==是运算符,用于比较两个变量是否相等,而equals是Object类的方法,用于比较两个对象是否相等。默认Object类的equals方法是比较两个对象的地址,此时和==的结果一样。换句话说:基本类型比较用==,比较的是他们的值。默认下,对象用==比较时,比较的是内存地址,如果需要比较对象内容,需要重写equal方法。

equals()hashcode()的联系

hashCode()是Object类的一个方法,返回一个哈希值。如果两个对象根据equal()方法比较相等,那么调用这两个对象中任意一个对象的hashCode()方法必须产生相同的哈希值。
如果两个对象根据eqaul()方法比较不相等,那么产生的哈希值不一定相等(碰撞的情况下还是会相等的。)

a.hashCode()有什么用?与a.equals(b)有什么关系

hashCode() 方法是相应对象整型的 hash 值。它常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap等等。它与 equals() 方法关系特别紧密。根据 Java 规范,使用 equal() 方法来判断两个相等的对象,必须具有相同的 hashcode。

将对象放入到集合中时,首先判断要放入对象的hashcode是否已经在集合中存在,不存在则直接放入集合。如果hashcode相等,然后通过equal()方法判断要放入对象与集合中的任意对象是否相等:如果equal()判断不相等,直接将该元素放入集合中,否则不放入。

有没有可能两个不相等的对象有相同的hashcode

有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。如果两个对象相等,必须有相同的hashcode 值,反之不成立。

可以在hashcode中使用随机数字吗?

不行,因为同一对象的 hashcode 值必须是相同的

a==b与a.equals(b)有什么区别

如果a 和b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。

3*0.1==0.3返回值是什么

false,因为有些浮点数不能完全精确的表示出来。

a=a+b与a+=b有什么区别吗?

+=操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换。如:
byte a = 127; 
byte b = 127; 
b = a + b; // error : cannot convert from int to byte 
b += a; // ok 
(译者注:这个地方应该表述的有误,其实无论 a+b 的值为多少,编译器都会报错,因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)

short s1= 1; s1 = s1 + 1; 该段代码是否有错,有的话怎么改?

有错误,short类型在进行运算时会自动提升为int类型,也就是说s1+1的运算结果是int类型。

short s1= 1; s1 += 1; 该段代码是否有错,有的话怎么改?

+=操作符会自动对右边的表达式结果强转匹配左边的数据类型,所以没错。

& 和 &&的区别

首先记住&是位操作,而&&是逻辑运算符。另外需要记住逻辑运算符具有短路特性,而&不具备短路特性。

public class Test{
   
static String name;

   
public static void main(String[] args){
       
if(name!=null&userName.equals("")){
           System.
out.println("ok");
       }
else{
           System.
out.println("erro");
       }
   }
}

 

以上代码将会抛出空指针异常。

一个java文件内部可以有类?(非内部类)

只能有一个public公共类,但是可以有多个default修饰的类。

如何正确的退出多层嵌套循环?

  1. 使用标号和break;
  2. 通过在外层循环中添加标识符

 

 

内部类的作用

内部类可以有多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立.在单个外围类当中,可以让多个内部类以不同的方式实现同一接口,或者继承同一个类.创建内部类对象的时刻不依赖于外部类对象的创建。内部类并没有令人疑惑的”is-a”管系,它就像是一个独立的实体。

内部类提供了更好的封装,除了该外围类,其他类都不能访问。

final, finalize和finally的不同之处

final 是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。finalize 方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会,但是什么时候调用 finalize 没有保证。finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行,无论在 try 块中是否有发生异常。

clone()是哪个类的方法?

java.lang.Cloneable 是一个标示性接口,不包含任何方法,clone 方法在 object 类中定义。并且需要知道 clone() 方法是一个本地方法,这意味着它是由 c 或 c++ 或 其他本地语言实现的。

深拷贝和浅拷贝的区别是什么?

浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。

深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。

static都有哪些用法?

几乎所有的人都知道static关键字这两个基本的用法:静态变量和静态方法。也就是被static所修饰的变量/方法都属于类的静态资源,类实例所共享。

除了静态变量和静态方法之外,static也用于静态块,多用于初始化操作:

public calss PreCache{
   
static{
       
//执行相关操作
   }
}

 

此外static也多用于修饰内部类,此时称之为静态内部类。

最后一种用法就是静态导包,即import static.import static是在JDK 1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名。资源名,可以直接使用资源名,比如:

import static java.lang.Math.*;

public class Test{

   
public static void main(String[] args){
       
//System.out.println(Math.sin(20));传统做法
       System.out.println(sin(20));
   }
}

 

final有哪些用法

final也是很多面试喜欢问的地方,能回答下以下三点就不错了:
1.被final修饰的类不可以被继承 
2.被final修饰的方法不可以被重写 
3.被final修饰的变量不可以被改变。如果修饰引用,那么表示引用不可变,引用指向的内容可变。
4.被final修饰的方法,JVM会尝试将其内联,以提高运行效率 
5.被final修饰的常量,在编译阶段会存入常量池中。

回答出编译器对final域要遵守的两个重排序规则更好:
1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
2.初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

数据类型相关

java中int char,long各占多少字节?

类型

字节数

位数

short

2

16

int

4

32

long

8

64

float

4

32

double

8

64

char

2

16


64位的JVM当中,int的长度是多少?

Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。

int和Integer的区别

Integer是int的包装类型,在拆箱和装箱中,二者自动转换。int是基本类型,直接存数值,而integer是对象,用一个引用指向这个对象。

int 和Integer谁占用的内存更多?

Integer 对象会占用更多的内存。Integer是一个对象,需要存储对象的元数据。int 是一个原始类型的数据,所以占用的空间更少。

String, StringBuffer和StringBuilder区别

String是字符串常量,final修饰:StringBuffer字符串变量(线程安全);
StringBuilder 字符串变量(线程不安全)。

String和StringBuffer

String和StringBuffer主要区别是性能:String是不可变对象,每次对String类型进行操作都等同于产生了一个新的String对象,然后指向新的String对象。所以尽量不在对String进行大量的拼接操作,否则会产生很多临时对象,导致GC开始工作,影响系统性能。

StringBuffer是对对象本身操作,而不是产生新的对象,因此在有大量拼接的情况下,我们建议使用StringBuffer。

但是需要注意现在JVM会对String拼接做一定的优化:
String s=“This is only ”+”simple”+”test”会被虚拟机直接优化成String s=“This is only simple test”,此时就不存在拼接过程。

StringBuffer和StringBuilder

StringBuffer是线程安全的可变字符串,其内部实现是可变数组。StringBuilder是jdk 1.5新增的,其功能和StringBuffer类似,但是非线程安全。因此,在没有多线程问题的前提下,使用StringBuilder会取得更好的性能。

什么是编译器常量?使用它有什么风险?

公共静态不可变(public static final )变量也就是我们所说的编译期常量,这里的 public 可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。

java当中使用什么类型表示价格比较好?

如果不是特别关心内存和性能的话,使用BigDecimal,否则使用预定义精度的 double 类型。

如何将byte转为String

可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同。

可以将int强转为byte类型么?会产生什么问题?

我们可以做强制转换,但是Java中int是32位的而byte是8 位的,所以,如果强制转化int类型的高24位将会被丢弃,byte 类型的范围是从-128到128


关于垃圾回收

你知道哪些垃圾回收算法?

垃圾回收从理论上非常容易理解,具体的方法有以下几种: 
1. 标记-清除 
2. 标记-复制 
3. 标记-整理 
4. 分代回收 
更详细的内容参见深入理解垃圾回收算法:

http://blog.csdn.net/dd864140130/article/details/50084471

如何判断一个对象是否应该被回收

这就是所谓的对象存活性判断,常用的方法有两种:1.引用计数法; 2.对象可达性分析。由于引用计数法存在互相引用导致无法进行GC的问题,所以目前JVM虚拟机多使用对象可达性分析算法。

简单的解释一下垃圾回收

Java 垃圾回收机制最基本的做法是分代回收。内存中的区域被划分成不同的世代,对象根据其存活的时间被保存在对应世代的区域中。一般的实现是划分成3个世代:年轻、年老和永久。内存的分配是发生在年轻世代中的。当一个对象存活时间足够长的时候,它就会被复制到年老世代中。对于不同的世代可以使用不同的垃圾回收算法。进行世代划分的出发点是对应用中对象存活时间进行研究之后得出的统计规律。一般来说,一个应用中的大部分对象的存活时间都很短。比如局部变量的存活时间就只在方法的执行过程中。基于这一点,对于年轻世代的垃圾回收算法就可以很有针对性。

调用System.gc()会发生什么?

通知GC开始工作,但是GC真正开始的时间不确定。


进程,线程相关

说说进程,线程,协程之间的区别

简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。

你了解守护线程吗?它和非守护线程有什么区别

程序运行完毕,jvm会等待非守护线程完成后关闭,但是jvm不会等待守护线程。守护线程最典型的例子就是GC线程。

什么是多线程上下文切换

多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。

创建两种线程的方式?他们有什么区别?

通过实现java.lang.Runnable或者通过扩展java.lang.Thread类。相比扩展Thread,实现Runnable接口可能更优.原因有二:

  1. Java不支持多继承。因此扩展Thread类就代表这个子类不能扩展其他类。而实现Runnable接口的类还可能扩展另一个类。
  2. 类可能只要求可执行即可,因此继承整个Thread类的开销过大。

Thread类中的start()和run()方法有什么区别?

start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

怎么检测一个线程是否持有对象监视器

Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着”某条线程”指的是当前线程。

Runnable和Callable的区别

Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。 
这其实是很有用的一个特性,因为多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候我们期望的数据是否已经赋值完毕?无法得知,我们能做的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却可以方便获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务。

什么导致线程阻塞

阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),学过操作系统的同学对它一定已经很熟悉了。Java 提供了大量方法来支持阻塞,下面让我们逐一分析。

方法

说明

sleep()

sleep() 允许 指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。 典型地,sleep() 被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止

suspend() 和 resume()

两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。

yield()

yield() 使当前线程放弃当前已经分得的CPU 时间,但不使当前线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程

wait() 和 notify()

两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用。


wait(),notify()和suspend(),resume()之间的区别

初看起来它们与 suspend() 和 resume() 方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的所有方法,阻塞时都不会释放占用的锁(如果占用了的话),而这一对方法则相反。上述的核心区别导致了一系列的细节上的区别。

首先,前面叙述的所有方法都隶属于 Thread 类,但是这一对却直接隶属于 Object 类,也就是说,所有对象都拥有这一对方法。初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用 任意对象的notify()方法则导致从调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

其次,前面叙述的所有方法都可在任何位置调用,但是这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized 方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的 synchronized 方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException 异常。

wait() 和 notify() 方法的上述特性决定了它们经常和synchronized关键字一起使用,将它们和操作系统进程间通信机制作一个比较就会发现它们的相似性:synchronized方法或块提供了类似于操作系统原语的功能,它们的执行不会受到多线程机制的干扰,而这一对方法则相当于 block 和wakeup 原语(这一对方法均声明为 synchronized)。它们的结合使得我们可以实现操作系统上一系列精妙的进程间通信的算法(如信号量算法),并用于解决各种复杂的线程间通信问题。

关于 wait() 和 notify() 方法最后再说明两点: 
第一:调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。

第二:除了 notify(),还有一个方法 notifyAll() 也可起到类似作用,唯一的区别在于,调用 notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。

谈到阻塞,就不能不谈一谈死锁,略一分析就能发现,suspend() 方法和不指定超时期限的 wait() 方法的调用都可能产生死锁。遗憾的是,Java 并不在语言级别上支持死锁的避免,我们在编程中必须小心地避免死锁。

以上我们对 Java 中实现线程阻塞的各种方法作了一番分析,我们重点分析了 wait() 和 notify() 方法,因为它们的功能最强大,使用也最灵活,但是这也导致了它们的效率较低,较容易出错。实际使用中我们应该灵活使用各种方法,以便更好地达到我们的目的。

产生死锁的条件

1.互斥条件:一个资源每次只能被一个进程使用。 
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 
3.不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。 
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用

这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁

wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别

wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。

wait()与sleep()的区别

关于这两者已经在上面进行详细的说明,这里就做个概括好了:

  • sleep()来自Thread类,和wait()来自Object类。调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁
  • sleep()睡眠后不出让系统资源,wait让其他线程可以占用CPU
  • sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒.而wait()需要配合notify()或者notifyAll()使用

 

 

为什么wait, nofity和nofityAll这些方法不放在Thread类当中

一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

怎么唤醒一个阻塞的线程

如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。

什么是多线程的上下文切换

多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。

synchronized和ReentrantLock的区别

synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上: 
(1)ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁 
(2)ReentrantLock可以获取各种锁的信息 
(3)ReentrantLock可以灵活地实现多路通知 
另外,二者的锁机制其实也是不一样的:ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word。

FutureTask是什么

这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

一个线程如果出现了运行时异常怎么办?

如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放。

Java当中有哪几种锁

  1. 自旋锁: 自旋锁在JDK1.6之后就默认开启了。基于之前的观察,共享数据的锁定状态只会持续很短的时间,为了这一小段时间而去挂起和恢复线程有点浪费,所以这里就做了一个处理,让后面请求锁的那个线程在稍等一会,但是不放弃处理器的执行时间,看看持有锁的线程能否快速释放。为了让线程等待,所以需要让线程执行一个忙循环也就是自旋操作。在jdk6之后,引入了自适应的自旋锁,也就是等待的时间不再固定了,而是由上一次在同一个锁上的自旋时间及锁的拥有者状态来决定。
  2. 偏向锁: 在JDK1.之后引入的一项锁优化,目的是消除数据在无竞争情况下的同步原语。进一步提升程序的运行性能。 偏向锁就是偏心的偏,意思是这个锁会偏向第一个获得他的线程,如果接下来的执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步。偏向锁可以提高带有同步但无竞争的程序性能,也就是说他并不一定总是对程序运行有利,如果程序中大多数的锁都是被多个不同的线程访问,那偏向模式就是多余的,在具体问题具体分析的前提下,可以考虑是否使用偏向锁。
  3. 轻量级锁: 为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在Java SE1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。

如何在两个线程间共享数据

通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的。

如何正确的使用wait()?使用if还是while?

wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:

synchronized (obj) {
   
while (condition does not hold)
     
obj.wait(); // (Releases lock, and reacquires on wakeup)
     ... // Perform action appropriate to condition
}

什么是线程局部变量ThreadLocal

线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。

ThreadLoal的作用是什么?

简单说ThreadLocal就是一种以空间换时间的做法在每个Thread里面维护了一个ThreadLocal.ThreadLocalMap把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。

生产者消费者模型的作用是什么?

(1)通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用。
(2)解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约。

写一个生产者-消费者队列

可以通过阻塞队列实现,也可以通过wait-notify来实现。

使用阻塞队列来实现

//消费者
public class Producer implements Runnable{
   
private final BlockingQueue<Integer> queue;

   
public Producer(BlockingQueue q){
       
this.queue=q;
   }

   @Override
   
public void run() {
       
try {
           
while (true){
               Thread.sleep(
1000);//模拟耗时
               queue.put(produce());
           }
       }
catch (InterruptedException e){

       }
   }

   
private int produce() {
       
int n=new Random().nextInt(10000);
       System.out.println(
"Thread:" + Thread.currentThread().getId() + " produce:" + n);
       
return n;
   }
}

//消费者
public class Consumer implements Runnable {
   
private final BlockingQueue<Integer> queue;

   
public Consumer(BlockingQueue q){
       
this.queue=q;
   }

   @Override
   
public void run() {
       
while (true){
           
try {
               Thread.sleep(
2000);//模拟耗时
               consume(queue.take());
           }
catch (InterruptedException e){

           }

       }
   }

   
private void consume(Integer n) {
       System.out.println(
"Thread:" + Thread.currentThread().getId() + " consume:" + n);

   }
}

//测试
public class Main {

   
public static void main(String[] args) {
       BlockingQueue<Integer> 
queue=new ArrayBlockingQueue<Integer>(100);
       Producer p=
new Producer(queue);
       Consumer c1=
new Consumer(queue);
       Consumer c2=
new Consumer(queue);

       
new Thread(p).start();
       
new Thread(c1).start();
       
new Thread(c2).start();
   }
}

使用wait-notify来实现

该种方式应该最经典,这里就不做说明了。

如果你提交任务时,线程池队列已满,这时会发生什么

如果你使用的LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务;如果你使用的是有界队列比方说ArrayBlockingQueue的话,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy。

为什么要使用线程池

避免频繁地创建和销毁线程,达到线程对象的重用。另外,使用线程池还可以根据项目灵活地控制并发的数目。

java中用到的线程调度算法是什么

抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

Thread.sleep(0)的作用是什么

由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。

什么是CAS

CAS,全称为Compare and Swap,即比较-替换。假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功。

什么是乐观锁和悲观锁

乐观锁:乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

悲观锁:悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

ConcurrentHashMap的并发度是什么?

ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势,任何情况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?

ConcurrentHashMap的工作原理

ConcurrentHashMap在jdk 1.6和jdk 1.8实现原理是不同的。

jdk 1.6:

ConcurrentHashMap是线程安全的,但是与Hashtablea相比,实现线程安全的方式不同。Hashtable是通过对hash表结构进行锁定,是阻塞式的,当一个线程占有这个锁时,其他线程必须阻塞等待其释放锁。ConcurrentHashMap是采用分离锁的方式,它并没有对整个hash表进行锁定,而是局部锁定,也就是说当一个线程占有这个局部锁时,不影响其他线程对hash表其他地方的访问。 
具体实现:ConcurrentHashMap内部有一个Segment.

jdk 1.8

在jdk 8中,ConcurrentHashMap不再使用Segment分离锁,而是采用一种乐观锁CAS算法来实现同步问题,但其底层还是“数组+链表->红黑树”的实现。

CyclicBarrier和CountDownLatch区别

这两个类非常类似,都在java.util.concurrent下,都可以用来表示代码运行到某个点上,二者的区别在于:

  • CyclicBarrier的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch则不是,某线程运行到某个点上之后,只是给某个数值-1而已,该线程继续运行。
  • CyclicBarrier只能唤起一个任务,CountDownLatch可以唤起多个任务
  • CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了。

java中的++操作符线程安全么?

不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。

你有哪些多线程开发良好的实践?

  1. 给线程命名
  2. 最小化同步范围
  3. 优先使用volatile
  4. 尽可能使用更高层次的并发工具而非wait和notify()来实现线程通信,如BlockingQueue,Semeaphore
  5. 优先使用并发容器而非同步容器.
  6. 考虑使用线程池

关于volatile关键字

可以创建Volatile数组吗?

Java 中可以创建 volatile类型数组,不过只是一个指向数组的引用,而不是整个数组。如果改变引用指向的数组,将会受到volatile 的保护,但是如果多个线程同时改变数组的元素,volatile标示符就不能起到之前的保护作用了。

volatile能使得一个非原子操作变成原子操作吗?

一个典型的例子是在类中有一个 long 类型的成员变量。如果你知道该成员变量会被多个线程访问,如计数器、价格等,你最好是将其设置为 volatile。为什么?因为 Java 中读取 long 类型变量不是原子的,需要分成两步,如果一个线程正在修改该 long 变量的值,另一个线程可能只能看到该值的一半(前 32 位)。但是对一个 volatile 型的 long 或 double 变量的读写是原子。

一种实践是用 volatile 修饰 long 和 double 变量,使其能按原子类型来读写。double 和 long 都是64位宽,因此对这两种类型的读是分为两部分的,第一次读取第一个 32 位,然后再读剩下的 32 位,这个过程不是原子的,但 Java 中 volatile 型的 long 或 double 变量的读写是原子的。volatile 修复符的另一个作用是提供内存屏障(memory barrier),例如在分布式框架中的应用。简单的说,就是当你写一个 volatile 变量之前,Java 内存模型会插入一个写屏障(write barrier),读一个 volatile 变量之前,会插入一个读屏障(read barrier)。意思就是说,在你写一个 volatile 域时,能保证任何线程都能看到你写的值,同时,在写之前,也能保证任何数值的更新对所有线程是可见的,因为内存屏障会将其他所有写的值更新到缓存。

volatile类型变量提供什么保证?

volatile 主要有两方面的作用:1.避免指令重排2.可见性保证.例如,JVM 或者 JIT为了获得更好的性能会对语句重排序,但是 volatile 类型变量即使在没有同步块的情况下赋值也不会与其他语句重排序。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。某些情况下,volatile 还能提供原子性,如读 64 位数据类型,像 long 和 double 都不是原子的(低32位和高32位),但 volatile 类型的 double 和 long 就是原子的。


关于集合

Java中的集合及其继承关系

关于集合的体系是每个人都应该烂熟于心的,尤其是对我们经常使用的List,Map的原理更该如此.这里我们看这张图即可: 

更多内容可见集合类总结:

http://write.blog.csdn.net/postedit/40826423

poll()方法和remove()方法区别?

poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常

LinkedHashMap和PriorityQueue的区别

PriorityQueue 是一个优先级队列,保证最高或者最低优先级的的元素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个 PriorityQueue 时,没有任何顺序保证,但是 LinkedHashMap 课保证遍历顺序是元素插入的顺序。

WeakHashMap与HashMap的区别是什么?

WeakHashMap 的工作与正常的 HashMap 类似,但是使用弱引用作为 key,意思就是当 key 对象没有任何引用时,key/value 将会被回收。

ArrayList和LinkedList的区别?

最明显的区别是 ArrrayList底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。

ArrayList和Array有什么区别?

  1. Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
  2. Array是指定大小的,而ArrayList大小是固定的

ArrayList和HashMap默认大小?

在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是16个元素(必须是2的幂)。这就是 Java 7 中 ArrayList 和 HashMap 类的代码片段。

private static final int DEFAULT_CAPACITY = 10;

//from HashMap.java JDK 7
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4// aka 16

Comparator和Comparable的区别?

Comparable 接口用于定义对象的自然顺序,而 comparator 通常用于定义用户定制的顺序。Comparable 总是只有一个,但是可以有多个 comparator 来定义对象的顺序。

如何实现集合排序?

你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有顺序的的集合,如 list,然后通过 Collections.sort() 来排序。

如何打印数组内容

你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法来打印数组。由于数组没有实现 toString() 方法,所以如果将数组传递给 System.out.println() 方法,将无法打印出数组的内容,但是 Arrays.toString() 可以打印每个元素。

LinkedList的是单向链表还是双向?

双向循环列表,具体实现自行查阅源码。

TreeMap是实现原理

采用红黑树实现,具体实现自行查阅源码。

遍历ArrayList时如何正确移除一个元素

该问题的关键在于面试者使用的是 ArrayList 的 remove() 还是 Iterator 的 remove()方法。这有一段示例代码,是使用正确的方式来实现在遍历的过程中移除元素,而不会出现 ConcurrentModificationException 异常的示例代码

什么是ArrayMap?它和HashMap有什么区别?

ArrayMap是Android SDK中提供的,非Android开发者可以略过。
ArrayMap是用两个数组来模拟map,更少的内存占用空间,更高的效率。
具体参考这篇文章:ArrayMap VS HashMap:
http://lvable.com/?p=217%5D

HashMap的实现原理

1. HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 
2. HashMap的数据结构: 在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

当我们往Hashmap中put元素时,首先根据key的hashcode重新计算hash值,根绝hash值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上.

需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)

你了解Fail-Fast机制吗?

Fail-Fast即我们常说的快速失败,

更多内容参看fail-fast机制:http://blog.csdn.net/chenssy/article/details/38151189

Fail-fast和Fail-safe有什么区别

Iterator的fail-fast属性与当前的集合共同起作用,因此它不会受到集合中任何改动的影响。Java.util包中的所有集合类都被设计为fail->fast的,而java.util.concurrent中的集合类都为fail-safe的。当检测到正在遍历的集合的结构被改变时,Fail-fast迭代器抛出ConcurrentModificationException,而fail-safe迭代器从不抛出ConcurrentModificationException。


关于日期

SimpleDateFormat是线程安全的吗?

非常不幸,DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使用,除非是在对外线程安全的环境中使用,如 将 SimpleDateFormat 限制在 ThreadLocal 中。如果你不这么做,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。因此,从日期、时间处理的所有实践来说,我强力推荐 joda-time 库。

如何格式化日期?

Java 中,可以使用 SimpleDateFormat 类或者 joda-time 库来格式日期。DateFormat 类允许你使用多种流行的格式来格式化日期。参见答案中的示例代码,代码中演示了将日期格式化成不同的格式,如 dd-MM-yyyy 或 ddMMyyyy。


关于异常

简单描述java异常体系

相比没有人不了解异常体系,关于异常体系的更多信息可以见

白话异常机制:http://blog.csdn.net/dd864140130/article/details/42504189

什么是异常链

详情直接参见上面的白话异常机制,不做解释了。

throw和throws的区别

throw用于主动抛出java.lang.Throwable 类的一个实例化对象,意思是说你可以通过关键字 throw 抛出一个 Error 或者 一个Exception,如:throw new IllegalArgumentException(“size must be multiple of 2″)
而throws 的作用是作为方法声明和签名的一部分,方法被抛出相应的异常以便调用者能处理。Java 中,任何未处理的受检查异常强制在 throws 子句中声明。


关于序列化

Java 中,Serializable 与 Externalizable 的区别

Serializable 接口是一个序列化 Java 类的接口,以便于它们可以在网络上传输或者可以将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化方式,成本高、脆弱而且不安全。Externalizable 允许你控制整个序列化过程,指定特定的二进制格式,增加安全机制。


关于JVM

JVM特性

平台无关性. 
Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用模式Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。

简单解释一下类加载器

有关类加载器一般会问你四种类加载器的应用场景以及双亲委派模型,

更多的内容参看深入理解JVM加载器:

http://blog.csdn.net/dd864140130/article/details/49817357

简述堆和栈的区别

VM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。

简述JVM内存分配

  1. 基本数据类型比变量和对象的引用都是在栈分配的。
  2. 堆内存用来存放由new创建的对象和数组。
  3. 类变量(static修饰的变量),程序在一加载的时候就在堆中为类变量分配内存,堆中的内存地址存放在栈中。
  4. 实例变量:当你使用java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给变量,是根据零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的”物理位置”,实例变量的生命周期–当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并不是马上就释放堆中内存。
  5. 局部变量: 由声明在某方法,或某代码段里(比如for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离作用域,内存立即释放。

其他

java当中采用的是大端还是小端?

XML解析的几种方式和特点

DOM, SAX, PULL三种解析方式:

  • DOM:消耗内存:先把xml文档都读到内存中,然后再用DOM API来访问树形结构,并获取数据。这个写起来很简单,但是很消耗内存。要是数据过大,手机不够牛逼,可能手机直接死机
  • SAX:解析效率高,占用内存少,基于事件驱动的:更加简单地说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。
  • PULL:与 SAX 类似,也是基于事件驱动,我们可以调用它的next()方法,来获取下一个解析事件(就是开始文档,结束文档,开始标签,结束标签),当处于某个元素时可以调用XmlPullParser的getAttributte()方法来获取属性的值,也可调用它的nextText()获取本节点的值。

JDK 1.7特性

然 JDK 1.7 不像 JDK 5 和 8 一样的大版本,但是,还是有很多新的特性,如 try-with-resource 语句,这样你在使用流或者资源的时候,就不需要手动关闭,Java 会自动关闭。Fork-Join 池某种程度上实现 Java 版的 Map-reduce。允许 Switch 中有 String 变量和文本。菱形操作符(<>)用于类型推断,不再需要在变量声明的右边申明泛型,因此可以写出可读写更强、更简洁的代码。

JDK 1.8特性

java 8 在 Java 历史上是一个开创新的版本,下面 JDK 8 中 5 个主要的特性: 
Lambda 表达式,允许像对象一样传递匿名函数 
Stream API,充分利用现代多核 CPU,可以写出很简洁的代码 
Date 与 Time API,最终,有一个稳定、简单的日期和时间库可供你使用 
扩展方法,现在,接口中可以有静态、默认方法。 
重复注解,现在你可以将相同的注解在同一类型上使用多次。

Maven和ANT有什么区别?

虽然两者都是构建工具,都用于创建 Java 应用,但是 Maven 做的事情更多,在基于“约定优于配置”的概念下,提供标准的Java 项目结构,同时能为应用自动管理依赖(应用中所依赖的 JAR 文件。

JDBC最佳实践

  • 优先使用批量操作来插入和更新数据
  • 使用PreparedStatement来避免SQL漏洞
  • 使用数据连接池
  • 通过列名来获取结果集

IO操作最佳实践

  • 使用有缓冲的IO类,不要单独读取字节或字符
  • 使用NIO和NIO 2或者AIO,而非BIO
  • 在finally中关闭流
  • 使用内存映射文件获取更快的IO

 

来源于:

https://blog.csdn.net/dd864140130/article/details/55833087

 

这么全面的面试题,赶紧收藏转发吧!

一键关注 • Java面经

 

阅读原文

https://mp.weixin.qq.com/mp/qrcode?scene=10000004&size=102&__biz=MzA3MjMwMzg2Nw==&mid=2247483660&idx=2&sn=4e9d72dd2d29588f2cb8d2d91709b267&send_time=

微信扫一扫
关注该公众号

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/Handsome2013/article/details/81506117