java面试题

文章详细阐述了HashMap的底层原理,包括其数组+链表的结构,扩容机制以及为何在多线程环境下不安全。同时,讨论了如何通过同步化操作(如Collections.synchronizedMap或ConcurrentHashMap)实现线程安全。此外,提到了线程池的四种类型以及synchronized和volatile的区别。还涉及了对象创建过程和SpringBoot的优势。

面试题

hashmap底层原理

HashMap底层就是一个数组结构,数组中的每一项又是一个链表。数组+链表结构

HashMap扩容机制

扩容必须满足两个条件
1.存放新值的时候当前已有元素必须大于阈值;
2.存放新值的时候当前存放数据发生hash碰撞(当前key计算的hash值计算出的数组索引位置已经存在值)
HashMap在添加值的时候,它默认能存储16个键值对,直到你使用这个HashMap时,它才会给HashMap分配16个键值对的存储空间,(负载因子为0.75,阈值为12),当16个键值对已经存储满了,我们在添加第17个键值对的时候才会发生扩容现象,因为前16个值,每个值在底层数组中分别占据一个位置,并没有发生hash碰撞。
HashMap也有可能存储更多的键值对,最多可以存储26个键值对,我们来算一下:存储的前11个值全部发生hash碰撞,存到数组的同一个位置中,(这时元素个数小于阈值12,不会扩容),之后存入15个值全部分散到数组剩下的15个位置中,(这时元素个数大于等于阈值,但是每次存入元素并没有发生hash碰撞,不会扩容),11+15=26,当我们存入第27个值得时候满足以上两个条件,HashMap才会发生扩容;

HashMap为什么线程不安全

JDK1.7 中,由于多线程对HashMap进行扩容,调用了HashMap#transfer(),具体原因:某个线程执行过程中,被挂起,其他线程已经完成数据迁移,等CPU资源释放后被挂起的线程重新执行之前的逻辑,数据已经被改变,造成死循环、数据丢失。
JDK1.8 中,由于多线程对HashMap进行put操作,调用了HashMap#putVal(),具体原因:假设两个线程A、B都在进行put操作,并且hash函数计算出的插入下标是相同的,当线程A执行完第六行代码后由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正常的插入,然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。

如何使HashMap在多线程情况下进行线程安全操作

使用 Collections.synchronizedMap(map),包装成同步Map,原理就是在HashMap的所有方法上synchronized
使用 ConcurrentHashMap:ConcurrentHashMap 是线程安全的 HashMap 实现,采用了分段锁的机制,可以提高并发性能。
使用锁分段技术:锁分段技术是指将整个 HashMap 拆分成多个小的 HashMap,每个小的 HashMap 都有自己的锁,不同的线程可以同时访问不同的小 HashMap,从而提高并发性能。这种技术的实现可以参考 ConcurrentHashMap 的源码。
使用 CAS(Compare and Swap)算法:CAS 是一种无锁算法,可以实现线程安全的 HashMap。CAS 算法通过比较内存中的值和期望值是否相等,如果相等则将新值写入内存,否则重新读取内存中的值并重试。但是,CAS 算法的实现比较复杂,容易出错,不建议自己实现。

hashtable底层原理

hashtable底层原理是使用哈希函数将数据映射到数组中
哈希函数式核心,应该满足:均匀性:均匀映射到数组中、独立性:尽可能的将数据映射成不同的值、稳定性:相同的数据产生相同的哈希值
哈希函数通常是将key转换成数值类型,然后和数组长度取模,确认数组下标
冲突解决:
如果发生冲突,将新的数据放在表头,来保证新的数据在最前面

线程池有几种

Java通过Executors工厂类提供四种线程池,分别为:
newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,否则新建线程。(线程最大并发数不可控制)
newFixedThreadPool:创建一个固定大小的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool : 创建一个定时线程池,支持定时及周期性任务执行。
newSingleThreadExecutor :创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

synchronized和volatile有什么不同?

1,作用的位置不同
synchronized是修饰方法,代码块。
volatile是修饰变量。

2,作用不同
synchronized,可以保证变量修改的可见性及原子性,可能会造成线程的阻塞;synchronized在锁释放的时候会将数据写入主内存,保证可见性;
volatile仅能实现变量修改的可见性,但无法保证原子性,不会造成线程的阻塞;volatile修饰变量后,每次读取都是去主内存进行读取,保证可见性

synchronized原理,使用方式(使用在哪里)

哪里:synchronized是修饰方法代码块
Java对象头是synchronized实现的关键,synchronized用的锁是存在Java对象头里的。
JVM 基于进入和退出 Monitor 对象来实现方法同步和代码块同步。
Monitor(监视器锁)本质是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的。Mutex Lock 的切换需要从用户态转换到核心态中,因此状态转换需要耗费很多的处理器时间。所以synchronized是Java语言中的一个重量级操作。
JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有 monitor, 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放 monitor。

