java八股文


1.synchronized和Lock有什么区别

1.lock是一个接口,synchronized是java关键字。 2.lock不会主动释放,需要手动释放锁。synchronized自动释放锁 3.lock可以通过tryLock()查看是否加锁成功,而synchronized无法获悉是否加锁成功。

2.说说volatile的用法及原理

可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入,解决多核CPU缓存可见性问题

有序性:采用内存屏障来实现的,就是在编译器生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序

原子性:对单个volatile变量的读写具有原子性,对“volatile变量++”这种复合操作则不具有原子性,但是volatile写与锁的释放有相同的内存语义。 volatile只能保证单个变量读写的原子性,而锁则可以保证对整个临界区的代码执行具有原子性

3.说说Redis的单线程架构

redis的网络IO和键值对读写是单线程的,而其他功能如持久化、异步删除等是依赖其他线程来执行的。事实上他底层并不是单线程的。

1.对于服务端程序来说,线程切换和锁通常很影响性能,redis采用单线程则避免了线程间的阿切换和锁,减小了消耗。

2.redis的大部分操作实在内存上的完成的,因此它的性能很高。

3.redis采用IO多路复用机制,使其能够处理并发请求。

4.如何实现Redis高可用

1.主从复制:写一定是在主服务器上,然后主服务器同步给从服务器。

缺点:当主服务器挂掉的时候,不能自动切换到从服务器上。主从服务器存储数据一样,内存可用性差。

优点:在一定程度上分担主服务器读的压力。

2.哨兵模式:构建多个哨兵节点监视主从服务器,当主服务器挂掉的时候,自动将对应的从服务器切换成主服务器。

优点:实现自动切换,可用性高。

缺点:主从服务器存储数据一致,内存可用性差。还要额外维护一套哨兵系统,较为麻烦。

3.集群模式:采用无中心节点的方式实现。多个主服务器相连,一个主服务器可以有多个从服务器,不同的主服务器存储不同的数据。

优点:可用性更高,内存可用性高。

5.请你说一下final关键字

1.final被用来修饰类和类的成分。

2.final属性:变量引用不可变,但对象内部内容可变;被final修饰的变量必须被初始化。

3.final方法:该方法不能被重写,但子类可以使用该方法。

4.final参数:参数在方法内部不允许被修改

5.final类:该类不能被继承,所有方法不能被重写,但未被声明为final的成员变量可以改变。

6.请你说说重载和重写的区别,构造方法能不能重写

重写和重载的区别:

1.重载发生在同一类中,而重写发生在子类中。

2.重载要求方法名相同,参数列表,返回值,访问修饰符都可以不同。重写要求方法名相同,参数列表相同,返回值类型要小于等于父类的方法,抛出的异常要小于等于父类方法抛出的异常,访问修饰符权限大于等于父类方法的访问修饰符权限。

3.final,private修饰的方法不能重写,构造方法也不能重写。

7.请说说你对Java集合的了解

Java集合类有两个子接口,分别为(单例集合)Collection和(双例集合)Map,其中Collection接口中有List,Set,Queue,其中List是有序可重复的,(List里面又包含3个子接口ArrayList、LinkedList、Vector),Set接口里有HashSet、TreeSet、LinkedHashSet,Set是无序不重复的(LinkedHashSet有序的),Queue队列具有先进先出的特点,Map里面有HashMap、LinkedHashMap、HashTable、TreeMap、ConcurrentHashMap。

8.请你说说IO多路复用

IO多路复用:单个线程同时操作多个IO请求。

select调用:查询有多少个文件描述符需要进行IO操作,特点:轮询次数多,内存开销大,支持文件描述符的个数有限。

poll调用:和select几乎差不多。但是它的底层数据结构为链表,所以支持文件描述符的个数无上限。

epoll:更加高效的调用方式,底层的数据结构为红黑树加链表。避免大内存分配和轮询。

9.请你说说索引怎么实现的B+树,为什么选这个数据结构?

如果单单只是在内存查询数据,B+树的速度很快,但是当数据量很大时,就会使用到索引,索引一般很大,内存中放不下,必须要保存在本地的磁盘文件中。此时影响数据的检索效率就是磁盘的IO次数。磁盘IO次数越少,速度越快,由于AVL和红黑树一个父节点只能有两个子节点,所以当存储大量数据时,树的深度就会很大,从而导致磁盘IO次数增加,效率变慢。B+树一个父节点可以有多个子节点,树的深度就小

10.请你讲一下Java 8的新特性

得分点 Lambda表达式、Java8对接口的改进
1、Lambda表达式:可将功能视为方法参数,或者将代码视为数据。使用 Lambda 表达式,可以更简洁地表示单方法接口(称为功能接口)的实例。 -
2、方法引用:提供了非常有用的语法,可直接引用已有Java类或对象(实例)的方法或构造器。与Lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。 -
3、对接口进行了改进:允许在接口中定义默认方法,默认方法必须使用default修饰。 -
4、Stream API:新添加的Stream API(java.util.stream)支持对元素流进行函数式操作。Stream API 集成在 Collections API 中,可以对集合进行批量操作,例如顺序或并行的 map-reduce 转换。 -
5、Date Time API:加强对日期与时间的处理。

11.请你说说泛型、泛型擦除

1)泛型:Java在jdk5引入了泛型,在没有泛型之前,每次从集合中读取的对象都必须进行类型转换,如果在插入对象时,类型出错,那么在运行时转换处理的阶段就会报错。在提出泛型之后就可以明确的指定集合接受哪些对象类型,编译器就能知晓并且自动为插入的代码进行泛化,在编译阶段告知是否插入类型错误的对象,程序会变得更加安全清晰。

2)泛型擦除:Java泛型是伪泛型,因为Java代码在编译阶段,所有的泛型信息会被擦除,Java的泛型基本上都是在编辑器这个层次上实现的,在生成的字节码文件中是不包含泛型信息的,使用泛型的时候加上的类型,在编译阶段会被擦除掉,这个过程称为泛型擦除。

12.说说你了解的线程通信方式

1.在Java中提供了两种多线程通信方式分别是利用monitor实现通信方式和使用condition实现线程通信方式。
2.使用不同的线程同步方式也就相应的使用不同的线程通信方式。当我们使用synchronize同步时就会使用monitor来实现线程通信,这里的monitor其实就是锁对象,其利用object的wait,notify,notifyAll等方法来实现线程通信。
3.而使用Lock进行同步时就是使用Condition来实现线程通信,Condition对象通过Lock创建出来依赖于Lock对象,使用其await,sign或signAll方法实现线程通信。

13.请你说说JUC

JUC是java.util.concurrent的缩写,这个包包含了支持并发操作的各种工具。 1.原子类:遵循比较和替换原则。可以用于解决单个变量的线程安全问题。 2.锁:与synchronized类似,在包含synchronized所有功能基础上,还支持超时机制,响应中断机制,主要用于解决多个变量的线程安全问题。 3.线程池:可以更方便的管理线程,同时避免开线程杀线程带来的消耗,效率高。 4.并发容器:例如ConcurrentHashMap,支持多线程操作的并发集合,效率更快。

14.请你说说HashMap和Hashtable的区别

1.HashMap非线程安全的,HashTable线程安全的

2.HashMap的底层数据结构为数组+链表/红黑树,HashTable,底层数据结构为数组+链表

3.HashMap的key和Value可以为null,而HashTable的key和value都不可以为null

15.HashMap是线程安全的吗?如果不是该如何解决?

