编程知识 cdmana.com

java整理,java高级特性编程及实战第一章

1、ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。

2、对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。

3、对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

  • ArrayList和HashSet的区别

【答题要点】

1、ArrayList中保存的数据是有序的,HashSet中保存的数据是无序的。

2、ArrayList是用数组实现的而HashSet的底层是用HashMap实现的。ArrayList可以保存重复的数据,而HashSet不能。调用ArrayList的add方法时在数组的下一个位置添加了一个对象,调用HashSet的add方法时,实际上是向HashMap中增加了一行(key-value对),该行的key就是向HashSet增加的那个对象,该行的value就是一个Object类型的常量。

3、保证HashSet的数据是唯一的要重写equals()和hashCode()方法。

  • HashMap、HashTable和ConcurrentHashMap的区别

【答题要点】

1、HashMap是非线程安全的不适用于多线程资源共享场景,可以接受 key和 value 为 null,而且在单线程场景下性能最好;

2、Hashtable与HashMap相比API都是sychronized的,因此是线程安全的,而且Hashtable不可以接受 key和 value 为 null;由于Hashtable中的所有数据都加了同步锁,因此性能最差;

3、ConcurrentHashMap在HashMap的基础上对其所存的数据进行了分段,每个分段都有一个锁,当读的时候不受锁的影响,只有在写的时候受锁的限制,缩小了锁的范围,不同段之间不受锁竞争影响,既保证了线程安全,有提升了性能。

【注意】候选人在回答完如上3点后,还能引申出Segment的机制则可加分。

Segment 类继承于 ReentrantLock 类,从而使得 Segment 对象能充当锁的角色。每个 Segment 对象用来守护其(成员对象 table 中)包含的若干个桶。

table 是一个由 HashEntry 对象组成的数组。table 数组的每一个数组成员就是散列映射表的一个桶。

count 变量是一个计数器,它表示每个 Segment 对象管理的 table 数组(若干个 HashEntry 组成的链表)包含的 HashEntry 对象的个数。每一个 Segment 对象都有一个 count 对象来表示本 Segment 中包含的 HashEntry 对象的总数。注意,之所以在每个 Segment 对象中包含一个计数器,而不是在 ConcurrentHashMap 中使用全局的计数器,是为了避免出现“热点域”而影响 ConcurrentHashMap 的并发性。

并发问题


  • Java中volatile与synchronized有什么区别,分别作用于哪些场景

【答题要点】

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

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

3、volatile仅能实现变量的修改可见性,但不具备原子特性,而synchronized则可以保证变量的修改可见性和原子性.

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

4、volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化.

  • synchonrize和juc中的锁比较分别适用于哪些场景

【答题要点】

1、ReentrantLock在内存上的语义与synchronize相同, 但是它提供了额外的功能, 可以作为一种高级工具. 当需要一些可定时, 可轮询, 可中断的锁获取操作, 或者希望使用公平锁, 或者使用非块结构的编码时才应该考虑ReetrantLock.

2、在业务并发简单清晰的情况下推荐synchronized, 在业务逻辑并发复杂, 或对使用锁的扩展性要求较高时, 推荐使用ReentrantLock这类锁.

  • 既要保证线程安全又要尽可能提升性能,怎么取得平衡?

【答题要点】

1、分析业务场景,尽可能的实现无锁编程;

2、对于并发场景,尽可能的缩小锁的范围;

  • 分布式场景中,如何实现一个全局锁

【答题要点】

1、使用数据库实现;

2、使用缓存实现;

总之候选人能够回答出来,用一个全局唯一的资源来满足资源竞争的顺序执行和原子性就行。

  • 对于一个8核的的高性能CPU来说在多线程场景下是不是线程池设置的越大越好?如何确定线程池的大小,设置不当会带来什么问题?

【答题要点】

1、并非越大越好,线程池大小的设置要根据CPU处理的任务特征来区别对待。

2、如果线程执行的是CPU密集型任务服务器的物理内核数就应该被视为是有限的资源,这样创建的线程数就不应该超过系统的内核数。

3、如果线程执行的是IO密集型任务就要根据IO的占比和速度进行性能测试来确认线程池的大小。

4、线程池大小设置的过小或者过大都会导致系统产生问题无法利用系统资源,如果线程池的线程数量过少这使得用户需要花费很长时间来等待请求的响应。但是,如果允许创建过多的线程,会产生线程不断切换,系统的资源又会被耗尽,这会对系统造成更大的破坏,甚至宕机。

JVM


  • 题一:jvm的类加载机制是什么样的?有几类加载器?

参考答案:

jvm通过双亲委派模型进行类的加载,即当某个类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