volatile如何保证的可见性

volatile实现内存可见性是通过store和load指令完成的;也就是对volatile变量执行写操作时,会在写操作后加入一条store指令写屏障指令,即强迫线程将最新的值刷新到主内存中;而在读操作时,会加入一条load指令读屏障指令,即强迫从主内存中读入变量的值。

JVM创建对象,原理,让你算一下代码中创建了几个String对象(字符串常量池)

检查加载、分配内存、内存空间初始化、设置、对象初始化

  1. 检查加载
    jvm 首先会去加载我们的 class 文件,这个步骤可以从本地加载,也可以从网络中去加载

  2. 分配内存
    我们首先提出两个问题

jvm是如何确可用的内存空间的
如果有两个线程同时 new 对象,如何避免分配的内存空间重叠(并发安全问题)
下面我们一一 解释这两个问题

jvm是如何确可用的内存空间的

指针碰撞:
如果堆内存区域规整,所有用过的内存放在一边,没用过的 放在一边,中间有一个指针来指示,那分配内存就是把那个指针向空闲的区域移动一段距离(对象的大小),如下图所示

空闲列表

如果内存区域不规整,已使用和未使用的区域相互交错,采用空闲列表的方式,虚拟机会维护一个列表,列表上记录了哪些区域使用了,哪些区域未使用.,分配的时候就从列表中找一块足够大的空间给对象,堆中的内存区域是否规整,取决于采用的垃圾收集器是否带有压缩功能。

发安全问题

CAS 机制
分配内存的时候采用cas机制,保证分配的原子性

分配缓冲(TLAB)
每个线程在java堆中,预先分配一块私有的内存地址,线程如果需要分配内存,就在自己的这个私有的这个区域分配
当空间不足的时候,再从eden 区 申请一块继续使用

  1. 内存空间初始化
    内存分配完毕后,将分配到内存空间都初始化0值。比如 int 就初始化成 0,boolean 就设置成 false 。
    注意这里的初始化是jvm的初始化。
    这一步保证了 java的对象实例字段,在java代码中可以不赋值,就可以直接使用。

4)设置
虚拟机对对象的头部进行一些必要的设置

如:

这个对象是哪个类的示例

如何才能找到类的元数据信息

对象的gc分代年龄等信息

这些都存放在对象的头部中

5)对象初始化
上面的步骤完成以后,对于jvm虚拟机来说,一个新的对象已经产生了,但是从java程序的角度来说对象的创建才刚刚开始。

这一步就是给对象赋值,并执行构造方法

springboot比spring优秀在哪里

Spring Boot框架通过简化配置、内嵌Web服务器、自动化依赖管理、监测和运维功能以及强大的生态系统支持等特性,使得Java开发人员能够更快速、更高效地开发和部署应用程序。

常用注解用过什么

.@RestController 用于标注控制层组件
@RestController = @Controller + @ResponseBody组成:

@Controller 将当前修饰的类注入SpringBoot IOC容器,使得从该类所在的项目跑起来的过程中,这个类就被实例化。当然也有语义化的作用,即代表该类是充当Controller的作用
@ResponseBody 它的作用简短截说就是指该类中所有的API接口返回的数据,甭管你对应的方法返回Map或是其他Object,它会以Json字符串的形式返回给客户端,本人尝试了一下,如果返回的是String类型,则仍然是String。
2.@Service一般用于修饰service层的组件

3.@RequestMapping:@RequestMapping(“/path”)表示该控制器处理所有“/path”的URL请求。RequestMapping是一个用来处理请求地址映射的注解,提供路由信息,负责URL到Controller中的具体函数的映射,可用于类或方法上。

用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。该注解有六个属性:

params:指定request中必须包含某些参数值是,才让该方法处理。
headers:指定request中必须包含某些指定的header值,才能让该方法处理请求。
value:指定请求的实际地址,指定的地址可以是URI Template 模式
method:指定请求的method类型, GET、POST、PUT、DELETE等
consumes:指定处理请求的提交内容类型(Content-Type),如application/json,text/html;
produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回
4.@Autowired:自动导入依赖的bean,自动导入依赖的bean。byType方式。把配置好的Bean拿来用,完成属性、方法的组装,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。当加上(required=false)时,就算找不到bean也不报错。

5.@Controller:用于定义控制器类,在spring 项目中由控制器负责将用户发来的URL请求转发到对应的服务接口(service层),一般这个注解在类中,通常方法需要配合注解@RequestMapping。

6.@Bean:用@Bean标注方法等价于XML中配置的bean。

7.@RequestParam:用在方法的参数前面

8.@Entity:@Table(name=”“):表明这是一个实体类。一般用于jpa这两个注解一般一块使用,但是如果表名和实体类名相同的话,@Table可以省略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值