HashMap不是线程安全的。hashmap的底层是利用数组+链表+红黑树的组合,在多线程的情况下,多个线程同时触发hashmap的时候可能会发生冲突。所以在多线程的时候不建议使用hashmap。

解决办法就是不要在多线程中使用HashMap,或者使用更安全的CurrentHashMap,CurrentHashMap通过对桶加锁,以较小的性能来保证线程安全。

16.请你说说Java的四种引用方式(强软弱虚)

1,强引用,以new关键字创建的引用都是强引用,被强引用引用的对象永远都不会被回收。

2,软引用:以SoftRererenc引用对象,被弱引用引用的对象只有在内存空间不足时会被垃圾回收。

3,弱引用,以WeakReference引用对象,被弱引用引用的对象一定会被回收,它只能存活到下一次垃圾回收。

4,虚引用:以PhantomReference引用对象,一个对象被引用引用后不会有任何影响,也无法通过该引用来获取该对象,它的目的只是:当这个对象被垃圾回收时收到一个系统通知。

17.请你讲下G1垃圾回收器

G1收集器是一个多线程的采用标记清除算法的,面向混合收集(同时收集新生带和老年带)的一个垃圾收集器。G1收集器将整个堆内存区域划分为多个大小相等的region,以region为单位进行垃圾收集并获取每个region的收集效率和收集收益,通过一张优先级表对其进行维护。同时每个region中维护了一个remember set用来存储该分区中的对象所引用的对象在其他分区的位置来避免在做可达性分析算法时全堆扫描。G1垃圾收集器垃圾回收主要包括四个流程:1,初始标记,2,并发标记(类似于CMS),3,最终标记在并发标记中维护了一个remember set log用来记录在该阶段发生变化的对象引用关系,在该阶段就是将该信息同步到最终标记信息中。4,并发筛选回收:根据优先级表选择分区进行垃圾回收,用户线程不停顿。

18.请你说说内存溢出

内存溢出:指的是程序运行过程中申请的内存大于系统能够提供的内存,导致无法申请到足够的内幕才能,于是就发生了内存溢出。

引起内存溢出的原因有:1)内存加载的数据量过于庞大,如一次从数据库取出过多的数据。2)代码中存在死循环或者死循环中产生大量的对象实体。3)启动内存值设定过小。

解决内存溢出的方案:1)修改JVM启动参数,直接增加内存。2)检查错误日志,查看“OutOfMemory”错误之前是否存在异常。3)对代码进行debug分析。4)使用内存工具动态查看内存使用情况。

常见的内存溢出出现在:1)堆,对象创建过多。2)栈溢出。3)方法区和运行时常量池,创建大量动态类。

19.请你说说内存泄漏

内存泄漏,是指不再使用的对象仍然被引用,导致垃圾收集器无法回收它们的内存。这种情况可能会越积越多,最终导致致命的OutOfMemoryError。

解决内存泄漏有以下几种方法:

1.启用分析器。Java分析器可以通过应用程序监视和诊断内存泄漏,它可以分析应用程序内部是如何分配内存的。

2.启用详细垃圾收集日志。通过这种方式,我们可以跟踪垃圾收集的详细进度。

3.使用特殊引用对象。我们可以借助ref包内置的Java引用对象来规避问题,也就是使用对象的特殊引用,使得它们可以轻松地被垃圾收集。

4.Eclipse内存泄漏警告。Eclipse会在遇到明显的内存泄漏情况时显示警告和错误。

5.基准测试。我们可以通过执行基准测试来衡量和分析Java代码的性能。 6.代码审查。可以采用经典的代码审查方式来进行简单的代码演练,也有助于消除一些常见的内存泄漏问题。

20.请你说说数据库引擎有哪些,各自有什么区别

1.InnoDB引擎支持MySQL事务,具有提交,回滚和崩溃恢复功能能够更加安全的保护用户数据;支持行级锁,提高多用户并发和性能;支持外键,维护数据完整性。

2.MyISAM引擎,占用空间较小,支持表级锁,能够限制读写工作的负载的性能,查询效率较高,常用于只读场景。

3.Memory引擎,将所有数据存储在RAM(主存)中,在非关键字查询时,查询效率较高。

21.简单介绍Spring

Spring是一个轻量级的免费框架,它有两大核心功能,分别是IOC和AOP。IOC 意为控制反转,是一种面向对象编程的设计思想,如果在不采用这种设计思想情况下,我们需要自己维护对象与对象之间的依赖关系,这就很可能导致对象之间的耦合性较高;在采用了IOC容器后,我们可以让IOC容器去维护对象与对象之间的依赖关系,从而能够降低对象之间的耦合度。4.IOC是通过DI(依赖注入)实现的,实现依赖注入的关键就是IOC容器,其本质上是一个工厂。

AOP是面向切面编程的思想,该思想是对OOP的补充,可以在OOP的基础上进一步提高编程的效率,其可以统一解决一批组件的共性需求(权限检查,记录日志,事务管理等),在AOP思想下,我们可以将解决共性需求的代码独立出来,然后通过配置的方式,声明这些代码在什么地方,什么时候调用,当满足了调用条件后,AOP会将该业务代码织入到我们指定的位置,从而统一的解决问题,有不需要我们去修改代码。

22.介绍一下MyBatis的缓存机制

Mybatis 的一级缓存是指Session缓存。一级缓存的作用域默认是SqlSession。Mybatis默认开启一级缓存。 在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并写到缓存中;第二次以后则直接去一级缓存中取。当执行的SQL查询中间发生了增删改的操作,mybatis会把SqlSession的缓存清空。 二级缓存的作用域是nameSpace。Mybatis需要手动设置启动二级缓存。一个会话,查询一条数据,这个数据会被放在当前会话的一级缓存中;如果会话被关闭了,一级缓存汇总的数据会被保存到二级缓存。新的会话查询信息就会参照二级缓存。

23.请你说说String类,以及new

String类是由final修饰,所以不能被继承,创建字符串由两种方式,一种是使用字符串直接量,另一种是使用new关键字,当使用字符串直接量的方式来创建字符串时,JVM会使用常量池来管理这个字符串,当使用new关键词来创建字符串时,JVM会先使用常量池来管理字符串直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象,新创建的String对象会被保存在堆内存中。

24.请你说说hashCode()和equals()的区别,为什么重写equals()就要重写hashcod()

1、hashCode():获取哈希码,equals():比较两个对象是否相等。
2、二者两个约定:如果两个对象相等,它们必须有相同的哈希码;若两个对象的哈希码相同,他们却不一定相等。也就是说,equals()比较两个对象相等时hashCode()一定相等,hashCode()相等的两个对象equqls()不一定相等。
3、加分回答:由于hashCode()与equals()具有联动关系,equals()重写时,hashCode()进行重写,使得这两个方法始终满足相关的约定。

25.说说线程的创建方式

创建线程有3种方式:

1.继承Thread类,重写run()方法;

2.实现Runnable接口,并实现该接口的run()方法;

3.实现Callable接口,重写call()方法。

前两种方式线程执行完后都没有返回值,最后一种带返回值;一般推荐实现Runnable接口的方式。

26.说说你对ArrayList的理解

arraylist在jdk7.0的时候,创建容器的时候会在底层创建一个长度为10的object数组,在jdk8.0的时候,在创建容器的时候底层并不会立刻创建,只有在第一次调用add方法的时候才会创建一个长度为10的数组,默认情况下,扩容为原来容量的1.5倍,同时将原有数组中的值复制到新的数组中,并且arraylist属于有序的,可重复的集合,提供了iterator方法,增强了迭代能力。

27.请你说说BIO、NIO、O

