欢迎光临散文网 会员登陆 & 注册

Java面试必备八股文

2023-06-30 15:38 作者:下班被游戏打-  | 我要投稿
  • 一、Java基础篇

  • 二、多线程篇

  • 三、JVM篇

  • 四、MYSQL篇

  • 五、Redis系列

  • 六、Spring系列

  • 七、分布式

一、Java基础篇

1.1)Java有哪几种数据类型

基本数据类型:byte(1字节) short(2字节) int(4字节) long(8字节) float(4字节) double(8字节) char(2字节) boolean(1字节)

引用数据类型:String 类 接口 抽象类 枚举 数组

1.2)JVM、JRE和JDK的关系

JVM指的是Java的虚拟机,Java程序需要运行在虚拟机上,不同的平台都有自己的虚拟机,所以Java语言实现了跨平台。


JRE指的是Java的运行时环境,包括需要的大量的类库和Java的虚拟机。


JDK指的运行时候的需要的一些工具类和运行时环境,比如包括javac.exe ,javadoxc.exe 一系列用于编译字节码工具 打包文档的工具


1.3)Switch支持的数据类型?

jdk1.5之前 byte、short、int、char


jdk5 ~ jdk1.7 加入了enum


jdk1.7之后 加入了String


*注意 不支持long类型


1.4)为什么float=3.4报错

3.4 默认是浮点double类型的,如果赋值给float是向下转型,会出现精度缺失,,需要强制转换


1.5)final 有什么用?

用于修饰类,方法,变量(属性):


如果修饰类,就是不能够继承

如果修饰方法就是不能够重写

修饰变量:修饰的变量是不能够修改的,如果是基本类型那么就是数值不能修改,如果是引用类型就是地址不能够修改。

成员变量:必须实现赋值

局部变量:声明的时候可以不赋值,后续允许一次赋值,赋值之后不能够修改

1.6)String有哪些特性

首先String是 private final char[]


数组 所以长度不可变

final 不能够被继承

private 内容不能够修改

1.6)Stringbuffer和 Stringbuilder有什么不同?

首先他们都是继承AbstractStringBuilder,相对于String来说是可变的字符串,只不过Stringbuffer加了synchronized所以是线程安全的,而Stringbuilder是线程非安全的


1.7)== 和 equals 的区别

== 默认是比较两个对象的引用地址的,而equals默认情况是==比较规则,但是不同的类会重写掉Object类的equals从而改变了equals的比较规则,比如String类的equals方法就是比较他们两个的内容是否相等


1.8)hashCode和equals

两个对象相等他们的hashCode和equals一定相等,但是hashCode相等的两个对象未必相等


1.9)方法重载和方法重写区别

实现方式:方法重载是在一个类当中;而方法重写是在父子类中实现的

方法重载是方法名字相同,参数个数,参数类型不同。和访问修饰符 和 返回类型无关;方法重写是方法名字相同,参数个数,参数类型必须相同,子类的返回值,抛出的异常必须小于等于父类。子类的访问修饰符大于等于父类。

1.10)面向对象和面向过程的区别

面向过程:就是具体化的一步一步的去实现,优点 就是性能比较快因为不需要进行实例化。 缺点就是不容易进行维护,扩展和复用


面向对象:因为面向对象的三大特点就是 封装、继承和多态


封装就是增加了代码的安全性,开发者不用在乎细节,只知道如何使用就可以了

继承就是代码的复用,结果设计模式就可以达到已于扩展和复用的特点

多态使用比较灵活,可以设计出低耦合的系统从而便于维护

1.11)ArrayList 和LinkedList 的区别

数据结构:


ArrayList是底层是数组存储的,用于一大块连续的空间进行存储的,默认的初始容量是10 一般情况下当容量不够的时候会进行1.5 倍的一个扩容,因为是数组形式长度是不变的,所以在扩容的时候需要数据的搬迁,从而导致频繁的扩容会导致性能效率,所以我们在使用时候最好指定好大小。 因为有索引所以在查找、修改数据时候都是O(1)时间复杂度,而数据的增加删除确要移动后面的元素,所以时间复杂度是O(n)。

