中国邮政储蓄银行面经总结

163 阅读16分钟

ThreadLocal

通常情况下,我们创建的变量是可以被任意一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢?
ThreadLoacl类主要解决的就是让每个线程绑定自己的值,可以将ThreadLoacl类形象得比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。
如果创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这就是ThreadLocal变量名的由来。他们可以使用get()和set()方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。
Thread类中有一个threadlocals和一个inheritableThreadLocals变量,它们都是ThreadLocalMap类型的变量,可以把ThreadLocalMap理解为ThreadLocal类实现的定制化的HashMap。默认情况下这两个变量都是null,只有当前线程调用ThreadLocal类的set和get方法才能创建他们,实际上调用这两个方法的时候,我们调用的是ThreadLocalMap类对应的get()方法和set()方法。
最终的变量是放在了当前线程的ThreadLocalMap中,并不是存在于ThreadLocal上,ThreadLoacl可以理解为只是ThreaLocalMap的封装,传递了变量值。
ThrealLocal类中可以通过Thread.currentThread()获取到当前线程的对象后,直接通过getMap(Thread t)访问到该线程的ThreadLoaclMap对象。
每个Thread中都具有一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为key,Object对象为value的键值对。

Java有哪些集合类型?

Java的集合类型主要有3种:
1、List(有序、可重复):List中存放的对象是有序的,同时也是可以重复的,List关注的是索引,拥有一系列和索引相关的方法,查询速度块。因为往List集合里插入或者删除元素时,会伴随着后面数据的移动,所以插入删除数据速度慢。
2、Set(无序,不能重复):Set里存放的对象是无序的,不能重复的,集合中的对象不按照特定的方式排序,只是简单得把对象加入集合中。
3、Map(键值对、键唯一、值不唯一):Map集合中存储的是键值对,键不能重复,值可以重复。根据键得到值,对map集合遍历时先得到键的Set集合,对Set进行遍历,得到对应的值。

JVM垃圾收集器 (CG)

收集算法是内存回收的方法论,垃圾回收器是内存回收的具体实现。

  • CMS收集器
    CMS收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。
    CMS收集器是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程和用户线程同时工作。 1)初识标记
    2)并发标记
    3)重新标记
    4)并发清除
    优点:并发收集、低停顿
    缺点:对CPU资源敏感
    无法处理浮动垃圾
    它使用的回收算法-“标记-清除”算法会导致收集结束时有大量的空间碎片产生

  • G1收集器
    G1是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足CG停顿时间要求的同时,还具备高吞吐量性能特征。
    G1收集器具有以下特征:
    并行与并发
    分代收集
    空间整合
    可预测的停顿
    G1收集器的运作大致分为以下几个步骤:

  • 初识标记

  • 并发标记

  • 最终标记

  • 筛选回收
    G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region。这种Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限时间内可以尽可能高的收集效率。

类加载器

类加载过程:加载->连接->初始化
连接过程又可以分为三步:验证->准备->解析

  • 加载是类加载过程的第一步,主要完成三件事情:
  • 1、通过全类名获取定义此类的二进制字节流
  • 2、将字节流所代表的静态存储结构转换为方法区的运行时数据结构
  • 3、在内存中生成一个代表该类的Class对象,作为方法区这些数据的访问入口

类加载器的主要作用就是加载Java类的字节码(.class文件)到JVM中(在内存中生成一个代表该类的Class对象),字节码可以是Java源程序经过javac编译得来,也可以是通过工具动态生成或者通过网络下载得来。

类加载器加载规则:
JVM启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载。大部分类在具体用到的时候才会去加载,这样对内存更加友好。
对于已加载的类会放在ClassLoader中。在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。对于一个类加载器来说,相同二进制名称的类只会被加载一次。

类加载器总结
JVM中内置了三个重要的ClassLoader:
1、BootstrapClassLoader(启动类加载器)。
2、ExtensionClassLoader(扩展类加载器)。
3、AppClassLoader(应用程序类加载器)。

双亲委派模型

类加载器有很多种,当我们想要加载一个类的时候,具体是哪个类加载器加载呢?这就需要提到双亲委派模型。
ClassLoader类使用委托模型来搜索类和资源。
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。
ClassLoader实例会在亲自查找类或资源之前,将搜索类或资源的任务委派给其父类加载器。
每当一个类加载器接收到加载请求时,它会先将请求转发给父类加载器。在父类加载器没有找到所请求的类的情况下,该类加载器才会尝试才加载。
双亲委派模型的执行流程:

  • 在类加载的时候,系统会首先判断当前类是否被加载过,已经被加载的类会直接返回,否则才会尝试加载。

  • 类加载器在进行类加载的时候,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。这样的话,所有的请求最终都会传送到顶层的启动类加载器中。

  • 只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。

  • JVM判定两个Java类是否相同的具体规则:
    JVM不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必不相同。

浏览器如何保存用户的登录状态

  • cookie和session
    cookie是什么?
    Cookie实际上就是一小段的文本信息,客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端会把Cookie保存起来。
  • JWT
  • HTTP Auth Authentication

什么是Cookie和Session?

  1. 什么是Cookie?
    HTTP Cookie是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务器两个请求是否来自同一个浏览器,如保存用户的登录状态。Cookie使基于无状态的HTTP协议记录稳定的状态信息成为了可能。
    Cookie主要用于以下三个方面:
  • 会话状态管理(如用户登录信息、购物车、游戏分数或其他需要记录的信息)
  • 个性化设置(如用户自定义设置,主题等)
  • 浏览器行为跟踪(如跟踪分析用户行为等)