BIO:同步并阻塞,服务实现模式为一个连接对应一个线程,即客户端发送一个连接,服务端要有一个线程来处理,如果连接多了,线程数量不够,就只能等待,即会发生阻塞。 NIO:同步非阻塞,服务实现模式为一个线程可以处理多个连接,即客户端发送的连接都会注册到多路复用器上,然后进行轮询连接,有IO请求就处理。 AIO:异步非阻塞,引入了异步通信,采用的是proactor模式,特点是:有效的请求才启动线程,先由操作系统完成再通知服务端。

28.说说你对Spring Boot的理解,以及它和Spring的区别?

(1)1、从本质上来说,Spring Boot就是Spring,它帮你完成了一些Spring Bean配置。
2、Spring Boot使用“习惯优于配置”的理念让你的项目快速地运行起来
3、但Spring Boot本身不提供Spring的核心功能,而是作为Spring的脚手架框架,达到快速构建项目的目的
(2)Spring Boot优点, 可以快速构建项目 - 可以对主流开发框架的无配置集成 - 项目可独立运行,无需外部依赖Servlet容器 - 提供运行时的应用监控 - 可以极大地提高开发、部署效率 - 可以与云计算天然集成
(3)核心功能: 1. 自动配置 针对很多Spring应用程序常见的应用功能,Spring Boot能自动提供相关配置。 2. 起步依赖 Spring Boot通过起步依赖为项目的依赖管理提供帮助。起步依赖其实就是特殊的Maven依赖和Gradle依赖,利用了传递依赖解析,把常用库聚合在一起,组成了几个为特定功能而定制的依赖。 3. 端点监控 Spring Boot 可以对正在运行的项目提供监控。

29.说说Spring Boot的自动装配

1.使用Springboot时,我们需要先引入对应starts,Springboot启动时会自动加载相关依赖,配置相应的初始化参数;

2.自动装配的流程:SpringBoot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factory中注册的各种AutoConfiguration类,满足@Conditional注解的条件是,就实例化该AutoConfiguration类中定义的Bean,并注入Spring容器,即可完成Springboot的自动装配。

30.说说@Autowired和@Resource注解的区别

@Autowired是spring提供的注解,而@Resource是JDK提供的注解。@Autowired默认按类型装配,默认情况下必须要求依赖对象存在,如果要允许null值,可以设置它的required属性为false。如果想使用名称装配可以结合@Qualifier注解进行使用。@Resource,默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行名称查找。

31.说说Redis的主从同步机制

redis主从同步指任意个从节点都可以从主节点复制数据,除了多个从节点连接到同一个主节点外,一个从节点还可以接收另一个从节点的连接,形成一个树的结构,使得redis可以完成一个单层树复制。

连接过程:

1,启动一个从节点,从节点发送一个PSYNC命令,主节点收到后开起一个后台线程生成一份RDB文件发送给从节点,从节点收到后线程存入磁盘,再加载进内存(全量同步)。

2,RDB发送完成后,主机点还会发送在生成RDB期间,缓存中新出现的写命令发送给从节点

3,当从节点断开并重新连接时主节点会复制期间丢失的数据给从节点(增量同步)

32.说说Redis的缓存淘汰策略

惰性删除、定期删除、maxmemory

1.每次访问时检测是否过期,过期删除

2.到时间随机挑20个key判断过期时间,删除

3.超过缓存大小,就采用类似lru算法进行淘汰(改进为lfu算法)。

33.说说垃圾收集器

Serial(新生代)、Serial Old(老年代):适用于单核小CPU,单核工作,回收时会暂停其他工作stop the word。

PawNew(新生代)、CMS(老年代):适用于多核CPU,最求短暂停时间,多核工作,使用标记清除算法,最短的暂停时间。

Parallel Scavenge(新生代-标记复制算法)、Parallel Old(老年代-标记整理算法):1.7,1.8默认的组合,适用于多核CPU,追求最大吞吐量

G1 jdk1.9默认,适用于大内存多核CPU服务器,它不按整个新生代或老年代去回收,而是开辟了面向局部收集,实现了较小的收集暂停时间和高吞吐量。

34.请你说说Java的特点和优点,为什么要选择Java?

1.java语言的特点是:一次编译,到处运行,即平台无关性;是纯面向对象的语言。

2.JAVA语言的优点有:内置的类库简化了开发人员的设计工作;具有较好的安全性和健壮性;开发人员不需要对内存的使用进行管理。

3.选择JAVA的原因是:使用范围很广,安卓操作系统的应用软件目前大部分还是使用JAVA语言编写。

35.介绍一下包装类的自动拆装箱与自动装箱

1、自动装箱、自动拆箱是JDK1.5提供的功能。
2、自动装箱:把一个基本类型的数据直接赋值给对应的包装类型;
3、自动拆箱是指把一个包装类型的对象直接赋值给对应的基本类型;
4、通过自动装箱、自动拆箱功能,简化基本类型变量和包装类对象之间的转换过程

36.说说wait()和sleep()的区别

1)所属类型不同:wait()是Object类的实例方法,调用该方法的线程将进入writing状态。sleep()是Thread类的静态方法,调用该方法的线程将进入timed_writing状态。

2)对锁的依赖不同:wait()依赖于synchronized锁,通过监控器进行调用,调用后线程会释放锁。sleep()不依赖于任何锁,所以在调用后它也不会释放锁。

3)返回的条件不同:调用wait()进入等待状态的线程,需要由notify()/notifyAll()唤醒,从而返回。调用sleep()进入超时等待的线程,需要在超时时间达到后自动返回。

37.说说你对线程池的理解

线程池可以有效的管理线程:1)它可以管理线程的数量,可以避免无节制的创建线程,导致超出系统负荷直至奔溃。2)它还可以让线程复用,可以大大地减少创建和销毁线程所带来的开销。线程池需要依赖一些参数来控制任务的执行流程,其中最重要的参数有:corePoolSize(核心线程池数)、workQueue(等待队列)、maxinumPoolSize(最大线程池数)、handler(拒绝策略)、keepAliveTime(空闲线程存活时间)。当我们想线程池提交一个任务之后,线程池按照如下步骤处理这个任务:1)判断线程数是否达到corePoolSize,若没有则新建线程执行该任务,否则进入下一步。2)判断等待队列是否已满,若没有则将任务放入等待队列,否则进入下一步。3)判断线程数是否达到maxinumPoolSize,如果没有则新建线程执行任务,否则进入下一步。4)采用初始化线程池时指定的拒绝策略,拒绝执行该任务。5)新建的线程处理完当前任务后,不会立即关闭,而是继续处理等待队列中的任务。如果线程的空闲时间达到了keepAliveTime,则线程池会销毁一部分线程,将线程数量收缩至corePoolSize。第2步中的列队可以有界也可以无界。若指定无界的队列,则线程池永远无法进入第3步,相当于废弃了maxinumPoolSize参数。这种用法是十分危险的,如果任务在队列中产生大量的堆积,就很容易造成内存泄露。JDK为我们提供了一个名为Executors的线程池的创建工具,该工具创建出来的就是带有无界队列的线程池,所以一般在工作中我们是不建议使用这个类来创建线程池的。

38.简单说下你对JVM的了解

jvm是java语言跨平台的关键,是java的虚拟机,jvm由三部分组成:类加载器,运行时数据区,执行引擎。当程序运行时,会把对象、方法、返回值、局部变量等放入运行时数据区,运行时数据区由方法区、堆、本地方法栈、虚拟机栈、程序计数器组成,其中方法区跟堆是线程共享的,虚拟机栈跟程序计数器是线程私有的