LinkedList是双向链表的形式存储的,可以充分用碎片化的空间进行存储,查找,修改数据的时间复杂度都是O(n),因为都是要遍历整个链表。插入和删除除非是在指定的指针后面下进行插入和删除不然还是要遍历整个链表。所以说我们通常使用ArrayList进行数据存储。

1.12)说一说HashMap数据结构

HashMap通常是key-value键值对的方式进行存储,在JDK1.7的时候就是通过数组+链表的形式存储,每个数组存储的是一个一个的Entity组成的链表,这些链表上的数字都有一个特点就是Hash值相同,当数据量比较大的时候链表的长度比较大,这个时候查找的时间复杂度就比较大所以。在JDK1.8的时候引入了红黑树概念,当一个链表上的长度大于8的时候并且数组长度大于64这个时候链表就自动转换成红黑树,如果数组长度没有达到64那么此时就对数组进行1倍的扩容。


而且在JDK1.7和1.8还有一个改动就是插入的方式,由于JDK1.7的时候插入方式是头插法,在多线程的方式下会出选循环链表情况,所以1.8改为了尾插法方式。


扩容有三个地方


初始化数组长度为0

元素个数达到了 最大个数*0.75

单个链表的长度大于8并且数组长度大于64

HashMap是线程不安全的,如果要保证线程安全那么可以使用ConcurrentHashMap


1.13)ConcurrentHashMap原如何保证的线程安全?

JDK 1.7 时候是使用分成16个Seagment段,每个Seagment里面存储着一个HashMap和一个锁,所以说1.7能支持最大的并发量也就是16个线程


JDK1.8采用的CAS + Synchronized,每次插入的时候判断是否是第一次插入,是就通过CAS插入,然后判断f.hash是否=-1,如果是的那么其他线程在扩容,当前线程也会参与扩容;删除方法用了synchronized修饰,保障并发下删除元素的安全


1.14)抽象类和接口的区别

继承:抽象类是只能单继承的,接口是可以多实现的


属性:属性修饰符也不一样,抽象累可以是public protect 默认不写 还有final 修饰,但是接口只能是public static final修饰


方法:抽象类既可以抽象的方法,也可以具体的方法,接口只有抽象的方法,而且子类必须实现


二、多线程篇

2.1) *简述线程、进程的基本概念。以及他们之间关系是什么

进程:是程序的一次执行的过程,是系统运行的基本单位,其中包含着程序运行过程中一些内存空间和系统资源。进程在运行过程中都是相互独立,但是线程之间运行可以相互影响。

线程:是进程的更小的单位。一个进程中包含着多个线程。和进程不同的是线程是共享着进程中的内存空间和系统资源的。所以在切换线程过程中开销要比进程小的多。

2.2)线程池

*线程池有七个参数?

corePoolSize: 线程池核心线程数最大值


maximumPoolSize: 线程池最大线程数大小


keepAliveTime: 线程池中非核心线程空闲的存活时间大小


unit: 线程空闲存活时间单位


workQueue: 存放任务的阻塞队列


threadFactory: 用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。


handler: 线程池的饱和策略事件,主要有四种类型。


*线程池的执行过程?

当提交一个线程执行任务时候 ,先看有没有空闲的核心线程如果有就执行,如果没有就看阻塞队列中有没有满的状态,如果没有放满则放入队列中,否则就直接看线程数量是否大于非核心线程数量,如果没有就直接创建一个非核心线程,否则就是按照指定的拒绝策略给处理。。如果一个非核心线程在某个一段时间类是空闲的那么线程池就会把这个线程自动销毁掉。


四种拒绝策略

AbortPolicy(抛出一个异常,默认的)

DiscardPolicy(直接丢弃任务)

DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)

CallerRunsPolicy(交给线程池调用所在的线程进行处理)

五种阻塞队列 (5)

ArrayBlockingQueue(有界队列)是一个用数组实现的有界阻塞队列,按FIFO排序量。