2)什么是Session?
Session代表着服务器和客户端一次会话的过程。Session对象存储特定用户会话所需的属性以及配置信息。这样,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量不会丢失,而是在整个用户会话中一直存在下去。当客户端关闭会话,或者Session超时失效时会话结束。

如何考虑分布式Session问题?

在互联网公司为了可以支持更大的流量,后端往往需要多台服务器共同支撑前端应用请求,那如果用户在A服务器登录了,第二次请求跑到服务器B就会出现登录失效问题。
分布式Session一般会有以下几种解决方案:

  • 客户端存储:直接将信息存储在cookie中,cookie是存储在客户端的一小段数据,客户端通过http协议和服务器进行cookie交互,通常用来存储一些不敏感信息。
  • 共享Session:服务端无状态化,将用户的Session等信息使用缓存中间件(如Redis)来统一管理,保障分发到每一个服务器的相应结果都一致。

Java实现线程安全的方法

要实现线程安全,需要保证数据操作的两个特性:
(1)原子性:对数据的操作不会受到其他线程打断
(2)可见性:当线程修改了数据的状态时,能够立即被其他线程知晓

  • Volatile保证可见性
    1)保证变量对所有线程的可见性
    2)禁止指令的重排序
  • 线程同步-Synchronized锁

索引是什么?

索引是一种特殊的文件,他们包含着对数据表里所有记录的引用指针。
索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。(索引就相当于目录) ,为了方便查找书中的内容,通常对内容建立索引形成目录,而且索引是一个文件,需要占据物理空间。

索引有哪些优缺点?

索引的优点

  • 可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
  • 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
    索引的缺点
  • 时间方面:创建索引和维护索引需要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增\改\删的执行效率。
  • 空间方面:索引需要占据物理空间。

sql的distinct关键字

  • 删除重复记录
  • SQL DSITINCT 关键字需要和SELECT语句一起使用,用来删除结果集中所有重复的记录,仅保留唯一的一条记录。
  • 数据表中有时候会有重复的记录,如果您只需要其中一条,就可以使用DISTINCT关键字。

sql的左连接、右连接、内连接

  • 左连接(LEFT JOIN)全称为左外连接
    以左表为基础,根据ON后面给出的两个表的条件将两个表连接起来
    结果是会将左表所有的查询结果展示出来,而右表只展示出ON后面的条件与左表满足的部分
  • 右连接(RIGHT JOIN)全称为右外连接
    以右表为基础,根据ON后面给出的两个表的条件将两个表连接起来。
    结果是会将右表所有的查询展示出来,而左表只展示出ON后面的条件与右表满足的部分。
  • 内连接
    是同时将两个表作为参考对象,根据ON后面给出的两个表的条件将两个表连接起来。
    结果则是只有两个表同时满足ON后面的条件的部分才会展示出来。

并发和并行的区别

并行是指在同一时刻,有多条指令在多个处理器上同时执行。
并发是指在同一个时刻只能有一条指令执行,但多个进程指令被快速的轮转执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行,只是把时间分成若干段,使多个进程快速交替的执行。

SpringBoot自动装配

通过注解或者一些简单的配置就能在SpringBoot的帮助下实现某块功能。

Java多线程的几种实现方式

1.继承Thread类,重写run方法
2.实现Runnable方法,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
3.通过Callable和FutureTask创建线程
4.通过线程池创建线程

Java线程池

  • 线程池的优势:
    (1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
    (2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
    (3)提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。
  • 线程池的使用:
    线程池的真正实现类是ThreadPoolExecutor。
  • 线程池的参数:
  1. 任务队列(workQueue)
  2. 线程工厂
  3. 拒绝策略(handler)

start()和run()的区别

1)start:用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行这个线程的内容,Run方法运行结束,此线程随即终止。
2)run:run()方法只是类的一个普通方法而已,如果调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可以继续执行下面的代码,这样就没有达到写线程的目的。
总结:调用start方法方可启动线程,而run方法只是thread的一个普通的方法调用,还是在主线程里执行。

插入排序

直接插入排序

基本思想
每一步将一个待排序的数据插入到前面已经排好序的有序序列中,直到插完所有元素为止。
算法实现
直接插入排序是将无序序列中的数据插入到有序序列中,在遍历无序序列时,首先拿无序序列中的首元素去与有序序列中的每一个元素比较并插入到合适的位置,一直到无序序列中的所有元素插完为止。

隔离级别

数据库事务指的是一组数据操作,事务内的操作要么全部成功,要么全部失败,什么都不做,其实不是没做,是可能做了一部分但是只要有一步失败,就要回滚所有操作。

  • 事务隔离级别需要实际解决的问题:

  • 脏读
    脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是最终可能不会存到数据库中,也就是不存在的数据。读到了并不一定最终存在的数据,这就是脏读。

  • 可重复读
    可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任何时刻读到的同一批数据都是一致的。通常针对数据更新操作。

  • 不可重复读
    对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批事务并提交了,通常针对数据更新操作。

  • 幻读
    幻读是针对数据插入操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,这就是幻读。

  • 事务隔离级别 1.SQL标准定义了四种隔离级别,MySQL全都支持,这四种隔离级别分别是:
    1、读未提交
    2、读提交
    3、可重复读
    4、串行化
    从上往下,隔离强度逐渐增强,性能逐渐变差。可重复读是MySQL的默认级别。

归并排序

采用分治法,各层分治递归可以同时进行。一般用于对总体无序,但是对各子项相对有序的数列。

  • 基本思想
    分治在每一层递归上有三个步骤:

  • 分解:将n个元素分成两个含n/2个元素的子序列

  • 解决:用合并排序法对两个子序列递归地排序

  • 合并:合并两个已排序的子序列得到排序结果