Java常见面试题总结
1. Java语言有哪些特性?
答:1)简单易学;2)面向对象(封装、继承、多态);3)跨平台性;4)支持多线程;5)可靠性(具备异常处理和自动内存处理机制);6)安全性(提供了多重的安全防御机制,限制程序直接访问操作系统资源等);7)高效性;8)支持网络编程等。
2. Java SE、EE/ME的概念
答:
1) Java SE(Java Platform, Standard Edition)Java平台标准版,Java编程语言的基础,它包含了支持Java应用程序开发和运行的核心类库以及虚拟机等核心组件,Java SE可以用于构建桌面应用程序和简单的服务器程序。
2) Java EE(Java Platform, Enterprise Edition)Java平台企业版,建立在Java EE的基础上,包含了支持企业级应用程序开发和部署的标准和规范,Java EE可以用于构建分布式、可移植、健壮、可伸缩和安全的服务端Java应用程序,例如web应用程序。
3)Java ME(Java Platform, Micro Edition)Java平台微型版,主要用于开发嵌入式消费电子设备的应用程序。例如手机,机顶盒等。
3. JVM和JDK和JRE的概念
答:
1)Java虚拟机是运行Java字节码的虚拟机,JVM针对不同的操作系统有不同的实现。
2)JDK是Java开发工具包(Java Development Kit),它是功能齐全的Java SDK,是提供给开发者使用,能够创建和编译Java程序的开发套件。
4. 引用拷贝,浅拷贝,深拷贝
5. String StringBuffer StringBuilder的区别
答:1)可变性:String是不可变的,StringBuffer和StringBuilder是可变的,两者是从AbstrctStringBuilder类继承来的,在AbstrctStringBuilder类中使用了字符数组保存字符串,不过没有使用final和private修饰,最关键的是这个AbstrctStringBuilder还提供了一些修改字符串的方法,比如append()方法。
2)String和StringBuffer是线程安全的,而StringBuilder是非线程安全的。因为String是不可变的,也就是常量,所以线程安全;而StringBuffer是对方法提供了同步锁或者对调用的方法加了同步锁,所以也是线程安全的。
3)性能上:StringBuilder>Stringuffer>String。因为String类型修改的时候,需要重新构建新的对象;StringBuffer相比于StringBuilder有锁使用的开销,所以性能也相低一些。
6. String.intern()方法有什么用?
答:String.intern()方法是一个native方法,是将指定字符串对象的引用保存在字符串常量池中,可以简单分为以下两种情况。
1)如果字符串常量池中保存了对应的字符串对象的引用,就直接返回该引用。
2)如果字符串常量池中没有保存了对应的字符串对象的引用,那就在常量池中创建一个指向该字符串对象的引用并返回。
7. 运行时候数据区域
答:Java虚拟机在运行程序时候会将其管理的内存划分为若干个不同的数据区域。jdk1.7和jdk1.8不同
1)jdk1.7的内存划分如下所示
2)jdk1.8的内存划分如下所示
3)线程私有的:虚拟机栈、本地方法区、程序计数器。
11)程序计数器:是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号保存器。是唯一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的消亡而消亡。
22)虚拟机栈:用于支持java程序的方法调用,方法调用的数据需要通过栈进行传递,每一个方法调用都需要一个栈帧入栈出栈等操作,而一个栈帧包含局部变量表、操作数栈、动态连接、方法返回地址。StackOverFlowError OutOfMemoryError
33)本地方法栈:是为了虚拟机使用到的native方法服务。同虚拟机栈一样,本地方法调用的时候就会触发压栈和出栈的操作。
4)线程共享的:堆区、方法区、直接内存(非运行时数据区的一部分)
11)Java虚拟机锁管理的内存空间最大的一块就是堆,是所有线程共享的,在虚拟机启动时候创建,此内存区域唯一的目标就是存放对象实例,几乎所有的对象实例以及数组都是在这里分配内存的。
22)方法区:当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
33)运行时常量池:Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic Reference)的 常量池表(Constant Pool Table) 。
5)字符串常量池:JVM为了提升性能和减少内存消耗针对字符串专门开辟的一块内存区域,主要目的是为了避免字符串的重复创建。
6)直接内存:直接内存是一种特殊的内存缓冲区,并不在 Java 堆或方法区中分配的,而是通过 JNI 的方式在本地内存上分配的。直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError
错误出现。
7. 对象的创建过程
答:1)类加载检查:虚拟机遇到一条new指令时候,首先检查这个指令的参数是否能够在常量池中定位到这个类的符号引用,并且检查这个符号的类是否已经被加载过、解析过和初始化过,如果没有,那必须先执行相应的类加载过程。
2)分配内存:在类加载检查通过后,接下来虚拟机将为新对象分配内存,对象所需的内存大小在类加载完成后便可确定。为对象分配内存等同于把一块确定大小的内存空间从堆中划分出来。分配方式有“指针碰撞”和“空闲列表”两种,选择哪种是由Java堆是否规整决定。
11)指针碰撞:适用于堆内存规整,即没有内存碎片的情况。用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分解指针,只需要向着没有用过的内存方向移动将该指针对象内存大小位置即可。
22)空闲列表:适用于堆内存不规整的情况,虚拟机会维护一个列表,该列表中会记录那些内存块是可用的,在分配内存的时候,找一块足够大的内存块划分给对象实例,最后更新列表记录。
创建对象必须是线程安全的。
11)CAS+失败重试:CAS是乐观锁的实现方式,所谓乐观锁就是每次假设没有冲突而去完成某项操作,如果真的因为冲突失败,就一直重新尝试,直到成功为止。
22)TLAB:为每一个线程预先在Eden区间上分配一块内存,JVM在给线程对象分配的时候,首先在TLAN分配,当对象对于TLAB剩余的内存大小好欧哲TLAB空间耗尽时候,再采用CAE+失败重试进行分配。
3)初始化零值:内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
4)设置对象头:初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
5)执行init方法:在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init>
方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init>
方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
8. Java对象的访问方式
答:使用句柄和直接指针
1)如果使用句柄的话,那么Java堆中需要划分出一块内存用作句柄池,referrence中存储的就是对象的句柄地址,而句柄中包含了对象实例的地址以及对象类型的地址信息。
2)直接指针,referfrence中存储的就是对象的地址。
9. Exception和Error有什么区别?
答:在Java中,所有的异常都有一个共同的祖先java.lang包中的Throwable类。Throwable类有两个重要的子类
1)Exception:程序本身产生的异常,可以通过catch来精心捕获。Exception又可以分为Checked Exception(受检查一行,必须处理)和Unchecked Exception(不受检查异常,可以不处理)。
11)Checked Exception即受检查异常,是在编译代码过程中,受检查的异常没有被处理,就没办法通过编译。
22)UnChecked Exception不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
NullPointerException
(空指针错误)IllegalArgumentException
(参数错误比如方法入参类型错误)NumberFormatException
(字符串转换为数字格式错误,IllegalArgumentException
的子类)ArrayIndexOutOfBoundsException
(数组越界错误)ClassCastException
(类型转换错误)ArithmeticException
(算术错误)SecurityException
(安全错误比如权限不够)UnsupportedOperationException
(不支持的操作错误比如重复创建同一用户)2)Error:属于程序无法处理的错误,一般会选择终止JVM。
3)Throwable类有哪些方法。
11)getMessage():返回异常发生时候的错误描述
22)toString():返回异常发生时候的详细描述
33)getLocalizedMessage():返回异常对象的本地化信息。
44)printStackTrace():输出异常的堆栈信息。
10. try-catch-finally如何使用
答:try关键字用于捕获异常,其后可跟一个或多个catch块,如果一个catch块都没有,则必须有一个finally。
catch用于处理捕获到的异常。finally关键字不管是否捕获到异常,是否处理了异常,都需要执行的代码块。
11. 什么是注解?如何解析注解
答:
1)Annotation是jdk5引入的一个新特性,可以看作是一种特殊的注释,主要修饰类,方法或者变量,提供某些信息供程序在编译或者运行时候使用。
2)注解的解析方法有两种:编译器直接扫描 运行期通过反射处理。框架中自带的注解都是通过反射来进行处理的。
12. 序列化和反序列化是什么
答:
1)序列化:就是将对象或者数据结构转换成二进制字节流的过程。
2)反序列化:就是将二进制字节流转换为数据结构或者对象实例的过程。
对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class)。
3)序列化和反序列化的应用场景:
4)对于有些字段不想序列化怎么办?使用transient关键字修饰。transient关键字只能修饰变量,不能修饰类和方法。static变量因为不属于实例对象,所以无论有没有transient关键字修饰,均不会被序列化。
13. Java中为什么要用本地方法呢?
答:1)Java中要使用一些功能,这些功能依赖操作系统的特性。2)对于其他语言已经完成的一些特定功能,java不具备的,可以直接调用。3)程序对时间和性能要求比较敏感时候,有必要使用更加底层的语言,比如C++甚至是汇编等。
14. 深入理解ClassLoader工作机制
答:
1)类加载时机与过程:类从被加载到虚拟机内存,再从内存卸载出内存为止,一共需要经历7个步骤:加载、验证、准备、解析、初始化、使用、卸载。其中验证,准备和解析过程称之为linking。
2)ClassLoader加载器的等级:BootStrap ClassLoader->启动类加载器,是java类中最顶层的类加载器,负责加载jdk中的核心类库,这个ClassLoader完全是JVM自己控制的;ExtClassLoader->扩展加载器,java虚拟机会提供一个扩展目录,该类为扩展目录下的java类精心加载,默认的路径/JAVA_HOME/jre/lib/ext;AppClassLoader->系统类加载器,负责加载应用程序classpath目录下的所有jar文件。
15. 如何使用Unsafe类呢?
答:1))不能直接调用其提供的静态函数getUnsafe()函数获取实例,是因为在获取实例的过程中会判断调用者类是否为启动类加载器加载,如果不是会调用失败,而我们的应用程序大部分都不是直接由启动类加载器加载的类,所以不能直接使用。
2)若想使用Unsafe类,推荐的做法如下所示:
11)利用反射获得Unsafe类private static Unsafe reflectGetUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
中已经实例化完成的单例对象theUnsafe。
22)从getUnsafe()函数的限制出发,通过java命令Xbootclasspath/a
把调用 Unsafe 相关方法的类 A 所在 jar 包路径追加到默认的 bootstrap 路径中,使得 A 被引导类加载器加载,从而通过Unsafe.getUnsafe
方法安全的获取 Unsafe 实例。
java -Xbootclasspath/a: ${path} // 其中path为调用Unsafe相关方法的类所在jar包路径
16. 什么是语法糖?
答:1)语法糖也称之为糖衣语法,是指计算机中的特定语法,这种语法对功能没有影响,但是更方便程序员的使用,简而言之,与鸭汤让程序更加简洁,更加可读。2)Java虚拟机是不认识语法糖的,语法糖语句在编译阶段就会被还原成简单的基础语句,这个过程就是语法糖解析。3)Java当中主要的语法糖是泛型、变长参数、条件编译、自动拆装箱、内部类等。4)swith条件支持String对象,是通过String对象的hashCode()和equals()两个函数实现的。5)泛型是通过擦除类型实现的。6)装箱的过程是通过调用包装类的valueOf()函数实现的,拆箱的过程是通过调用包装类对象的xxxValue()实现的。7)当我们使用enum关键字的时候,编译器实际上帮助我们创建了一个继承Enum的final类,所以枚举类是不可能被继承的。
17. Java集合概览
答:Java集合,也叫做容器,主要是由两大接口派生而来,一个是Collection接口,主要用于存放单一元素,另一个是map,主要用于存放键值对。对于Collection接口,下面有三个主要的子接口Set,List,Queue。
18. 说说List/Set、Queue/Map四者的区别
答:1)List(对付顺序的好帮手)存储的元素是有序的,可重复的。
2)Set(注重独一无二的性质):存储的元素是不可重复的。
3)Queue(实现排队队列的叫号机):按照特定的排队规则来确定先后顺序,存储的元素是有顺序的,可重复的。
4)Map(用key搜索的专家):使用键值对存储,key是无序的,不可重复的。
19. List的子类
1)ArrayList:底层是Object[]
2)Vector:底层是Object[]
3)LinkedList:底层是双向链表
20. Set的子类
1)HashSet:无序,唯一,底层使用HashMap实现
2)LiskedHashSet:其底层是通过LinkedHashMap来实现的
3)TreeSet:有序的,唯一的,底层是 通过红黑树来实现的。
21. Queue的子类
1)PriorityQueue:Object[]数组来实现小顶堆。
2)DelayQueue:PriorityQueue
3)ArrayQueue:可扩容的双向数组。
22. Map的子类
1)HashMap:JDK1.8 之前 HashMap
由数组+链表组成的,数组是 HashMap
的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
2)LinkedHashMap:LinkedHashMap
继承自 HashMap
,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap
在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑
3)HashTable:数组+链表组成的,数组是 Hashtable
的主体,链表则是主要为了解决哈希冲突而存在的。
4)TreeMap:红黑树。
23. 为什么要使用多线程?
答:1)从计算机底层来说,线程是轻量级的进程,是程序执行的最小大卫,线程间的切换和调度比进程小很多,多核CPU意味着可以同时执行多个线程,减少线程间切换的开销,提升系统的性能。
2)从当前互联网发展来说,现在的系统动不动就要求百万级甚至千万级的并发量,那么多线程并发编程是互联网发展的基础。
3)单核时代的多线程,可以提供系统资源的利用率。
4)多核时代的多线程,更加提高了CPU的能力。
24. sleep()方法和wait()方法区别
答:1)sleep()方法是让当前线程暂停执行,所以作用在线程上;而wait()方法是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁,每个对象都拥有对象锁,既然要释放当前线程占用的对象锁并让其进入WAITING状态,自然是要创建对应的对象,所以wait()函数是作用在对象上的,即Object()上。
2)sleep()方法让线程暂停执行,没有释放锁,而wait()方法是让线程获取对象锁释放锁并进入等待,释放锁。
3)wait()
方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()
或者 notifyAll()
方法。sleep()
方法执行完成后,线程会自动苏醒,或者也可以使用 wait(long timeout)
超时后线程会自动苏醒。
4)sleep()是Thread的方法,wait是Object类的本地方法。
25. 什么是spring框架?
答:Spring是一款java的开源 框架,旨在提高开发人员的开发效率以及提升代码的可维护性。
Spring的全称是Spring Framework,他有很多模块集合,使用这些模块集合协助我们日常开发。spring的核心思想就是不重复造轮子,开箱即用,提高开发效率。spring是java的一个杀手级的应用框架。
1)spring的核心模块:spring-core是spring框架基本的核心工具类;spring-beans提供对bean的创建、配置和管理等功能的支持; spring-context提供对spring国际化、资源加载、时间传播等功能的支持; spring-expression提供对语言表达式的支持SpEL,只需要core模块,不依赖其他模块,可以单独使用。
2)AOP:spring-aspects该模块为AspectJ的集成提供支持;spring-aop提供了面向切面的编程;spring-instrment提供了为JVM虚拟机添加代码的功能。
3)Data Access/Integration:spring-jdbc提供了第数据库的抽象访问;spring-tx提供对事务的支持;spring-orm提供对JPA等ORM框架的支持;spring-oxm提供一个抽象层OXM;spring-jms消息服务。
4)spring web:spring-web对web的实现提供了一些最基础的支持;spring-webmvc提供对springMVC的实现;spring-websocket提供了对websocket的支持,websocket可以让客户端和服务端进行双向通信;spring-webflux提供对webflux的支持。
5)Messaging:spring-messaging是从4.0开始新增的一个模块,用于spring框架继承写基础的报文传送应用。
6)Spring Test:spring团队提倡测试驱动开发,有了控制反转之后,单元测试和集成测试变得十分的简单。
26. 什么是IoC(Inversion Of Control-控制反转)
答:Ioc(Inversion Of Control控制反转)是一种设计思想,并不是一种技术实现,IoC的基本思想就是将对象的创建权交由框架来管理。其中控制->指的是对象的创建的权利,反转->控制权交由外部环境(Spring框架,IoC容器)。
将对象之前的相互依赖关系交给IoC容器来管理,并由IoC容器完成对象的注入,这样很大程度上简化了应用程序的开发,把应用从复杂的依赖关系中解救出来。IoC容器就像一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用关系对象是如何创建出来的。
27. Java语言的特点
答:1)面向对象
2)平台无关性
3)编译与解释并存
4)支持多线程
28. 什么是Spring MVC?
答:Spring MVC是spring中的一个模块,主要赋予spring快速构建MVC架构的web应用程序的能力,MVC是Model,View,Control的简写,其核心思想就是将业务逻辑处理,数据,显示分离来组织代码。
29. 什么是Spring Boot
答:使用spring开发各种配置往往过于麻烦,比如开启spring的某些特性时候,需要通过XML或java进行显示配置,于是Spring Boot就诞生了。Spring旨在简化J2EE企业级应用程序开发,Spring Boot旨在简化Spring开发(简化配置文件,开箱即用)。
30. 什么是Spring Bean
答:简单来说,Bean代指的就是那些被IoC容器所管理的对象。我们需要告诉IoC容器帮助我们管理那些类,这个是通过配置元数据来实现的,配置元数据就是XML文件、注解或者Java配置类。如下所示是配置文件方式。
<!-- Constructor-arg with 'value' attribute -->
<bean id="..." class="...">
<constructor-arg value="..."/>
</bean>
31. 将一个类声明为Bean的注解有哪些?
答:1)@Component
:通用的注解,可标注任意类为 Spring
组件。如果一个 Bean 不知道属于哪个层,可以使用@Component
注解标注。2)@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。3)@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。4)@Controller
: 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service
层返回数据给前端页面。
32. Bean的作用于有哪些?
答:1)singleton:IoC容器中只有唯一的bean实例。spring中的bean默认都是单例的,是对单例设计模式的应用。2)prototype:每次获取都会创建一个bean实例,也就是说,连续连词getBean(),得到的是不同的bean实例。3)request:每一次http请求都会产生一个bean,该bean只在本次请求中有效。4)session:每一次来自新session的http请求都会产生一个新的bean,该bean仅在当前http session中有效。5)application/global-session:每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。6)websocket:每一次 WebSocket 会话产生一个新的 bean。
<bean id="..." class="..." scope="singleton"></bean>
注解方式
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
return new Person();
}
30. &和&&有什么区别
答:&称之为逻辑与运算符,&&称之为短路与云算法。短路与运算符在运算的时候,左边表达式为false的时候,右边的表达式是不会计算的,而逻辑与左边和右边都会计算,然后在计算整个表达式的结果。
|称之为逻辑或云算法,||称之为短路或运算符,当短路或运算符的左边表达式为true的时候,右边表达式不计算,这个短路或表达式的结果为true。
31. 哪些类型可以作用域switch表达式
答:1)byte/short/char/int可以。2)从Java5开始,枚举类型也可以。3)从Java7开始,String也可以,是使用的hashCode和equals两个函数做比较的。
32. 多态的前置条件有哪些
答:多态的前置条件有三个。1):子类继承父类。2)字符覆写了父类的方法。3)使用父类的引用指向子类的对象。
33. 面向对象的设计原则有哪些。
答:面向对象的设计原则有SOLID5个原则。1)单一职责原则,指一个类只有一个引起它变化的原因,即一个类只有一项职责,这样做的目的是使类更加清晰,更容易理解和维护。2)开闭原则(Open-Closed Priciple),指的是一个类或者方法在设计的时候应该对扩展开放,对修改关闭。3)里氏替换原则,指的是任何父类出现的地方,都可以使用子类替换,说明子类对象中有一部分父类。4)接口隔离原则,指的是客户端不应该依赖它不需要的接口。5)依赖倒置原则,高层模块不应该依赖底层模块,二者都应该依赖其抽象。
34. 成员变量和局部变量的区别
答:1)成员变量是属于类的,局部变量是属于方法体或者代码块的,成员变量可以使用public, private,protected, default,static修饰,而局部变量是不可以的,局部变量只能只用final修饰。3)从变量在内存中的存储位置上说,成员变量如果是static的,属于类的,如果是非static的,是属于实例对象的,存储在堆区;而局部变量是存放在栈的,如果是引用类型,引用的是堆区的地址或者是常量池中的地址。3)从变量在内存中的生存时间来说,成员变量属于类对象,所以跟随对象的创建而存在,对象的销毁而消亡,局部变量随着代码块或者方法的调用完成而释放。4)从赋初值的角度来说,成员变量如果没有被赋初值,则会自动默认赋值,而局部变量是无初值的。
35. 为什么重写equals()方法了也必须重写hashCode()方法
答:因为基于哈希容器存储对象的时候使用了hashCode()和equals()方法存储和检索对象。
36. Java创建对象有哪几种方式
答:4中方式。1)通过new关键字。2)通过放射机制。3)采用clone()机制。4)通过序列话方式。
37. String对象如何转换成Integer类对象。
答:使用Interger.valueOf(String s )或者Integer.parseInt(String s)
38. Object对象中有哪些方法
答:1)对象比较方法,hashCode() equals()。2)对象拷贝方法,clone()。3)对象转字符串方法toString(); 4)多线程调度方法,wait()和notify()。5)反射方法getClass();6)垃圾回收方法finalize()
39. JDK1.8新特性有哪些
答:1)接口默认方法:
2)Lambda表达式和函数式接口:Lambda表达式是为了替换匿名内部类或者替换函数式接口的一种方式,函数式接口是指只有一个抽象方法的接口,Lambda表达式的用法如下如下所示
(参数1, 参数2)->{方法体}
3)Stream API:新增的类型Stream, 对集合做各种的操作,在java.util.Stream包中,分为中间操作和终端操作。中间操作返回一个集合,终端操作返回一个结果。如下所示中间操作和终端操作的分类。
4) 新日期时间API:之前提供的时间类存在定义不同,包不同,非线程安全等问题,所以在java 1.8版本中心提供了日期时间类,解决上述的问题,新提供的类有很多,可以按照自己的需求使用。
5)Optional类:该类型是用于防范空指针异常的NullPointerException。它提供了一些接口用于判断当前存储的返回值是否为空,以及值的对象信息等。
40. 用过哪些集合,他们的优劣?
答:在Java中,常见的集合有ArrayList, LinkedList, HashMap, LinkedHashMap
1)ArrayList是一个有序的,可重复的集合,内部通过动态数组实现,可以在使用的过程中动态的扩展数组的大小。有点是支持随机访问,缺点是删除。按位置插入元素需要移动元素位置,效率低。
2)LinkedList是一个双向链表实现的集合,适合删除或者插入元素,只需要改变前后指针就可以,但是查找元素需要遍历整个链表。
3)HashMap是基于哈希表实现的键值对集合,优点是查找、删除、插入元素都很快,但是缺点是无法保留键值对的插入顺序。
4)LinkedHashMap是在HashMap的基础上做了改变,新增了双向链表,维护了插入元素的顺序。
41. ArrayList是怎样序列化的呢?为什么用transient修饰数组?
答:ArrayList它使用transient修饰存储元素的elementData的数组,处于效率的考虑,数组长度可能是100,单实际存储的元素只有50,剩下的50不用序列号,这样可以提供序列化和反序列化的效率,还可以节省内存空间。
那么ArrayList是使用witeObject和readObject方法自动以序列化策略的。
42. 什么是快速失败(fail-fast)什么是安全失败(fail-safe)
答:安全失败和快速失败是集合的一种错误检测机制。
1)fail-fast:是指用一个迭代器遍历一个集合对象时候,如果A线程在遍历的过程中,B线程修改了集合,那么会抛出Concrrent Modification Exception;原理是A线程在遍历迭代器的时候会使用已modCount变量,集合在遍历期间如果被修改,modCount的值就会发生变化,每当迭代器使用hashNext()、next()遍历下一个元素之前就会比对modCount的值是否发生了变化,如果发生了变化就抛出异常,注意这里抛异常的条件是检测modCount的值是否发生了变化,但是如果B线程先修改了modCount的值,被A线程取到了,然后A读数据,然后B在修改集合,这样是不会检测到modCount发生变化的,也就是不会抛异常,因此存在非线程安全问题。
2)fail-safe:是指A线程在遍历一个集合的时候先拷贝一份,然后在拷贝的集合上遍历,所以不会触发异常的。
43. 能说一下HashMap的数组结构吗
答:数组+链表+红黑树
44. HashPut的流程知道吗
答:1)通过hash函数计算哈希值。
2)数组进行第一次扩容。
3)根据哈希值计算key在数组中的下标,如果对应下标正好没有存放数据,则直接插入。如果对应下标已经有数据了,需要判断是否为相同的value,是则覆盖value,否咋需要判断是否为树节点,是则向树中插入节点,否则向链表中插入节点。
3)所有元素处理完成后,还需要判断是否超过阈值,超过则需要扩容。
45. Java中线程的创建方式有几种
答:有三种。
1)继承Thread类,实现run方法,创建实现类对象,调用start函数启动线程。
2)实现Runnable接口,重写run方法,创建Thread对象,将Runnable实现类对象实例传递给Thread对象,调用Thread的start方法启动线程。优点是避免Java的单继承机制。
3)实现Callable接口,重写calll方法,然后调用FutureTask对象,参数为Callable实现类对象,紧接着创建Thread对象,参数为FutureTask对象,调用Thread的start函数启动线程。优点是可以获取线程函数的返回值。
46. 线程有哪些调度算法
答:
1)wait():当一个线程A调用一个共享变量的wait()方法时候,线程A会被阻塞挂起,知道发生下面情况才会返回。第一,线程B调用了共享对象的notify()或者notifyall()方法;第二,其他线程调用了线程A的interrupt()方法,线程A爬出InterruptedException异常返回。
2)wait(long timeot):这个方法相比 wait()
方法多了一个超时参数,它的不同之处在于,如果线程 A 调用共享对象的 wait(long timeout)
方法后,没有在指定的 timeout 时间内被其它线程唤醒,那么这个方法还是会因为超时而返回。
3)wait(long timeot, int nanos):其内部调用的是wait(long timeout)方法。
4)notify():一个线程A调用一个共享对象的notify()方法后,会唤醒其他在该共享对象变量上调用wait()方法的线程。只能唤醒一个,至于是哪一个是随机的。
5)notify():不同于在共享变量上调用 notify()
方法会唤醒被阻塞到该共享变量上的一个线程,notifyAll 方法会唤醒所有在该共享变量上调用 wait 系列方法而被挂起的线程。
6)yield():让出优先权,Thread类中的静态方法,当一个线程调用yield()方法后,实际是在暗示线程调度器,当前线程请求让出自己的CPU,但是线程调度器可能会假装看不见,忽略这个暗示。
7)void interrupt()
方法:中断线程,例如,当线程 A 运行时,线程 B 可以调用线程 interrupt()
方法来设置线程的中断标志为 true 并立即返回。设置标志仅仅是设置标志, 线程 B 实际并没有被中断,会继续往下执行。
8)boolean isInterrupted()
方法: 检测当前线程是否被中断。
9)boolean interrupted()
方法: 检测当前线程是否被中断,与 isInterrupted 不同的是,该方法如果发现当前线程被中断,则会清除中断标志。
47. Java线程间的通信方式有哪些
答:1)volatile和synchronized关键字:volatile关键字修饰的是类的成员变量,告知程序任何对该程序的访问必须从内存读取,而对它的改变必须刷新到内存中,保证所有线程对变量访问的可见性。synchorinized关键字修饰方法或者代码块,保证在多线程访问下,任何时刻只能有一个线程访问方法或者代码块。synchrnized关键字3中应用方式:第一种,同步方法,为当前对象this加锁,进入同步代码前要获得当前对象的锁;第二,同步静态方法,为当前类对象加锁,锁的是class对象,进入代码前要获得当前类的锁;第三,同步代码块,指定加锁对象,给对象加锁,进入同步代码库前要获得给定对象的锁。每个对象都有一个对象锁,不同的对象,他们的锁不会相互影响,在多线程情况下是非线程安全的的。
2)等待和通知机制:一个线程调用对象的wait()方法时候,它会进入该对象的等待池,并释放已经持有的该对象的锁,进入等待状态,直到其他线程调用同一个对象的notify或者notifyAll()方法。
3)管道输入/输出流:PipedInputStream/PipedOutputStream/PipedReader/PipedWriter,前面两个是面向字节的,后面两个是面向字符的。java的管道输入与输出实际上是一个循环缓冲来实现的。输入流从这个缓冲数组中读数据,输出流往这个缓冲数组中写入数据,当这个循环缓冲满的时候,这个输入流就会被阻塞,当这个循环缓冲数组为空的时候,输入流所在线程就会被阻塞。
4)使用Thread.join():join()是Thread类的一个方法,根据jdk文档的定义:public final void join() throw是InterruptedException: Waits for this thread to die。join()方法就是在等待线程的结束,是主线程等待子线程的终止,也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了才能继续执行t.join()之后的代码块。
5)使用ThreadLocal:是Java提供的一种用于实现线程局部变量的工具类,它允许每个线程都拥有自己的独立副本,从而实现线程隔离,用于解决多线程中贡献对象的线程安全问题。
48. 有什么解决线程安全问题的方法吗
答:1)Java 中的 synchronized 关键字可以用于方法和代码块,确保同一时间只有一个线程可以执行特定的代码段。
2)Java 并发包(java.util.concurrent.locks)中提供了 Lock 接口和一些实现类,如 ReentrantLock。相比于 synchronized,ReentrantLock 提供了公平锁和非公平锁。
3)Java 并发包还提供了一组原子变量类(如 AtomicInteger,AtomicLong 等),它们利用 CAS(比较并交换),实现了无锁的原子操作,适用于简单的计数器场景。3)Java 并发包提供了一些线程安全的集合类,如 ConcurrentHashMap,CopyOnWriteArrayList 等。这些集合类内部实现了必要的同步策略,提供了更高效的并发访问。4)volatile 变量保证了变量的可见性,修改操作是立即同步到主存的,读操作从主存中读取。5)ThreadLocal 为每个线程提供了变量的独立副本,每个线程都只能访问自己的副本,从而实现了线程隔离,保证了线程安全。
49. ThreadLocal怎么实现的呢?
答:1)ThreadLocal本身并不存储任何只,它只是一个映射,来映射线程的局部变量。当一个线程调用ThreadLocal的set或者get方法时候,实际上是在访问线程自己的ThreadLocal.ThreadLocalMap内部类。2)ThreadLocal的实现原理就是每个线程维护一个Map,Map的key为ThreadLocal对象,value为object对象,也是为了实现格式的对象,当需要存储线程隔离的对象的时候,使用ThreadLocal的set方法将对象存入到Map,当需要获取线程隔离的对象的时候,使用ThreadLocal的get方法从Map中取出对象。3)ThreadLocalMap是ThreadLocal的静态内部类,它内部维护了一个Entry数组,key是ThreadLocal对象,value是线程的局部变量本身。Entry 继承了 WeakReference,它限定了 key 是一个弱引用,弱引用的好处是当内存不足时,JVM 会回收 ThreadLocal 对象,并且将其对应的 Entry 的 value 设置为 null,这样在很大程度上可以避免内存泄漏。4)ThreadLocal为什么会导致内存泄露呢:通常情况下,随着线程 Thread 的结束,其内部的 ThreadLocalMap 也会被回收,从而避免了内存泄漏。但如果一个线程一直在运行,并且其 ThreadLocalMap
中的 Entry.value 一直指向某个强引用对象,那么这个对象就不会被回收,从而导致内存泄漏。当 Entry 非常多时,可能就会引发更严重的内存溢出问题。5)ThreadLocal如何解决内存泄露问题呢:remove()
方法会将当前线程的 ThreadLocalMap 中的所有 key 为 null 的 Entry 全部清除,这样就能避免内存泄漏问题。
6)ThreadLocalMap是Map吗?:不是,该类型没有实现Map的接口,但是结构还是和HashMap比较类似的,主要关注的是两个元素:元素数组+散列方法。一个table数组,存储Entry类型的元素。散列方法就是怎么把对应的key映射到table数组的相应下标,ThreadLocalMap使用的哈希取余发,取出key的threadLocalHashCode,然后和table的数组长度长度减1的&运算。
7)ThreadLocalMap是如何解决哈希冲突的: 采用的是开放定址法,单来说,就是散列的结果被占用了,那就在去寻找其他位置。如上图所示,如果我们插入一个 value=27 的数据,通过 hash 计算后应该落入第 4 个槽位中,而槽位 4 已经有了 Entry 数据,而且 Entry 数据的 key 和当前不相等。此时就会线性向后查找,一直找到 Entry 为 null 的槽位才会停止查找,把元素放到空的槽中。在 get 的时候,也会根据 ThreadLocal 对象的 hash 值,定位到 table 中的位置,然后判断该槽位 Entry 对象中的 key 是否和 get 的 key 一致,如果不一致,就判断下一个位置。
8)ThreaLocal的扩容机制了解吗:在 ThreadLocalMap.set()方法的最后,如果执行完启发式清理工作后,未清理到任何数据,且当前散列数组中Entry
的数量已经达到了列表的扩容阈值(len*2/3)
,就开始执行rehash()
逻辑:这里会先去清理过期的 Entry,然后还要根据条件判断size >= threshold - threshold / 4
也就是size >= threshold* 3/4
来决定是否需要扩容。接着看看具体的resize()
方法,扩容后的newTab
的大小为老数组的两倍,然后遍历老的 table 数组,散列方法重新计算位置,开放地址解决冲突,然后放到新的newTab
,遍历完成之后,oldTab
中所有的entry
数据都已经放入到newTab
中了,然后 table 引用指向newTab
9)父子线程怎么共享数据呢?:需要用到类InheritableThreadLocal。使用起来很简单,在主线程的 InheritableThreadLocal 实例设置值,在子线程中就可以拿到了。
50. Java内存模型的抽象
答:1)Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式地进行。Java线程之间的通信是由JMM内存模型控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系,线程之间的共享变量存储在主内存中,每个线程都有一个本地的副本,本地内存是一个抽象的概念,并不真实存在,它可能是寄存器、高速缓存等,Java内存抽象模型示意图如下所示:
51. 说说你原子性、可见性、有序性的理解
答:1)原子性是指一个操作是不可分割的、不可中断的,要么全部执行,要么不被执行。
2)可见性:指一个线程修改了某一共享变量的值时候,其他线程能够立即知道这个修改后的值。
3) 有序性:有序性是指对于一个线程的执行代码,从前往后依次执行,单线程下可以认为程序是有序的,但是多线程下,并发时候可能发生指令重排。
52. 什么是指令重排
答:在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分 3 种类型。
1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
3)指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应 机器指令的执行顺序。
3)内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
53. Java中线程的同步方式有哪些
答:Java中线程的同步方式有6中:互斥量、读写锁、条件变量,自旋锁,屏障,信号量
1)互斥量:互斥量(mutex)本质上是一把锁,访问共享资源前对其加锁,访问后释放锁,在访问期间如果有其他线程访问该共享资源则会被阻塞,直到当前线程解锁。
2)读写锁有三种状态:读模式加锁、写模式加锁、不加锁,一次只有一个线程可以加写模式锁,可以有多个线程加读模式锁,非常适合多读少写的场景。
3)条件变量:它允许线程在满足一定的条件下才继续执行,否则进入等待状态。
4)自旋锁:是一种锁的实现方式,线程不会进入到睡眠状态,而是一直在尝试获取锁,非常适合锁定时间短的场景。
5)信号量:信号量Semaphone本质上是一个计数器。,这个工具类提供的功能就是多个线程批次“传信号”,而这个信号是一个int类型的数据,也可以看成是一种资源。
6)在Java中,synchronized关键字和Lock接口是用来实现线程同步的常用方式。synchronized关键字作用域方法或者代码块时候,其他线程对该对象的所有的synchronized方法或代码块将被阻塞,直到第一个线程完成。
54. synchronized关键字和Lock的区别
答:1)ReentrantLock是一个类,而synchronized是java的一个关键字。
2)ReentrantLock可以实现多路选择通知,而synchronized只能通过wait和notify/notifyAll唤醒一个线程或者全部线程。
3)ReentrantLock必须手动释放锁,通常在finally块中调用unlock方法以确保锁被正确释放,而synchronized会自动释放锁,通常是有JVM自动释放,不需要手动操作。
4)ReentrantLock通常提供更好的性能,特别是在高竞争环境下。