39.说说Java运行时数据区

Java运行时数据区由五部分组成:程序计数器、Java栈、本地方法栈、Java堆和方法区。

程序计数器控制着程序下一步的执行,如循环、分值判断等,Java栈保存着方法运行时的数据结构,方法的调用和结束对应着一个帧栈的入栈和出栈,本地方法栈与Java栈作用类似,其作用对象是本地方法,Java堆中存储new的对象,也是垃圾回收的重要管理区域,方法区中保存着全局变量、静态变量等。

方法区和Java堆是线程共享的,而程序计数器和Java栈是线程私有的。

40.请你讲下CMS垃圾回收器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器,从名字上就可以看出来CMS收集器是基于标记清除算法实现的,它的运作过程相对于前面集中收集器来说要更复杂一些,整个过程分为四个步骤,包括:初始标记、并发标记、重新标记、并发清除。其中初始标记、重新标记这两个步骤仍然需要“stop the world”。

1)初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。

2)并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。 3)重新标记阶段则是为了修正并发标记期间,因影虎程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但是也远比并发标记阶段的时间短。

4)并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段合适可以与用户线程同时并发的。

41.说说JVM的双亲委派模型

双亲委派的理解:类加载器不会在获取到请求后直接加载,而是委托父加载器进行加载,如此类推,每一个类加载器都会反复如此直至最顶层的父加载器反馈没有对应的类则交给子加载器去层层向下加载。而JVM这么做的原因是:

1、不希望重复的类被加载,浪费内存降低性能

2、保护了程序的安全性,防止JAVA底层的核心API被修改从而影响程序后续运行

42.请你说说数据库索引的底层数据结构

索引可选的底层数据结构有:二叉树、红黑树、hash、B+树,但mysql索引的底层用的并不是二叉树和红黑树。因为二叉树和红黑树在某些场景下都会暴露出一些缺陷。首先,二叉树在某些场景下会退化成链表,而链表的查找需要从头部开始遍历,这就失去了加索引的意义。不适用红黑树的原因是:红黑树作为底层数据结构在面对某些表数据动辄数百万数千万的场景时,会导致索引树的高度很高。索引从根节点开始查找,而如果我们需要查找的数据在底层的叶子结点上,那么树的高度多少,就要进行多少次查找,数据存在磁盘上,访问需要进行磁盘IO,这回导致效率过低。而B+树由B树和索引顺序访问方法演化而来,它是为磁盘或其他直接存取辅助设备设计的一种平衡查找树,在B+树中,所有记录点都是按键值的大小顺序存放在同一层的叶子节点,各叶子节点通过很进行链接。B+树索引在数据库中的一个特点就是高扇出行。

43.说说Spring Boot的启动流程

调用run方法,run方法的执行流程:当Springboot项目创建完成后会默认生成一个Application的入口类,该类中的main方法可以启动Springboot项目,在main方法中,通过SpringApplication的静态方法,即run方法进行SpringApplication的实例化操作,然后针对该实例化对象调用另一个run方法去完成整个项目的初始化和启动; SpringApplication的run方法的重要操作:获取监听器的参数配置,打印Banner信息,创建并初始化容器,监听器发送通知

44.介绍一下Spring MVC的执行流程

SpringMVC 的执行流程如下。

  1. 用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器);

  2. 由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain)。

  3. DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);

  4. HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);

  5. Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);

  6. HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;

  7. DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;

  8. ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;

  9. DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);

  10. 视图负责将结果显示到浏览器(客户端)。

45.请你说说Java基本数据类型和引用类型

八大基本数据类型:

整数类型:byte,short,int,long;

浮点类型:float,double;字符类型:char;

布尔类型:boolean; 引用数据类型:string,数组,类,接口

46.请你说说Java的异常处理机制

1、异常处理机制让程序具有容错性和健壮性,程序运行出现状况时,系统会生成一个Exception对象来通知程序
2、处理异常的语句由try、catch、finally三部分组成。try块用于包裹业务代码,catch块用于捕获并处理某个类型的异常,finally块则用于回收资源。
3、如果业务代码发生异常,系统创建一个异常对象,并将其提交给JVM,由JVM寻找可以处理这个异常的catch块,并将异常对象交给这个catch块处理。如果JVM没有找到,运行环境终止,Java程序退出。
4、Java也允许程序主动抛出异常。当业务代码中,判断某项错误的条件成立时,可以使用throw关键字向外抛出异常。

47.说说你对面向对象的理解

1、面向对象三大基本特征:封装、继承、多态。
2、封装:将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,让外部程序通过该类提供的方法来实现对内部信息的操作和访问,提高了代码的可维护性;
3、继承:实现代码复用的重要手段,通过extends实现类的继承,实现继承的类被称为子类,被继承的类称为父类;
4、多态的实现离不开继承,在设计程序时,我们可以将参数的类型定义为父类型。在调用程序时根据实际情况,传入该父类型的某个子类型的实例,这样就实现了多态。

48.请介绍一下访问修饰符

Java中的访问修饰符有四种,分别为private,default,protected,public。

private:类中被private修饰的只能在被当前类的内部访问,

default:类中被default修饰的只能在当前类和当前类所在包的其他类访问。

protected:类中被protected修饰的可以被当前类和当前类所在的包的其他类以及子类访问。

public:类中被public修饰的能被当前项目下的所有类访问。

49.说说Java中常用的锁及原理

synchronize关键字和lock接口,其中lock接口典型的实现类是reentrantlock,synchronize通过JVM来实现线程同步的,涉及到操作系统中用户态进入内核态,

效率较低;而reentrantlock是Java类,由JavaAPI提供,可以看见源码,更具有灵活性,可中断锁,通过AQS定义一个先进先出的队列来实现,效率较高。

50.请你说说List与Set的区别

List和Set都是Collection接口的子接口,它们的主要区别在于元素的有序性和重复性: List代表有序的元素可以重复的集合,集合中每个元素都有对应的顺序索引,它默认按元素的添加顺序设置元素的索引,并且可以通过索引来访问指定位置的集合元素。另外,List允许使用重复元素。 Set代表无序的元素不可重复的集合,它通常不能记住元素的添加顺序

51.请你讲一下Java NIO

JavaNIO指的是同步非阻塞,比较经典的例子是类似一个socket监听接收不管有没有数据到达都立即返回一个值,只要没有获取到数据就会循环调用监听。

52.说说GC的可达性分析

可达性分析算法用于判断对象是否可以被回收,程序通过GC Roots中的对象为起点,以类之间的引用关系简历引用链,最终形成一个类似于数据结构中森林的一个结果,不存在与森林中的对象便是需要被回收的对象。这里的GC Roots主要包括线程栈中引用的变量,本地方法栈中引用的变量,方法区中的静态引用对象,常量池中的常量引用对象和被锁引用的对象。对一个对象真正的宣告回收需要经历两次标记过程,如果一个对象不再引用链上就会对它进行第一次标记,并判断它是否重新了finalize方法,若未重新或finalize方法已经被执行过了则会直接回收对象,否则会创建一个F-queue队列来存储这些对象,并启动一个低优先级的Finalizer线程去执行它们的finalize方法。第二次标记,稍后收集器会对队列中的对象进行可达性分析并标记,若仍然存在标记则表明该对象没有通过finalize方法实现自救则直接回收,否则对象复活。任何对象的finalize方法都只能被调用一次。

53.说说类的实例化过程

类加载->分配内存->初始化0值->状态设置->构造函数

54.请你讲讲B树和B+树