JVM提供了3种类加载器:

1》启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。

2》扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。

3》应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。

  • 题二:JDK8中MetaSpace代表什么?

参考答案:

MetaqSpace是JDK8才诞生的名词,它是一种新的内存空间,中文译为:元空间;JDK8 HotSpot中移除了永久代(PermGen Space),使用MetaSpace来代替,MetaSpace是使用本地内存来存储类元数据信息。内存容量取决于操作系统虚拟内存大小,通过参数MaxMetaspaceSize来限制MetaSpace的大小。

  • 题三:JVM内存结构是什么样的?

参考答案:

JVM内存主要分为五个区:方法区,虚拟机栈,本地方法栈,堆,程序计数器;每个区特点如下:

1》方法区

1.1、有时候也称为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型的卸载

1.2、方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。

1.3、该区域是被线程共享的。

1.4、方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。

2》虚拟机栈

2.1、虚拟机栈也就是我们平常所称的栈内存,它为java方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。

2.2、虚拟机栈是线程私有的,它的生命周期与线程相同。

3》本地方法栈

本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务。

4》堆

java堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

例都在这里创建,因此该区域经常发生垃圾回收操作。

5》程序计数器

内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java虚拟机规范没有规定任何OOM情况的区域。

  • 题四:Java中垃圾收集的方法有哪些?

参考答案:

1》标记-清除:

这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:1.效率不高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次GC动作。

2》复制算法:

为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。

于是将该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大那份内存交Eden区,其余是两块较小的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满,就将对象复制到第二块内存区上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制复制到老年代中。(java堆又分为新生代和老年代)

3》标记-整理

该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。

4》分代收集

现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。

  • 题五:MinorGC和FullGC的区别?

参考答案:

1》Minor GC通常发生在新生代的Eden区,在这个区的对象生存期短,往往发生GC的频率较高,回收速度比较快,一般采用复制-回收算法

2》Full GC/Major GC 发生在老年代,一般情况下,触发老年代GC的时候不会触发Minor GC,所采用的是标记-清除算法

  • 题六:产生concurrent-mode-failure的原因

参考答案:在CMSGC过程中,由于老年代剩余空间无法存放需要分配的对象,导致产生上述原因。

  • 题七:请写出常用的几种垃圾回收器及启用参数

参考答案:

1》串行收集器:暂停所有的线程,属于单线程工作,启用命令:-XX:+UseSerialGC

2》并行收集器(默认):暂停所有线程,多线程工作,启用命令:-XX:+UseParNewGC

3》G1收集器:这个主要是对堆内存进行分区,并发性回收,启用:-XX:+UseG1GC

4》CMS收集器:多线程扫描,使用的算法是标记清除算法,标记需要回收的对象,进行回收,启动命令:-XX:+UseConcMarkSweepGC

设计模式


  • 题一:平时工作中用到过哪些设计模式?分别有什么特点?

参考答案:

1》适配器模式:

2》工厂模式

3》单例模式

  • 题二:在Java中,什么时候用重载,什么时候用重写?

参考答案:

对有经验的Java设计师来说,这是一个相当简单的问题。如果你看到一个类的不同实现有着不同的方式来做同一件事,那么就应该用重写(overriding),而重载(overloading)是用不同的输入做同一件事。在Java中,重载的方法签名不同,而重写并不是。

  • 题三:请列举出在JDK中几个常用的设计模式?

参考答案:

1》单例模式用于Runtime, Calendar和其他的一些类中。

2》工厂模式被用于各种不可变的类如Boolean,像Boolean.valueOf方法。

3》观察者模式被用于swing和很多的时间监听中。

4》装饰器模式被用于多个java IO类。

  • 题四:举一个用Java 实现的装饰模式(decorator design pattern)?它是作用于对象层次还是类层次?

参考答案:

装饰模式增加强了单个对象的能力。Java IO 到处都使用了装饰模式,典型例子就是Buffered 系列类如BufferedReader和BufferedWriter,它们增强了Reader和Writer对象,以实现提升性能的 Buffer 层次的读取和写入。

笔试题一:写出至少两种单例模式的实现

参考答案:

1》懒汉式

public class TestSingleton{

private static TestSingleton instance;

private TestSingleton(){

}

public static synchronized TestSingleton getInstance(){

if(instance == null){

instance = new TestSingleton();

}

return instance;

}

}

2》饿汉式

public class TestSingleton {

private static TestSingleton instance = new TestSingleton();

private TestSingleton() {

}

public static TestSingleton getInstance() {

return instance;

}

}

  • 笔试题二:什么是适配器模式?用Java 实现一个适配器模式