LinkedBlockingQueue(可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列

DelayQueue(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。

PriorityBlockingQueue(优先级队列)是具有优先级的无界阻塞队列;

SynchronousQueue(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。

五种创建线程的方式池的方式:

newFixedThreadPool (固定数目线程的线程池)


newCachedThreadPool(可缓存线程的线程池)


newSingleThreadExecutor(单线程的线程池)


newScheduledThreadPool(定时及周期执行的线程池)


new ThreadPoolExecutor() 自定义的方式创建


使用无界队列的线程池会导致内存飙升吗?

会的,newFixedThreadPool使用了无界的阻塞队列LinkedBlockingQueue,如果线程获取一个任务后,任务的执行时间比较长(比如,上面demo设置了10秒),会导致队列的任务越积越多,导致机器内存使用不停飙升,最终导致OOM。


2.2)线程、进程、协程的区别

进程:进程是操作系统分配系统资源和内存空间的最小单位。进程是独立的一块空间,所以资源和内存空间的切换是特别消耗资源的。


线程:线程也叫做轻量级的进程,是操作系统调用执行的最小单位。线程的资源是依赖于他的父进程,所以他的资源是共享的,线程的切换需要转换到内核态开销相对于小一些。


协程:协程是一种轻量级的线程,协程是直接在用户态就可以控制,具有对内核态来说是不可见的,所以协程的上下文切换更加的节约资源消耗。


2.3)什么是上下文的切换

上下文的切换指的是CPU寄存器和程序计数器存储的数据进行切换,而内存数据的改变只有在内核态才能进行。所以上下文切换对系统来说是巨大的成本。


先是暂停原有的进程,保存好进程中寄存器中的数据在某个地方

内存获取中下一个进程上下文,存储到寄存器中

2.4)Java有几种创建线程的方式

new 一个Thread

继承Runnable类

使用Callable

使用线程池

2.5)sleep和wait区别

sleep 属于 Thread类,wait属于Object类

wait会释放掉锁,sleep不会释放锁

wait必须在同步代码方法和同步代码块中,sleep没有这一个限制

wait()要调用notify()或notifyall()唤醒,sleep()自动唤醒

2.6)Java的内存模型

JMM屏蔽了各种硬件和操作系统的内存访问差异,实现让Java程序在各平台都能够达到一致的内存访问效果,它定义了Java如何将程序中的变量在主存中读取


具体定义:所有变量都在主存中,主存是线程的共享区域,每个线程都有自己独有的工作内存,线程想要操作变量必须从主存中copy一份到自己的工作区域,每个独立内存区域相互隔离。


所以这个时候读写存在延迟,且不是原子操作,所以就出现了一些列的线程安全操作。比如 原子性、可见性、有序性。

2.6)保证并发安全的三大特性?

原子性:一次或多次操作在执行期间不被其他线程影响


可见性:当一个线程在工作内存修改了变量,其他线程能立刻知道**(利用内存原子操作解决或者内存屏障或者lock前缀)**


有序性:JVM对指令的优化会让指令执行顺序改变,有序性是禁止指令重排**(内存屏障)**


2.7)ThreadLocal原理

每一个线程创建变量的副本,不同线程间是不可见的,保证线程安全。每个线程都维护一个ThreadLocalMap,key为threadlocal实例,value是保存的副本。


但是使用ThreadLocal会存在内存泄漏问题,因为key 是弱引用,value是强引用,每次GC时key都会回收,而value不会被回收。所以为了解决内存泄漏问题,可以在每次使用完后删除value或者使用static修饰ThreadLocal,可以随时的获取value。


第二个会出现内存溢出问题,如果使用的是线程池的方式去使用ThreadLocal话,那么就会出现存储的value一直存在,这样就一直堆积。

2.8)什么是CAS锁

CAS锁可以保证原子性,思想是更新内存是会判断其内存的值是否被修改了,如果没有被修改就直接更新,如果被修改了,就得重新去获取值,知道更新为止。这样是有缺点的:


只能支持一个变量的原子操作,不能保证整个代码块的原子操作

CAS频繁的失败会造成CPU的开销打

会出现ABA问题

解决ABA问题,可以通过加入版本控制


Java面试必备八股文的评论 (共 条)

分享到微博请遵守国家法律