B树和B+树都是多路平衡查找树。B树中所有节点都存放数据。B+树只有叶子结点存放数据,其他节点存放key。B树中的叶子结点是独立的,B+书中的叶子结点通过链与相邻叶子结点连接。B树查找使用的是二分查找,没有查找到叶子结点就可能结束,而B+树必须从根节点进行查找,查询效率更稳定。

55.MySQL主从同步是如何实现的?

复制(replication)是MySQL数据库提供的一种高可用高性能的解决方案,一般用来建立大型的应用。总体来说,replication的工作原理分为以下3个步骤:

  1. 主服务器(master)把数据更改记录到二进制日志(binlog)中。

  2. 从服务器(slave)把主服务器的二进制日志复制到自己的中继日志(relay log)中。

  3. 从服务器重做中继日志中的日志,把更改应用到自己的数据库上,以达到数据的最终一致性

56.请你介绍一下数据库的ACID(原子性,一致性,隔离性,持久性)

原子性、一致性、隔离性、持久性,事务可由一条非常简单的SQL语句组成,也可以由一组复杂的SQL语句组成,在事务的操作中,要么都执行修改,要么都不执行,这就是事务的目的

57.请你说说数据库的索引是什么结构,为什么不用哈希表(B+树)

MySQL中的索引B+树实现的; 哈希表的查询效率的确最高,时间复杂度O(1),但是它要求将所有数据载入内存,而数据库存储的数据量级可能会非常大,全部载入内存基本上是不可能实现的; B+树可以分段加载需要的节点数据,可以在内存资源有限的前提下,极大提高查询效率。

58.请你说说InnoDB的MVCC

多版本并发控制,最大的优点是无锁并发,读不加锁,因此读写不冲突,并发性好,它为每个数据都根据事务维护了多个版本,使其在并发事务中解决了读写冲突,同时使用快照读为MVCC提供了非阻塞读功能,所以它是一个用户解决读写冲突的无锁并发控制机制,它通过数据表的三个隐藏字段分别为 db_trx_id(最长操作事务id),roll_point(undolog指针),db_row_id(唯一性自增张列,可能没有),undolog和readview实现。

59.说说Soring Boot的起步依赖

starter配置,约定大于配置,spring boot将日常企业应用研发中的各种场景都抽取出来,做成一个个的starter(启动器),starter中整合了该场景下各种可能用带的依赖,用户只需要在Maven中引入starter依赖,spring boot就能自动扫描到要加载的信息并启动响应的默认配置

60.说说Spring事务管理

spring支持编程式事务管理和声明式事务管理两种方式: ①编程式事务管理使用TransactionTemplate。 ②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。 声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

61.说说Bean的作用域,以及默认的作用域

singleton 默认作用域,单例bean,每个容器中只有一个bean的实例。

prototype 为每一个bean请求创建一个实例。 session 同个会话共享一个 request 一次请求创建一个 global-session 全局作用域,所有会话共享一个实例

62.说说BeanFactory和FactoryBean的区别

beanfactory:是所有spring bean容器的顶级接口,是最基础的IOC容器,它为spring的容器定义了一套方法,并提供像getBean这样的方法从容器中获取指定的bean实例。它在产生bean的同时,还提供了解决bean之间的依赖注入的能力,也就是所谓的di。

factory bean:工厂bean,只是SpringIOC容器创建Bean的一种形式,它是一个接口,它的主要功能是动态去生成某一类型的bean的一个实例,也就是说 我们可以自定义一个bean并且加载到ioc容器里面,getObject是用来实现动态构建bean的一个过程。

63.说说你对Redis的了解

Redis是一款基于键值对的NoSQL数据库,Redis中拥有string(字符串),hash(哈希)、list(列表)、set(集合)等多种数据结构。

redis将数据写进内存的性能很快,不仅如此,如遇到系统崩溃,内存中的数据不会丢失;redis访问速度快、支持的数据类型丰富,很适合用来储存热点数据、 而且适用业务广,如可以运用expire命令来做限时业务,设置一个键的生存时间,到时间后redis会自动删除它,,如排行榜可以借住redis的SortedSet进行热点数据的排序,还有分页查询,模糊查询,点赞好友等

64.请你说说线程和进程的区别

1.线程是进程的子集,一个进程中可以包含多个线程,每条线程执行不同的任务; 2.不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间; 3.每个线程拥有单独的栈内存用来存储本地数据。

65.请你说说MySQL索引,以及它们的好处和坏处

MySQL索引是一种帮助快速查找数据的数据结构,可以把它理解为书的目录,通过索引能够快速找到数据所在位置。场景的索引数据结构有:Hash表(通过hash算法快速定位数据,但不适合范围查询,因为需要每个key都进行一次hash)、二叉树(查找和修改效率都比较高),但是在InnoDB引擎中使用的索引是B+Tree,相较于二叉树,B+Tree这种多叉树,更加矮宽,更适合存储在磁盘中。使用索引增加了数据查找的效率,但是相对的由于索引也需要存储到磁盘,所以增加了存储的压力,并且新增数据时需要同步维护索引。但是合理的使用索引能够极大提高我们的效率!

66.请你说说多线程

1.线程是程序执行的最小单元,一个进程可以拥有多个线程 2.各个线程之间共享程序的内存空间(代码段、数据段和堆空间)和系统分配的资源(CPU,I/O,打开的文件),但是各个线程拥有自己的栈空间 3.多线程优点:减少程序响应时间;提高CPU利用率;创建和切换开销小;数据共享效率高;简化程序结构

67.说说怎么保证线程安全

线程安全问题是指在多线程背景下,线程没有按照我们的预期执行,导致操作共享变量出现异常。在Java中有许多同步方案提供给我们使用,从轻到重有三种方式:原子类、volatile关键字、锁。 原子类是juc atomic包下的一系列类,通过CAS比较与交换的机制实现线程安全的更新共享变量。通过预期值与内存值的比较来判断是否修改。volatile关键字是轻量级的同步机制,他实现了变量的可见性、防止指令重排序。保证了【单个变量】读写的线程安全。可见性问题是JMM内存模型中定义每个核心存在一个内存副本导致的,核心只操作他们的内存副本,volatile保证了一旦修改变量则立即刷新到共享内存中,且其他核心的内存副本失效,需要重新读取。 原子类和volatile只能保证单个共享变量的线程安全,锁则可以保证临界区内的多个共享变量线程安全。java中常用的锁有两种:synchronized+juc包下的lock锁。synchronized锁是互斥锁,可以作用于实例方法、静态方法、代码块,基于对象头和Monitor对象,在1.6之后引入轻量级锁、偏向锁等优化。lock锁接口可以通过lock、unlock方法锁住一段代码,基于AQS实现,其加锁解锁就是操作AQS的state变量,并且将阻塞队列存在AQS的双向队列中。除了锁以外,juc包下还提供了一些线程同步工具类,如CountDownLatch、Semaphore等等,我们还可以使用ThreadLocal定义线程局部变量!

68.请你说说死锁定义及发生的条件

1.两个或两个以上的线程因为争夺共享资源而造成的相互等待的现象,无外力作用,将无法推进。 当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。 2.从根部解决(从产生死锁的原因入手) 1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用 2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。 3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。 4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

69.请你说说进程间的通信方式

进程间的通信方式:管道、命名管道、信号、消息队列、共享内存、内存映射、信号量、socket

70.说说你对MVC的理解

M(Model)模型,V(View)视图,C(Controller),将代码分为三个模块,模型、视图和控制器进行管理,能够提高代码的维护性,降低代码的耦合性