参考答案:

适配器模式在现代的Java框架中十分常用。这种模式适用于以下场景:想使用一个已存在的类,但是该类不符合接口需求;或者需要创建一个可重用的类,适配没有提供合适接口的其它类。

java.io.InputStreamReader(InputStream) (返回一个Reader)

java.io.OutputStreamWriter(OutputStream) (返回一个Writer)

Spring


  • 【spring的Annotation使用】Spring @Resource、@Autowired的区别

【参考答案】

@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入;@Autowired默认是按照类型装配注入的;

  • 【spring的scope理解】在两个不同的线程中通过Spring获取同样id的bean,获取到的是同一个对象还是不同对象?

【参考答案】

  1. “同一个对象”,如果仅这样回答,可以得到1分。

  2. 考虑了scope,并且答出不同的scope下的结论是同一个对象还是不同对象,可以加分

  • 【Spring Boot Starter原理】总结Spring Boot Starter的工作原理

【参考答案】

  1. Spring Boot 在启动时扫描项目所依赖的JAR包,寻找包含spring.factories文件的JAR

  2. 根据spring.factories配置加载AutoConfigure类

  3. 根据 @Conditional注解的条件,进行自动配置并将Bean注入Spring Context

  • 【spring实例初始化】我们希望一个bean实例被初始化后执行一些逻辑,怎么做?

【参考答案】

如果存在类实现 BeanPostProcessor(后处理Bean) ,执行postProcessBeforeInitialization

如果Bean实现InitializingBean 执行 afterPropertiesSet

调用 指定初始化方法 init

如果存在类实现 BeanPostProcessor(处理Bean),执行postProcessAfterInitialization

  • 【spring设计模式】Spring 框架中都用到了哪些设计模式,并举例说明

【参考答案】

代理模式—在AOP和remoting中被用的比较多。

单例模式—在spring配置文件中定义的bean默认为单例模式。

模板方法—用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。

前端控制器—Spring提供了DispatcherServlet来对请求进行分发。

视图帮助(View Helper )—Spring提供了一系列的JSP标签,高效宏来辅助将分散的代码整合在视图里。

依赖注入—贯穿于BeanFactory / ApplicationContext接口的核心理念。

工厂模式—BeanFactory用来创建对象的实例。

  • 【spring循环依赖(spring circular dependency)】如何解决Spring循环依赖

【参考答案】

首先什么叫循环依赖,有几种情况:

A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象

A的构造方法中依赖了B的实例对象,同时B的某个field或者setter需要A的实例对象,以及反之

A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象,以及反之

Spring通过三级缓存加上“提前曝光”机制,配合Java的对象引用原理

  1. A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程。

  2. B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象

  3. B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化。

前面讲到的三种情况,第一种是无法解决的。

Redis


  • 【Redis数据结构】Redis支持哪几种数据结构

【参考答案】

字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。

  • 【Redis查询key列表】假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?

【参考答案】

• 使用keys指令可以扫出指定模式的key列表。

• redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。为了避免这种问题,可以换用scan指令。scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

  • 【redis集群搭建】使用redis进行缓存数据时,当三台机器扩容到四台时,如何能做到迁移数据量最小

【参考答案】

组建集群时使用分布式一致算法,扩容后能尽可能命中原来的机器

  • 【redis分布式锁】使用redis做一个分布式锁

【参考答案】

• 先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。但这种做法可能带来的一个问题:在setnx之后执行expire之前进程意外crash或者要重启维护了,导致永远不释放该锁。

• 为了避免上面提到的问题。setnx时加上一个时间戳,表示失效时间。

SETNX lock.foo <current Unix time + lock timeout + 1>

该锁包含一个Unix时间戳,如果这样一个时间戳等于当前的Unix时间,该锁将不再有效。

另一个客户端C4 检测到一个过期的锁并且都尝试去释放它的算法:

• C4 发送SETNX lock.foo为了获得该锁

• 已经崩溃的客户端 C3 仍然持有该锁,所以Redis将会返回0给 C4

• C4 发送GET lock.foo检查该锁是否已经过期。如果没有过期,C4 客户端将会睡眠一会,并且从一开始进行重试操作

• 另一种情况,如果因为 lock.foo键的Unix时间小于当前的Unix时间而导致该锁已经过期,C4 会尝试执行以下的操作:

GETSET lock.foo <current Unix timestamp + lock timeout + 1>

• 由于GETSET 的语意,C4会检查已经过期的旧值是否仍然存储在lock.foo中。如果是的话,C4 会获得锁

