「这是我参与2022首次更文挑战的第29天,活动详情查看:2022首次更文挑战」
摘自小傅哥面经 和 码哥字节等. 如有冒犯, 请谅解.
今天由于要来一个大佬, 特此准备面试题会会.
高开和架构面试题摘选
-
java基础 , 多线程, 算法
-
死锁:
- 死锁是指两个或两个以上的进程( 或线程) 在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用, 它们都将无法推进下去。 产生死锁的必要条件: 1、互斥条件:所谓互斥就是进程在某一时间内独占资源。 2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 3、不剥夺条件:进程已获得资源, 在末使用完之前, 不能强行剥夺。 4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
-
HashMap中, HashCode为什么使用31作为乘数?
- 31 是一个奇质数,如果选择偶数会导致乘积运算时数据溢出。
- 另外在二进制中,2个5次方是32,那么也就是 31 * i == (i << 5) - i。这主要是说乘积运算可以使用位移提升性能,同时目前的JVM虚拟机也会自动支持此类的优化。
-
ThreadLocal,一般可以用在什么场景中? ThreadLocal是怎样的数据结构吗,采用的是什么散列方式?
-
ThreadLocal 要解决的是线程内资源共享 (This class provides thread-local variables.),为同一个线程保存资源, 所以一般会用在全链路监控中,或者是像日志框架 MDC 这样的组件里。
-
ThreadLocal 底层采用的是数组结构存储数据,同时还有哈希值计算下标,这说明它是一个散列表的数组结构;
- 它是一个数组结构。
- Entry ,这里没用再打开,其实它是一个弱引用实现 static class Entry exte nds WeakReference<ThreadLocal<?>> 。这说明只要没用强引用存 在,发生 GC 时就会被垃圾回收。
- 数据元素采用哈希散列方式进行存储,不过这里的散列使用的是 斐波那契 Fibonacci )散列法 ,后面会具体分析。
- 另外由于这里不同于 HashMap 的数据结构,发生哈希碰撞不会存成链表或红黑 树,而是使用拉链法进行存储。也就是同一个下标位置发生冲突时,则 +1 向后寻 址 ,直到找到空位置或垃圾回收位置进行存储。
-
ThreadLocal 使用的就是 斐波那契(Fibonacci)散列法 + 拉链法存储数据到数组结构中。之所以使用斐波那契数列,是为了让数据更加散列,减少哈希碰撞。具体来自数学公式的计算求值,公式:f(k) = ((k * 2654435769) >> X) << Y对于常见的32位整数而言,也就是 f(k) = (k * 2654435769) >> 28
-
-
volatile 是干啥的?volatile 可以解决原子性问题吗? volatile 的底层原理是如何实现的呢?
- volatile 是保证变量对所有线程的可见性的。volatile 会控制被修饰的变量在内存操作上主动把值刷新到主内存, JVM 会把该线程对应的 CPU 内存设置过期,从主内存中读取最新值。volatile 如何防止指令重排也是内存屏障, volatile 的内存屏故障是在读写操作的前后各添加一个 StoreStore 屏障,也就是四个位置,来保证重排序时不能把内存屏障后面的指令重排序到内存屏障之前的位置。
- volatile 并不能解决原子性,如果需要解决原子性问题,需要使用synchronzied 或者 lock等
-
ReentrantLock 和 公平锁是什么? 什么又是CLH?
- ReentrantLock 是一个可重入且独占式锁,具有与 synchronized 监视器(monitor enter、monitor exit)锁基本相同的行为和语意。但与 synchronized相比,它更加灵活、强大、增加了轮询、超时、中断等高级功能以及可以创建公平和非公平锁。
- CLH 是一种基于单向链表的高性能、公平的自旋锁。AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。
- ReentrantLock 是基于 Lock 实现的可重入锁,对于公平锁 CLH 的实现,只是这部分知识的冰山一角,但有这一 脚 ,就可以很好热身便于后续的学习。 ReentrantLock 使用起来更加灵活,可操作性也更大,但一定要在 finally 中释放锁,目的是保证在获取锁之后,最终能够被释放。同时不要将获取锁的过程写在try 里面。 公平锁的实现依据不同场景和 SMP 、 NUMA 的使用,会有不同的优劣效果。在实际的使用中一般默认会选择非公平锁,即使是自旋也是耗费性能的,一般会用在较少等待的线程中,避免自旋时过长。
- AQS 是 AbstractQueuedSynchronizer 的缩写,几乎所有 Lock 都是基于 AQS 来实现了,其底层大量使用 CAS 提供乐观锁服务,在冲突时采用自旋方式进行重试,以此实现轻量级和高效的获取锁。 另外 AbstractQueuedSynchronizer 是一个抽象类,但并没有定义相应的抽象方法,而是提供了可以被字类继承时覆盖的 protected 的方法,这样就可以非常方便的支持继承类的使用
-
什么是CAS?
CAS 是 compareAndSet 的缩写,它的应用场景就是对一个变量进行值变更,在变更时会传入两个参数:一个是预期值、另外一个是更新值。如果被更新的变量预期值与传入值一致,则可以变更。 CAS 的具体操作使用到了 unsafe 类,底层用到了本地方法 unsafe.compareAndSwapInt 比较交换方法。 CAS 是一种无锁算法,这种操作是 CPU 指令集操作,只有一步原子操作,速度非常快。而且 CAS 避免了请求操作系统来裁定锁问题,直接由 CPU 搞定,但也不是没有开销,比如 Cache Miss,感兴趣的小伙伴可以自行了解 CPU 硬件相关知识。
-
-
网络基础
-
ping www.baidu.com 命令后,背后发生了什么?
- 如果是 ping 域名,首先需要通过 DNS 服务拿到对方的 IP。
- 主机构建一个 ICMP 格式的数据包,由 ICMP 协议将这个数据包(有效负荷)连同目的 IP 地址一起交给 IP 协议。
-
- IP 协议构建一个分组(本机 IP + 控制信息 + 目的 IP) ,控制信息至少包含了 01h 的协议字段,这样当此分组到达目的方时,这些内容就会告诉目的主机应该将这个有效负荷交付给哪个协议来处理,本例中就是 ICMP。
- 如果目的 IP 不跟自己在同一个网段,则需要通过路由转发,先通过路由信息得到网关 IP。如果在同一网段则没有此步骤。
- 如果之前与目的 IP (或网关 IP)通信过,应该在 ARP 缓存表中能查到对应的 MAC 地址。如果没有,就需要通过 ARP 广播来获取 MAC 地址了。把 IP 地址解析为 MAC 地址后,此分组就可被传送到数据链路层以组建成帧。
- 数据链路层将控制信息封装到此分组上,创建成帧。在这个帧中,附加有目的 MAC 地址和源 MAC 地址,以及以太网类型字段(用来指明应用于帧数据字段的协议)。在帧的尾部是 FCS(帧校验序列)字段,这个部分装载了 CRC(循环冗余校验)的计算结果。
- 把帧交付给物理层,物理层会以一次一比特的方式将帧发送到物理介质。
- 这时,此冲突域中的每台设备都会接收这些比特,并将它重新组建成帧。每个设备都会对接收到的内容进行 CRC 运算,并与帧中 FCS 字段的内容进行比对。如果值不匹配,接收到的帧将被丢弃。如果匹配,接着将检查目的 MAC 地址与自己的是否匹配。如果匹配,则接下来查看以太网类型字段,以获悉完成数据后续处理的网络层协议。
- 将分组从帧中取出,并将其他部分丢弃。然后,分组被递交给以太网类型字段中列出的协议(本例是 IP 协议)。
- IP 协议接收这个分组,并检查它的 IP 的地址。如果这是一个路由器(网关),跳到 11。如果这就是目的主机,跳到下一步 12。
- 由于分组的目的 IP 地址与此接收路由器上的各个地址均不匹配,路由器会在其路由表中查找目的 IP 的地址,(在此路由表中需要包含目的子网的相关表项,否则路由器会立即将收到的分组丢弃,并同时向发送方回送一个携带有目标网络不可达信息的 ICMP 报文。),并按照路由规则被交换到指定的输出接口的缓冲区内。接着重复 5 ~ 11 步骤。
- 分组的目的 IP 与此主机 IP 地址匹配,检查分组的协议字段,了解分组有效负荷的交付对象。此有效负荷将被递交给 ICMP,后者知道这是一个回应请求数据。ICMP 将负责应答这个请求,它首先立即丢弃这个接收到的分组然后产生一个新的有效负荷作为回应应答数据。
-
-
大数据
-
Hadoop MapReduce 作业的生命周期
-
作业提交与初始化
- 用户提交作业后, 首先由 JobClient 实例将作业相关信息, 比如将程序 jar 包、作业配置文件、 分片元信息文件等上传到分布式文件系统( 一般为HDFS)上,其中,分片元信息文件记录了每个输入分片的逻辑位置信息。 然后 JobClient通过 RPC 通知 JobTracker。 JobTracker 收到新作业提交请求后, 由 作业调度模块对作业进行初始化:为作业创建一个 JobInProgress 对象以跟踪作业运行状况, 而 JobInProgress 则会为每个Task 创建一个 TaskInProgress 对象以跟踪每个任务的运行状态, TaskInProgress 可能需要管理多个“ Task 运行尝试”( 称为“ Task Attempt”)。
-
任务调度与监控。
- 前面提到,任务调度和监控的功能均由 JobTracker 完成。TaskTracker 周期性地通过 Heartbeat 向 JobTracker 汇报本节点的资源使用 情况, 一旦出 现空闲资源, JobTracker 会按照一定的策略选择一个合适的任务使用该空闲资源, 这由任务调度器完成。 任务调度器是一个可插拔的独立模块, 且为双层架构, 即首先选择作业, 然后从该作业中选择任务, 其中,选择任务时需要重点考虑数据本地性。 此外,JobTracker 跟踪作业的整个运行过程,并为作业的成功运行提供全方位的保障。 首先, 当 TaskTracker 或者Task 失败时, 转移计算任务 ; 其次, 当某个 Task 执行进度远落后于同一作业的其他 Task 时,为之启动一个相同 Task, 并选取计算快的 Task 结果作为最终结果。
-
任务运行环境准备
- 运行环境准备包括 JVM 启动和资源隔 离, 均由TaskTracker 实现。 TaskTracker 为每个 Task 启动一个独立的 JVM 以避免不同 Task 在运行过程中相互影响 ; 同时,TaskTracker 使用了操作系统进程实现资源隔离以防止 Task 滥用资源。
-
任务执行
- TaskTracker 为 Task 准备好运行环境后, 便会启动 Task。 在运行过程中, 每个 Task 的最新进度首先由 Task 通过 RPC 汇报给 TaskTracker, 再由 TaskTracker汇报给 JobTracker。
-
作业完成。
- 待所有 Task 执行完毕后, 整个作业执行成功。
-
-
-
项目中的难点解决和亮点
-
git
-
redis
-
Redis数据类型于底层数据结构关系
-
- 为什么说Redis是单线程的?
**单线程指的是 Redis 键值对读写指令的执行是单线程。*** ***因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最**有可能是机器内存的大小或者网络带宽**
-
spring + 源码
-
Spring 框架优缺点
- 优点
- 方便解耦,简化开发:Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。
- AOP编程的支持:Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
- 声明式事务的支持:只需要通过配置就可以完成对事务的管理,而无需手动编程。
- 方便程序的测试:Spring对Junit4支持,可以通过注解方便的测试Spring程序。
- 方便集成各种优秀框架:Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。
- 降低JavaEE API的使用难度:Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。
- 缺点
- Spring依赖反射,反射影响性能
- 使用门槛升高,入门Spring需要较长时间
-
Spring 框架中都用到了哪些设计模式
- Spring 框架中使用到了大量的设计模式,下面列举了比较有代表性的:
代理模式
—在 AOP 和 remoting 中被用的比较多。单例模式
—在 spring 配置文件中定义的 bean 默认为单例模式。模板方法
—用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTempl ate。前端控制器
—Spring 提供了 DispatcherServlet 来对请求进行分发。视图帮助(View Helper )
—Spring 提供了一系列的 JSP 标签,高效宏来辅助将分散的代码整合在视图里。依赖注入
—贯穿于 BeanFactory / ApplicationContext 接口的核心理念。工厂模式
—BeanFactory 用来创建对象的实例
-
-
springcloud + 源码
-
数据库
-
MySQL 中有哪几种锁?
1、表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高, 并发度最低。 2、行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低, 并发度也最高。 3、页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
-
-
前端
-
架构
-
设计模式使用过那些? 你对设计模式的理解.
六大原则
总原则:开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。
1、单一职责原则
- 不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。
2、里氏替换原则(Liskov Substitution Principle)
- 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科
- 历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
3、依赖倒转原则(Dependence Inversion Principle)
- 这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。
4、接口隔离原则(Interface Segregation Principle)
- 这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
5、迪米特法则(最少知道原则)(Demeter Principle)
- 一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
- 最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。
6、合成复用原则(Composite Reuse Principle)
- 原则是尽量首先使用合成/聚合的方式,而不是使用继承。
-