71.详细的说说Redis的数据类型

redis中常用的五种数据结构:string、list、set、zset、hash。String结构底层是一个简单动态字符串,支持扩容,存储字符串。

list存储线性有序且可重复的元素,底层数据结构可以是双向链表/压缩列表。

set存储不可重复的元素,一般用于求交集、差集等,底层数据结构可以是hash和整数数组,

zset存储的是有序不可重复的元素,zset为每个元素添加了一个score属性作为排序依据,底层数据结构可以是ziplist和跳表,

hash类型存储的是键值对,底层数据结构是ziplist和hash。

redis会在性能以及节省内存间考虑,选择最适合当前状态的底层数据结构实现

72.请你说说乐观锁和悲观锁

乐观锁:乐观锁总是假设最好的情况,每次去拿数据的时候默认别人不会修改,所以不会上锁,只有当更新的时候会判断一下在此期间有没有人更新了这个数据。适用于多读,可以使用版本号机制进行控制

悲观锁:悲观锁总是假设最坏的情况,每次去拿数据是都认为别人会修改,所以每次在拿数据时都会上锁,这样别人想拿这个数据时会阻塞直到拿到锁。mysql数据库的共享锁和排他锁都是悲观锁的实现。

73.设计模式了解么

常用的设计模式有单例模式、工厂模式、代理模式、适配器模式、装饰器模式、模板方法模式等等。

像sping中的定义的bean默认为单例模式,spring中的BeanFactory用来创建对象的实例,他是工厂模式的体现。AOP面向切面编程时代理模式的体现,它的底层就是基于动态代理实现的。适配器模式在springMVC中有体现,它的处理器适配器会根据处理器规则适配相应的处理器执行,模板方法模式用来解决代码重复的问题等

74.说说你对AOP的理解

AOP面向切面编程。是spring两大核心之一,它是一种编程思想,是对OOP的一种补充。它可以对业务逻辑的各个部分进行隔离,降低耦合,提高代码的可重用性。它的底层是通过动态代理实现的。它的应用场景有事务、日志管理等。

75.说说Redis的持久化策略

1.RDB: redis database 在指定的时间间隔内,将内存中的数据集的快照写入磁盘,文件名dump.rdb 适合大规模的数据恢复,对数据库的完整性和一致性要求不是很高 一定时间间隔备份一次,如果数据库意外down掉,就会失去最后一次快照的所有修改

2.AOF: append only file 以日志的形式记录每个写操作,只允许追加文件,不允许改写文件,redis启动时会读取这个文件,并从头到尾执行一遍,以此来恢复数据,文件名appendonly.aof 在最恶劣的环境下,也丢失不会超过2秒的数据,完整性较高,但是会对磁盘持续的进行IO,代价太大。企业级最少需要5G才能支持 如果.aof文件大小超过原来的一倍,会进行重写压缩,保留最小的指令集合

3.优先级 aof>rdb

76.请你讲讲单例模式、请你手写一下单例模式

懒汉式

public class Singleton3 implements Serializable {
    private Singleton3() {
        System.out.println("private Singleton3()");
    }
private static Singleton3 INSTANCE = null;

// Singleton3.class
public static synchronized Singleton3 getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new Singleton3();
    }
    return INSTANCE;
}

public static void otherMethod() {
    System.out.println("otherMethod()");
}
}

饿汉式

public class Singleton1 implements Serializable {
    private Singleton1() {
        if (INSTANCE != null) {
            throw new RuntimeException("单例对象不能重复创建");
        }
        System.out.println("private Singleton1()");
    }
private static final Singleton1 INSTANCE = new Singleton1();

public static Singleton1 getInstance() {
    return INSTANCE;
}

public static void otherMethod() {
    System.out.println("otherMethod()");
}

public Object readResolve() {
    return INSTANCE;
}}

77.请你说说虚拟内存和物理内存的区别

物理内存:计算机中真实拥有的内存。物理内存是有限的,容易产生内存不足问题。

虚拟内存是一种抽象的逻辑概念,拥有连续的内存地址。

78.说说你对IoC的理解

IoC:控制反转。控制:对象的创建的控制权限;反转:将对象的控制权限交给spring。之前我们创建对象时用new,现在直接从spring容器中取,维护对象之间的依赖关系,降低对象之间的耦合度。 实现方式为DI,依赖注入,有三种注入方式:构造器、setter、接口注入

79.请你说说内存管理

Linux 操作系统是采用段页式内存管理方式: 页式存储管理能有效地提高内存利用率(解决内存碎片),而分段存储管理能反映程序的逻辑结构并有利于段的共享。将这两种存储管理方法结合起来,就形成了段页式存储管理方式。 段页式存储管理方式即先将用户程序分成若干个段,再把每个段分成若干个页,并为每一个段赋予一个段名。在段页式系统中,为了实现从逻辑地址到物理地址的转换,系统中需要同时配置段表和页表,利用段表和页表进行从用户地址空间到物理内存空间的映射。 系统为每一个进程建立一张段表,每个分段有一张页表。段表表项中至少包括段号、页表长度和页表始址,页表表项中至少包括页号和块号。在进行地址转换时,首先通过段表查到页表始址,然后通过页表找到页帧号,最终形成物理地址。

80.请你说说IO多路复用(select、poll、epoll)

IO多路复用指的是单个进程或者线程能同时处理多个IO请求,select,epoll,poll是LinuxAPI提供的复用方式。本质上由操作系统内核缓冲IO数据,使得单个进程线程能监视多个文件描述符。select是将装有文件描述符的集合从用户空间拷贝到内核空间,底层是数组,poll和select差距不大,但是底层是链表,这就代表没有上限,而select有数量限制。epoll则是回调的形式,底层是红黑树,避免轮询,时间复杂度从O(n)变为O(1)

81.请你说说线程和协程的区别

\1. 线程是操作系统的资源,线程的创建、切换、停止等都非常消耗资源,而创建协程不需要调用操作系统的功能,编程语言自身就能完成,所以协程也被称为用户态线程,协程比线程轻量很多;

\2. 线程在多核环境下是能做到真正意义上的并行,而协程是为并发而产生的;

\3. 一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行;

\4. 线程进程都是同步机制,而协程则是异步;

\5. 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力;

\6. 操作系统对于线程开辟数量限制在千的级别,而协程可以达到上万的级别。

82.请你说说MySQL的事务隔离级别

四大隔离级别:读未提交、读已提交、可重复读、串行化

隔离级别 脏读 幻读 不可重复读

image-20230208162102985

83.如何利用Redis实现一个分布式锁?

1.自增值实现分布式锁,increment ,java中该方法可以返回自增结果,如果大于初始值说明锁已经被抢到

2.setnx实现,setifabsent,java中该方***给key设置一个值,如果该key已经存在则放回false,通过返回结果判断是否获取到锁

3.redisson实现,redis框架封装了重入锁、读写锁等分布式锁,可以直接获取使用

84.请说说你对反射的了解

反射就是在程序运行期间动态的获取对象的属性和方法的功能叫做反射。它能够在程序运行期间,对于任意一个类,都能知道它所有的方法和属性,对于任意一个对象,都能知道他的属性和方法。 获取Class对象的三种方式:getClass();xx.class;Class.forName(“xxx”); 反射的优缺点:

优点:运行期间能够动态的获取类,提高代码的灵活性。

缺点:性能比直接的Java代码要慢很多。 应用场景:spring的xml配置模式,以及动态代理模式都用到了反射。

85.请你说说ArrayList和LinkedList的区别