• 如果另一个客户端,假如为 C5 ,比 C4 更快的通过GETSET操作获取到锁,那么 C4 执行GETSET操作会被返回一个不过期的时间戳。C4 将会从第一个步骤重新开始。请注意:即使 C4 在将来几秒设置该键,这也不是问题。

为了使这种加锁算法更加的健壮,持有锁的客户端应该总是要检查是否超时,保证使用DEL释放锁之前不会过期,因为客户端故障的情况可能是复杂的,不止是崩溃,还会阻塞一段时间,阻止一些操作的执行,并且在阻塞恢复后尝试执行DEL(此时,该LOCK已经被其他客户端所持有)

  • 【redis限流】使用Redis实现一个恶意登录保护功能,限制1小时内每用户Id最多只能登录5次

【参考答案】

  1. 使用信号量进行控制并发,限制一项资源最多能够同时被多少个进程访问。

  2. 使用List数据结构,每条数据记录登陆的时间,登陆时,首先将list中的超过一小时的数据清理,然后将此次登陆时间加入到list中,如果list中的size>=5时,说明list中已经记录了该用户最近一小时登陆超过5次,如果是1小时外,否则该用户最近一小时未超过5次。

Mybatis


  • Mybatis中#和$的区别是什么?

【参考答案】

${} 变量的替换阶段是在动态 SQL 解析阶段,而 #{ }变量的替换是在 DBMS 中。

这是 #{} 和 ${} 我们能看到的主要的区别,除此之外,还有以下区别:

• #方式能够很大程度防止sql注入。

• $方式无法防止Sql注入。

• $方式一般用于传入数据库对象,例如传入表名.

• 一般能用#的就别用$.

  • Mybatis中$可能带来的风险是什么?

【参考答案】

sql注入

  • Mybatis DAO接口为什么不需要实现类?

【参考答案】

mybatis通过JDK的动态代理方式,在启动加载配置文件时,根据配置mapper的xml去生成Dao的实现。

session.getMapper()使用了代理,当调用一次此方法,都会产生一个代理class的instance,看看这个代理class的实现.

  • mybatis是用来做什么的?为什么不直接用jdbc

【参考答案】

MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。

相对于JDBC,MyBatis有以下优点:

• SQL统一管理,便于维护和管理

• 能够对结果集进行映射

• 提供了缓存功能

  • mybatis的缓存分几种?各有什么作用?

【参考答案】

MyBatis中的缓存分为两种:一级缓存和二级缓存。使用过MyBatis的可能听到过这样一句话“一级缓存是sqlSession级别的,二级缓存是mapper级别的”

这也说明了,当使用同一个sqlSession时,查询到的数据可能是一级缓存;而当使用同一个mapper是,查询到的数据可能是二级缓存。

一级缓存默认开启的。二级缓存需要配置才开启,当我们的配置文件配置了cacheEnabled=true时,就会开启二级缓存

MQ


问题:

  • 1. 请列举出可能发生消息重复消费的场景

  • 2. 怎么解决消息的重复消费问题

  • 3. 业务端如何保证幂等性(提问要点:以上三个问题是连环炮,当候选人回答出一个问题后,引出下一个问题)

答案要点:

  1. 请列举出可能发生消息重复消费的场景

要点:重复消费在不可避免,1)网络出现抖动;2)消费端任务执行超时;3)消费端出现异常;

  1. 怎么解决消息的重复消费问题

要点:MQ的服务端通常不保证消息不重复,由业务端来去重;主要是消费端业务逻辑保持幂等性;也可以由消息的服务端通messageId保证,但是通常不这么做

  1. 业务端如何保证幂等性

要点:1)使用唯一字段,建立唯一性索引;2)多版本控制;3)状态机幂等;4)保证数据唯一性的其它方式,比如通过redis、zk等方式

  • 消息队列消息推拉模式是什么意思,分别在什么场景下适用;

答案要点:

1)推:服务端主动,拉:消费端主动;

2)看消费端的消费能力,处理复杂时间长的适合拉模式,推模式实时性响应更好;其中推对于消费端要求更高,拉对于MQ服务端的堆积能力要求较高

  • 消息的可靠性怎么是保证的;

答案要点:

1)生产者端:通过同步发送消息,收到服务端的确认写入并存储,来保证一致性;rabbitmq还支持先写入本地磁盘再发送给服务端

2)服务器端:通过把消息写入磁盘、数据库、分布式存储系统中,保证服务端的可靠性

3)消费端:通过消费端确认消费成功,才进行下一条消费;通过消息重试保证最终会被消费;

版权声明
本文为[Alibaba_开源]所创,转载请带上原文链接,感谢
https://blog.csdn.net/m0_63174811/article/details/121542662

Scroll to Top