ArrayList 是基于数组实现的,LinkedList是基于双向链表实现的,随机查询通过ArrayList查询,删除或增加多用LinkedList实现不需要重新计算索引和大小,LinkedList比Array更占内存,链表包括前后指向的引用和数据体

86.请你说说聚簇索引和非聚簇索引

聚簇索引:索引结果和数据一起存放的索引。

优点:聚簇索引的查询速度非常快,因为聚簇索引本身就是一个B+树,数据节点属于有序的,当定位到索引上的时候就相当于找到了数据。

缺点:更新的时候代价大,依赖有序的数据

非聚簇索引:索引结构和数据分开存放的索引。

优点:更新代价比聚簇索引小

缺点:依赖有序的数据,可能会二次回表查找

87.数据库为什么不用红黑树而用B+树?

AVL树和红黑树基本都是存储在内存中才会使用的数据结构。而数据库中的数据的索引会非常大,所以为了减少内存的占用,索引会被存储到磁盘文件中,此时影响数据库查询效率的主要因素就是磁盘的IO次数。AVL树和红黑树由于一个父节点只能存储两个子节点。所以使用AVL树或红黑树存储大规模数据时,树的深度就会很深,此时磁盘的IO次数也会大幅度增加。B+树中一个父节点有多个子节点,减少了树的深度,磁盘IO次数也相应的减少。

88.请你讲讲工厂模式,手写实现工厂模式

工厂模式:不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂。;分为简单工厂、工厂方法、抽象工厂模式 简单工厂:提供一个统一的工厂类来创造对象,应用场景:需要创建的对象较少。客户端不关心对象的创建过程 工厂方法:针对不同的对象提供不同的工厂,应用场景: 客户端不需要知道它所创建的对象的类。 客户端可以通过子类来指定创建对应的对象。 抽象工厂模式

89.你知道哪些线程安全的集合?

java.uti包中的集合类大部分都是非线程安全的,例如:ArrayList/LinkedList/HashMap等等,但也有少部分是线程安全的,像是Vector和Hashtable,它们属于很古老的API了,是基于Synchronized实现的,性能很差,在实际的开发中不常用。一般可以使用collections工具类中的syncheronizedXxx()方法将非线程安全的集合包装成线程安全的类。在java5之后可以使用concurrent包提供的大量的支持并发访问的集合类,例如ConcurrentHashMap/CopyOnWriteArrayList等

90.请你说说ConcurrentHashMap

一、ConcurrentHashMap的底层数据结构与HashMap一样,也是采用“数组+链表+红黑树

二、采用锁定头节点的方式降低了锁粒度,以较低的性能代价实现了线程安全。

三、实现机制:

  1. 初始化数组或头节点时,ConcurrentHashMap并没有加锁,而是CAS的方式进行原子替换
  2. 插入数据时会进行加锁处理,但锁定的不是整个数组,而是槽中的头节点。所以,ConcurrentHashMap中锁的粒度是槽,而不是整个数组,并发的性能很好。
  3. 扩容时会进行加锁处理,锁定的仍然是头节点。并且,支持多个线程同时对数组扩容,提高并发能力。
  4. 在扩容的过程中,依然可以支持查找操作。

91.说说缓存穿透、击穿、雪崩的区别

一.缓存穿透:客户端访问不存在的数据,使得请求直达存储层,导致负载过大,直至宕机。原因可能是业务层误删了缓存和库中的数据,或是有人恶意访问不存在的数据。

解决方式:1.存储层未命中后,返回空值存入缓存层,客户端再次访问时,缓存层直接返回空值。2.将数据存入布隆过滤器,访问缓存之前经过滤器拦截,若请求的数据不存在则直接返回空值。

二.缓存击穿:一份热点数据,它的访问量非常大,在它缓存失效的瞬间,大量请求直达存储层,导致服务崩溃。

解决方案:1.永不过期:对热点数据不设置过期时间。2.加互斥锁,当一个线程访问该数据时,另一个线程只能等待,这个线程访问之后,缓存中的数据将被重建,届时其他线程就可以从缓存中取值。

三.缓存雪崩:大量数据同时过期、或是redis节点故障导致服务不可用,缓存层无法提供服务,所有的请求直达存储层,造成数据库宕机。解决方案:1.避免数据同时过期,设置随机过期时间。2.启用降级和熔断措施。3.设置热点数据永不过期。4.采用redis集群,一个宕机,另外的还能用

92.Redis如何与数据库保持双写一致性

共有四种同步策略:

1.先更新数据库再更新缓存。缺点:多线程并发下会存在数据库中数据和缓存不一致的的现象。可能出现

2.先更新缓存在更新数据库,优点就是每次数据变化都可以及时的更新缓存,但是消耗很大,影响服务器性能。

3.先删除缓存在更新数据库。缺点:也会导致缓存和数据库数据不一致。

4.先更新数据库再删除缓存。缺点仍然可能存在缓存和数据库中数据不一致的情况,但是,我们可以使用重试机制进行操作。,所以说这是效果最好的解决方案。

93.说说你了解的线程同步方式

1、Java通过加锁实现线程同步,锁有两类:synchronized和Lock。
2、synchronized加在三个不同的位置,对应三种不同的使用方式,这三种方式的区别是锁对象不同:
(1.)加在普通方法上,则锁是当前的实例(this)。 (2.)加在静态方法上,锁是当前类的Class对象。 (3.)加在代码块上,则需要在关键字后面的小括号里,显式指定一个对象作为锁对象。
3、Lock支持的功能包括:支持响应中断、支持超时机制、支持以非阻塞的方式获取锁、支持多个条件变量(阻塞队列)。

94.请你说说innodb和myisam的区别?

1.innodb支持事务,myisam不支持。

2.Innodb支持行级锁;myisam支持表级锁。

3.Innodb的增删改性能更优;Myisam的查询性能更优。

4.Innodb不支持全文索引,myisam默认支持。

5.Innodb默认支持外键,而myisam不支持。

95.String、StringBuffer、Stringbuilder有什么区别

String是不可变字符序列,从它创建到销毁前,都不可变。

StringBuilder是可变字符序列,但不是线程安全的。

StringBuffer是可变字符序列,与StringBuilder功能相似,区别是它是线程安全的。 相比String,StringBuilder有append()方法,即追加字符串。最后toString()转换成String

96.请你说说HashMap底层原理

在1.8之前,HashMap的底层是数组加链表,在1.8之后是数组+链表+红黑树; 它的put流程是:基于哈希算法来确定元素位置,当我们向集合存入数据时,他会计算传入的key的哈希值,并利用哈希值取绝对值再根据集合长度取余来确定元素的位置,如果这个位置已经存在其他元素了,就会发生哈希碰撞,则hashmap就会通过链表将这些元素组织起来。

追加后,需要判断链表长度以决定是否转为红黑树。若链表长度达到8、数组容量未达到64,则扩容。若链表长度达到8、数组容量达到64,则转为红黑树。从而提高查询速度。 扩容机制:HashMap中数组的默认初始容量为16,当达到默认负载因子0.75时,会以2的指数倍进行扩容。 Hashmap是非线程安全的,在多线程环境下回产生循环死链,因此在多线程环境下建议使用ConcurrentHashMap。

97.说说你了解的JVM内存模型

JVM由三部分组成:类加载子系统、执行引擎、运行时数据区

1、类加载子系统:可以根据指定的全限定名来载入类或接口。

2、执行引擎:负责执行那些包含在被载入类的方法中的指令。

3、运行时数据区:分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。当程序运行时,JVM需要内存来存储许多内容,例如:字节码、对象、参数、返回值、局部变量、运算的中间结果等,把这些东西都存储到运行时数据区中,以便于管理。

98.说说JVM的垃圾回收机制

当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集”的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则。而分代收集理论,建立在如下三个分代假说之上,即弱分代假说、强分代假说、跨代引用假说。

依据分代假说理论,垃圾回收可以分为如下几类:

  1. 新生代收集:目标为新生代的垃圾收集。
  2. 老年代收集:目标为老年代的垃圾收集,目前只有CMS收集器会有这种行为。
  3. 混合收集:目标为整个新生代及部分老年代的垃圾收集,目前只有G1收集器会有这种行为。
  4. 整堆收集:目标为整个堆和方法区的垃圾收集。
  5. 加分回答 HotSpot虚拟机内置了很多垃圾收集器,其中针对新生代的垃圾收集器有Serial、ParNew、Parallel Scavenge,针对老年代的垃圾收集器有CMS、Serial Old、Parallel Old。此外,HotSpot还内置了面向整堆的G1收集器。在上述收集器中,常见的组合方式有: 1. Serial + Serial Old,是客户端模式下常用的收集器。 2. ParNew + CMS,是服务端模式下常用的收集器。 3. Parallel Scavenge + Parallel Old,适用于后台运算而不需要太多交互的分析任务。

99.说说类加载机制

加载、验证、准备、解析、初始化

image-20230208164546810

100.请你说一下抽象类和接口的区别

1.抽象类多用于在同类事物中有无法具体描述的方法的场景,而接口多用于不同类之间,定义不同类之间的通信规则。

2.接口只有定义,而抽象类可以有定义和实现。

3.接口需要实现implement,抽象类只能被继承extends,一个类可以实现多个接口,但一个类只能继承一个抽象类。

4.抽象类倾向于充当公共类的角色,当功能需要累积时,用抽象类;接口被运用于实现比较常用的功能,功能不需要累积时,用接口。

101.请你说说==与equals()的区别

== 比较基本数据类型时,比较的是两个数值是否相等; 比较引用类型是,比较的是对象的内存地址是否相等。

equals() 没有重写时,Object默认以==来实现,即比较两个对象的内存地址是否相等; 重写以后,按照对象的内容进行比较

102.说说synchronize的用法及原理

一、用法:

  1. 静态方法上,则锁是当前类的Class对象。

  2. 作用在普通方法上,则锁是当前的实例(this)。

  3. 作用在代码块上,则需要在关键字后面的小括号里,显式指定一个对象作为锁对象。 能够保证同一个时刻只有一个线程执行该段代码,保证线程安全。 在执行完或者出现异常时自动释放锁。

二、原理:底层是采用Java对象头来存储锁信息的,并且还支持锁升级。在JVM里的实现都是 基于进入和退出Monitor对象来实现方法同步和代码块同步

103.Java哪些地方使用了CAS

1、CAS 比较并交换,比较典型的使用场景有原子类、AQS、并发容器。

2、AQS:在向同步队列的尾部追加节点时,它首先会以CAS的方式尝试一次,如果失败则进入自旋状态,并反复以CAS的方式进行尝试。

3、并发容器:以ConcurrentHashMap为例,它的内部多次使用了CAS操作。在初始化数组时,以CAS的方式修改初始化状态,避免多个线程同时进行初始化。在执行put方法初始化头节点时,它会以CAS的方式将初始化好的头节点设置到指定槽的首位,避免多个线程同时设置头节点。

104.说说JVM的垃圾回收算法

1.引用计数法,每次赋值时均要维护引用计数器且计数器本身也有一定的消耗,较难处理循环引用,一般不采用这种方式;
2.复制算法,将内存分为两块,每次只使用其中一块,当这块内存用完,就将还活着的对象复制到另外一块上面,效率高且没有碎片,但是需要双倍的空间,年轻代中使用复制算法;
3.标记-清除,先标记要清除的对象,然后统一回收这些对象,不需要额外的空间,但是需要两次扫描耗时严重并且会产生内存碎片;
4.标记-整理,标记存活对象,然后将标记的存活对象按内存地址依次排序,清除边界外未标记的对象,没有内存碎片,但是需要移动对象。老年代一般用标记-清除和标记-整理的混合实现。

105.请你说说Redis数据类型中的zset,它和set有什么区别?底层是怎么实现的?

zset有序,set无序,但里面的元素不会出现重复,zset常用排行榜相关的功能。set:整型数组、hashtable;zset:ziplist、skiplist

106.说说static修饰符的用法

Java包含五种成员,分别是成员变量、初始化块、构造器、方法、内部类(包括接口、枚举)。static可以修饰除构造器外的其他成员,被static修饰的成员属于类成员,类成员属于整个类,而不属于单个对象,类成员不能访问实例成员,因为类成员比实例成员先进性初始化,很有可能出现类成员初始化完成,但是实例成员还没有初始化的情况。

107.说说线程的状态

线程分为初始化、可运行状态、阻塞、等待、超时等待、销毁这六个状态 初始化是指线程被创建了但还没有调用start()方法 可运行状态是指进程调用了start()方法,并且获取到了CPU的调度权 阻塞状态是指锁被其他线程占用,该线程等待获取锁 等待是指线程正在等待其他线程的中断或通知 超时等待是指在等待的基础上增加了超时限制,在时间过后,即使没有其他其他线程的唤醒,在时间过后该线程也会回到运行状态 销毁是run()方法执行完或者抛出异常

108.说说你对ThreadLocal的理解

ThreadLocal,即线程变量,它将需要并发访问的资源复制多份,让每个线程拥有一份资源。由于每个线程都拥有自己的资源副本,从而也就没有必要对该变量进行同步了。

109.说说Spring Boot常用的注解

Springboot的启动类有一个注解:@SpringbootApplication注解,它是springboot的核心注解,用于开启自动配置。 springbootApplication注解又包含三个注解 @EnableutoConfiguration注解:它用来开启自动注解,配置各种组件 @ComponentScan,用于扫描指定的包和组件。 @SpringBootConfiguration:声明当前类springboot应用的配置类,项目中只能有一个一般无须我们添加。 还有其他的import注解,conditional,uncondition注解等等,它们都是注解里面包含的注解。

110.说说Bean的生命周期

Bean的生命周期主要包括:Bean定义,Bean初始化,Bean生存期,Bean销毁。

具体流程如下:

1,spring启动,查找并加载所需的Bean,然后初始化

2,进行Bean的属性依赖注入。

3,如果Bean实现了BeanNameAware接口(@Resource,@Qualifier),spring会将Bean的Id传入SetBeanName方法去执行。

4,如果Bean实现了BeanFactoryAware接口,spring会调用Bean的setBeanFactory方法将BeanFactory的ioc容器传入。

5,如果Bean实现的时ApplicationContextAware接口的话,Spring会调用Bean的setApplicationContext将Bean应用的上下文传入进来。

6,还一些其他的设定例如使用@PostContruct来定义Bean在初始化时执行的方法,或者使用@PreDestory来定义Ioc容器被销毁时执行的方法等。

111.volatile是如何实现可见性的?

volatile关键字的作用是确保共享变量的内存可见性,即当一个线程修改了共享变量的值,另外一个线程能够读到这个修改后的值。
Volatile是通过实现虚拟机中的内存屏障(memory barriers),或者称为内存栅栏(store store fence/load load fence)来实现可见性的。
内存屏障是一种处理器指令,它会告诉编译器不能重排序在这条指令之前与之后的存储器访问指令,这样可以确定共享变量的值的更新对于其他线程来说是可见的。


  目录