您现在的位置是:首页 >其他 >八股+面经网站首页其他
八股+面经
文章目录
项目介绍
1.不动产项目
数据
机器学习算法调研
12种基学习器,评价指标为RMSE、MAE、MAPE和R2,最终选定Catboost和LightGBM
图像提取算法调研
数据集-ImageNet
Xception
论文:Xception: Deep Learning with Depthwise Separable Convolutions (CVPR 2017)
源码:Keras开源代码
VGG
Very Deep Convolutional Networks for Large-Scale Image Recognition
Inception
Densenet
Mobilenet
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ldVLL0PH-1684247644028)(C:UsersHPAppDataRoamingTypora ypora-user-imagesimage-20230419190536360.png)]
2.图书项目
JPA和Mybatis区别
技术栈
前端服务器Nginx,后端服务器Tomcat,开发前端内容时,可以把前端的请求通过前端服务器转发给后端(称为反向代理)
用户信息明文存储在数据库中,不安全
shiro
三大概念
- Subject:负责存储与修改当前用户的信息和状态
- SecurityManager:安全相关的操作实际上是由她管理的
- Realms:负责从数据源中获取数据并加工后传给 SecurityManager
四大功能
- Authentication(认证)
- Authorization(授权)
- Session Management(会话管理)
- Cryptography(加密)
Mybatis
面试问题
-
Spring Security和Shiro的区别?
- Shiro比Spring更容易使用,实现和理解
- Shiro 功能强大、且 简单、灵活
- Shiro是 Apache 下的项目,比较可靠,不跟任何的框架或者容器绑定,可以独立运行
-
项目中redis用什么客户端部署?
-
Java 访问 Redis 主要是通过 Jedis 和 Lettuce 两种由不同团队开发的客户端(提供访问、操作所需的 API),Jedis 比较原生,Lettuce 提供的能力更加全面。
-
本项目用Spring Data Redis,Spring Data Redis是在 Lettuce 的基础上做了一些封装,与 Spring 生态更加贴合,使用起来也更简便。
-
-
java怎么连接数据库?
配置maven依赖->配置数据库(application.properties)
延时双删:先清除缓存,在更新数据库后,等一段时间,再去第二次执行删除操作。
Java基础
基本数据类型
反射
反射就是把java类中的各种成分映射成一个个的Java对象
Spring/Spring Boot、MyBatis 这些框架中大量使用了动态代理,而动态代理的实现也依赖反射。
接口和抽象类
共同点 :
- 都不能被实例化。
- 都可以包含抽象方法。
- 都可以有默认实现的方法(default关键字)
区别 :
- 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
- 一个类只能继承一个类,但是可以实现多个接口。
- 接口中的成员变量只能是
public static final
类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
Map
HashMap
HashMap v.s Hashtable(5点)
ConcurrentHashMap v.s Hashtable(2点)
- 底层数据结构不同
- 实现线程安全方式不同
代理模式
1. 静态代理
针对每个目标类都单独创建一个代理类
2. 动态代理
从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
Spring AOP、RPC框架的实现依赖动态代理
-
2.1 JDK 动态代理机制
InvocationHandler
接口和Proxy
类是核心
-
2.2 CGLIB 动态代理机制
- JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
- 在 CGLIB 动态代理机制中
MethodInterceptor
接口和Enhancer
类是核心。
-
2.3 二者对比
- 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
- JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
Java并发
JMM(Java内存模型)
-
什么是JMM?
- Java 定义的并发编程相关的一组规范
- 除了抽象了线程和主内存之间的关系之外,其还规定了从 Java 源代码到 CPU 可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范
- 主要目的是为了简化多线程编程,增强程序可移植性的。
-
为啥需要JMM?
- 并发编程下,CPU多级缓存和指令重排会导致程序运行出现一些问题,JMM定义一些规范解决这些问题。
- JMM 屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果。
-
Java内存区域和 JMM 有什么区别?
- JVM 内存结构:和 Java 虚拟机的运行时区域相关,定义了 JVM 在运行时如何分区存储程序数据,就比如说堆主要用于存放对象实例。
- Java 内存模型:和 Java 的并发编程相关,抽象了线程和主内存之间的关系就比如说线程之间的共享变量必须存储在主内存中,规定了从 Java 源代码到 CPU 可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范,其主要目的是为了简化多线程编程,增强程序可移植性的。
-
JMM如何抽象线程和主内存之间的关系?
-
Java内存模型(不仅仅是JVM内存分区):调用栈和本地变量存放在线程栈上,对象存放在堆上。
-
主内存 & 本地内存
- 主内存:所有线程创建的实例对象都放主内存
- 本地内存:每个线程都有一个本地内存存储共享变量的副本(本地内存时JMM抽象出来的概念)
-
JMM示意图:每个线程有个本地内存放副本,共享变量放主内存中
-
从示意图看,线程1和2咋通信?
- 线程1:本地内存中修改过的共享变量副本值–(同步)–>主内存
- 线程2:到主内存中读取对应共享变量的值
-
多线程下,可能出现的线程安全问题?
- when 线程1修改共享变量,线程2读取同一个共享变量,线程2读取的是修改前的值还是修改后的?
- 不确定!因为线程1和2都是先将共享变量 主内存–(拷贝)–>对应线程工作内存
-
So,JMM定义了8种同步操作&一些同步规则,规定一个变量如何从工作内存同步到主内存
- 同步操作:lock、unlock、read、load、use、assign、store、write
-
-
happens-before 原则
-
程序员追求:易于理解和编程的强内存模型;编译器和处理器追求:较少约束的弱内存模型
-
happens-before 设计思想
- 编译器和处理器的约束尽可能少->只要不改变程序的执行结果,编译器和处理器怎么进行重排序都行*(比如两个赋值语句)*
- 对改变程序执行结果的重排序,编译器和处理器必须禁止*(比如赋完值以后再用这个值)*
-
和 JMM 的关系
-
-
并发编程三个重要特性
-
原子性
含义:一次操作或者多次操作,要么所有的操作全部都得到执行并且不会受到任何因素的干扰而中断,要么都不执行。
Java实现:
synchronized
、各种Lock
以及各种原子类synchronized
和各种Lock
可以保证任一时刻只有一个线程访问该代码块,因此可以保障原子性。各种原子类是利用 CAS (compare and swap) 操作(可能也会用到volatile
或者final
关键字)来保证原子操作。 -
可见性
含义:一个线程对共享变量进行修改,另外的线程立即可以看到修改后的最新值。
Java实现:
synchronized
、volatile
以及各种Lock
-
有序性
含义:代码的执行顺序未必就是编写代码时候的顺序。
Java实现:
volatile
关键字可以禁止指令进行重排序优化
线程
- 什么是线程?
- 比进程更小的执行单位
- 线程切换工作时负担比进程小得多
- 进程和线程的区别
- 进程可以有多个线程,同类的多个线程共享进程的堆和方法区资源,每个线程有自己的程序计数器、虚拟机栈和本地方法栈
- 各进程是独立的,同一进程中的线程可能会互相影响
- 线程执行开销小,但不利于资源的管理和保护;进程相反
- 为啥程序计数器是私有的?
- 为了线程切换后能恢复到正确的执行位置
- 为啥虚拟机栈和本地方法栈私有?
- 为了保证线程中的局部变量不被别的线程访问到
- 堆和方法区了解
- 堆是进程中最大的一块内存,主要用于存放新创建的对象
创建线程的三种方式
-
1、继承Thread类
- 重写run方法,start()启动
class MyThread extends Thread{ @Override public void run(){ System.out.println("这是重写的run方法,也叫执行体"); System.out.println("线程号:" + currentThread().getName()); } } public class Test{ public static void main(String[] args) throws Exception{ Thread t1 = new MyThread(); t1.start(); } }
- 优点:简单,访问当前现线程直接使用currentThread()
- 缺点:继承Thread类,无法继承其他类
-
2、实现Runable接口
class MyThread implements Runable{ @Override public void run(){ System.out.println("这是重写的run方法,也叫执行体"); System.out.println("线程号:" + Thread.currentThread().getName()); } } public class Test{ public static void main(String[] args) throws Exception{ MyThread myThread = new MyThread(); Thread t1 = new Thread(myThread); t1.start(); } }
- 优点:可以继承别的类,多个线程共享一个对象,适合处理同一份资源的情况
- 缺点:访问当前线程需要使用Thread.currentThread()
-
3、Callable接口:
- 实现Callble接口,重写call()方法,作为执行体;
- 创建实现类的实例,用FutureTask包装;
- 使用FutureTask对象作为Thread对象创建线程;
- 使用FutureTask对象的get()方法获得子线程执行结束后的返回值
class MyThread implements Callable{ @Override public Object call() throws Exception{ System.out.println("线程号:" + Thread.currentThread().getName()); return 10; } } public class Test{ public static void main(String[] args) throws Exception{ Callable callable = new MyThread(); FutureTask task = new FutureTask(callable); new Thread(task).start(); System.out.println(task.get()); Thread.sleep(10);//等待线程执行结束 //task.get() 获取call()的返回值。若调用时call()方法未返回,则阻塞线程等待返回值 //get的传入参数为等待时间,超时抛出超时异常;传入参数为空时,则不设超时,一直等待 System.out.println(task.get(100L, TimeUnit.MILLSECONDS)); } }
-
Runnable和Callable的区别:
- Callable规定的方法是call(),Runnable规定的方法是run().
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
- call方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
-
start()与run()的区别:
- start用于启动线程,线程处于就绪状态,直到得到CPU时间片才运行,再自动执行run方法;
- run方法只是类的普通方法,直接调用相当于只有主线程一个线程;
-
sleep() 与 wait() 与 join() 与 yield():
- 作用:sleep()与wait()都可以暂停线程执行
- sleep:线程让出CPU资源,不会释放锁;
- wait:线程让出CPU, 释放锁;与notify(), notifyAll() 一起使用
- 基于哨兵思想:检查特定条件直到满足,才继续进行
- 需要监视器监视当前线程:如synchronized, Condition类
- notify:随机唤醒单个线程
- notifyAll:唤醒所有线程
- 基于哨兵思想:检查特定条件直到满足,才继续进行
- yield:暂停当前线程,给其他具有相同优先级的线程(包括自己)运行的机会;
- join:让主线程等待子线程结束之后在结束; 比如需要子线程的运行结果的时候,由子线程调用;
- sleep 和 yield 是Thread的静态方法; join是线程对象调用; wait, notify, notifyAll 是Object类的方法,所有对象都可以调用。
- 作用:sleep()与wait()都可以暂停线程执行
多线程
- 为什么使用多线程?
- 总体上
- 计算机底层:线程切换调度成本小于进程;多核CPU->多个线程可以同时运行,减少上下文切换开销
- 互连网发展趋势:多线程是高并发系统的基础,可以提高系统整体的并发能力和性能
- 计算机底层
- 单核时代:提高单进程利用CPU和IO系统的效率,一个线程IO阻塞,其他线程还能用CPU
- 多核时代:提高进程利用多核CPU的能力
- 总体上
- 多线程带来的问题?
- 内存泄露、死锁、线程不安全
生命周期&状态
六个状态:
-
NEW: 初始状态,线程被创建出来但没有被调用
start()
。 -
RUNNABLE: 运行状态,线程被调用了
start()
等待运行的状态。 -
BLOCKED :阻塞状态,需要等待锁释放。
-
WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
-
TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
-
TERMINATED:终止状态,表示该线程已经运行完毕。
上下文切换
- 什么是上下文?
- 线程执行中自己的运行条件和状态(比如程序计数器、栈信息
- 为什么会上下文切换?
- 主动出让CPU
- 时间片用完
- 调用了阻塞类型的系统中断
- 被终止或结束运行
- 什么是上下文切换?
- 线程切换,意味着要保存当前线程的上下文,留着线程下次占用CPU的时候恢复现场,并加载下一个将要占用CPU的线程上下文,所以要上下文切换
- 切换的时候干啥?
- 每次要保存信息恢复信息,占用CPU,所以不能频繁切换
sleep()和wait()
- 二者异同
- 为什么wait()不定义在Thread中?sleep()定义在Thread中?
wait()
是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。- 每个对象(Object)都拥有对象锁
- 既然要释放当前线程占有的对象锁并让其进入WAITING状态,操作的对象自然是
Object
,而不是当前线程Thread
sleep()
是让当前线程暂停执行,不涉及到对象类,也不需要获得对象锁
- 可以直接调用Theard类的run方法吗?
- 调用
start()
方法方可启动线程并使线程进入就绪状态,直接执行run()
方法的话不会以多线程的方式执行。
- 调用
volatile
-
如何保证变量的可见性?
- 用 **
volatile
**关键字 - 如果我们将变量声明为
volatile
,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。 volatile
关键字能保证数据的可见性,但不能保证数据的原子性。synchronized
关键字两者都能保证。
- 用 **
-
如何禁止指令重排序?
-
用 **
volatile
**关键字 -
如果我们将变量声明为
volatile
,在对这个变量进行读写操作的时候,会通过插入特定的 内存屏障 的方式来禁止指令重排序。 -
volatile使用场景:双重校验锁实现对象单例(线程安全)
-
public class Singleton { private volatile static Singleton uniqueInstance;//volatile修饰! private Singleton() {} public static Singleton getUniqueInstance() { if (uniqueInstance == null) {//没有实例化过才进入加锁代码 synchronized (Singleton.class) {//类对象加锁 if (uniqueInstance == null) { /*这句话分三步进行: 1.为 uniqueInstance 分配内存空间 2.初始化 uniqueInstance 3.将 uniqueInstance 指向分配的内存地址 JVM指令重排,可能变成1->3->2,因此需要volatile*/ uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
-
-
-
volatile 可以保证原子性么?
- 不能,只能保证可见性和有序性
- 怎么改进?
synchronized
、Lock
或者AtomicInteger
乐观锁/悲观锁
1. 悲观锁
- 假设最坏的情况,认为共享资源每次被访问的时候就会出现问题
- 每次获取资源都会上锁
- 共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。
- 实现:
synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现 - 导致的问题
- 高并发场景下,锁竞争->线程阻塞->大量->系统上下文切换->增加系统开销
- 死锁
2. 乐观锁
- 假设最好的情况,认为共享资源每次被访问的时候都不会出现问题
- 只有提交修改的时候验证对应资源是否被其他线程修改了(版本号 or CAS)
- 实现:
java.util.concurrent.atomic
包下面的原子变量类(比如AtomicInteger
、LongAdder
) - 实现方法:
- 版本号机制
- 数据表中加上一个数据版本号
version
字段,表示数据被修改的次数 - 数据被修改时,version值+1
- 当线程 A 要更新数据值时,在读取数据的同时也会读取
version
值,在提交更新时,若刚才读取到的 version 值为当前数据库中的version
值相等时才更新,否则重试更新操作,直到更新成功。
- 数据表中加上一个数据版本号
- CAS(比较与交换)
- 思想:用一个预期值和要更新的变量值进行比较,两值相等才会进行更新
- 三个操作数
- V :要更新的变量值(Var)
- E :预期值(Expected)
- N :拟写入的新值(New)
- 当且仅当V==E时,CAS通过原子方式用N更新V;
- V!=E时,说明有其它线程更新了V,则当前线程放弃更新。
- 版本号机制
- 问题:
- ABA问题
- 描述:变量V刚开始是A值,最后也是A值,能证明其它线程没改过它吗?不能!这就是ABA问题
- 解决:变量前面追加版本号或者时间戳
- 循环时间长开销大
- 描述:CAS会用到自旋进行重试,不成功就一直循环执行直到成功;长时间不成功,会给CPU带来非常大的时间开销
- 解决:JVM支持的pause命令,作用有两个
- 延迟流水线执行指令,使CPU不会消耗过多的执行资源
- 避免退出循环时因内存顺序引起CPU流水线被清空,提高CPU效率
- 只能保证一个共享变量的原子操作
- 描述:CAS只对单个共享变量有效,跨多个共享变量时无效
- 解决:JDK1.5之后,提供了
AtomicReference
类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行 CAS 操作
- ABA问题
synchronized
synchronized
主要解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
1. 使用场景
-
1. 修饰实例方法(锁当前对象实例)
-
synchronized void method() { //业务代码 }
-
-
2. 修饰静态方法(锁当前类)
-
synchronized static void method() { //业务代码 }
-
-
3. 修饰代码块(锁指定对象/类)
-
synchronized(this) { //业务代码 }
-
-
注意:构造方法不能用synchronized修饰!(构造方法本身就线程安全)
2. 底层原理
底层依赖于JVM
-
synchronized 同步语句块
-
synchronized
同步语句块的实现使用的是monitorenter
和monitorexit
指令,其中monitorenter
指令指向同步代码块的开始位置,monitorexit
指令则指明同步代码块的结束位置。 -
在执行**
monitorenter
**时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。 -
对象锁的的拥有者线程才可以执行
monitorexit
指令来释放锁。在执行monitorexit
指令后,将锁计数器设为 0,表明锁被释放,其他线程可以尝试获取锁。 -
如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
-
-
synchronized 修饰方法
-
synchronized
修饰的方法并没有monitorenter
指令和monitorexit
指令,取得代之的确实是ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法。JVM 通过该ACC_SYNCHRONIZED
访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 -
如果是实例方法,JVM 会尝试获取实例对象的锁。如果是静态方法,JVM 会尝试获取当前 class 的锁。
-
-
同步语句块用
monitorenter
指令和monitorexit
指令,修饰方法用**ACC_SYNCHRONIZED
** 标识,不过两者的本质都是对对象监视器 monitor 的获取
3. 和volatile区别
synchronized
和 volatile
是互补的存在,区别有四点:
- 性能:
volatile
关键字是线程同步的轻量级实现,性能比synchronized好 - 使用:
volatile
只能用于变量,synchronized
可以修饰方法以及代码块 - 并发特性:
volatile
能保证数据的可见性,但不能保证数据的原子性。synchronized
关键字两者都能保证 - 场景:
volatile
主要用于解决变量在多个线程之间的可见性,而synchronized
关键字解决的是多个线程之间访问资源的同步性
ReentrantLock
ReentrantLock
实现了Lock
接口,是一个可重入且独占式的锁- 底层:AQS(
AbstractQueuedSynchronizer
)ReentrantLock
里面有一个内部类Sync
,Sync
继承 AQS,添加锁和释放锁的大部分操作实际上都是在Sync
中实现的Sync
有公平锁FairSync
和非公平锁NonfairSync
两个子类- 公平锁:先等待的线程先获得锁
- 非公平锁:随机线程获得锁,性能更好,但可能某些线程永远无法获得锁(
ReentrantLock
默认)
和synchronized区别
-
都是可重入锁(相同点)
-
可重入锁也叫递归锁,指的是线程可以再次获取自己的内部锁
-
JDK所有现成的
Lock
实现类,包括synchronized
关键字锁都是可重入的
-
-
依赖对象不同
-
synchronized 依赖于 JVM
-
ReentrantLock 依赖于 API(需要lock()和unlock()方法配合try/finally语句块完成)
-
-
ReentrantLock功能更多
ReentrantLock
增加了三点高级功能:- 等待可中断:正在等待的线程可以选择放弃等待,处理其他事情
- 可中断锁:获取锁的过程可以被中断,进行其他逻辑处理【ReentrantLock】
- 不可中断锁:一旦线程申请了锁,就只能等到拿到锁以后才能进行其他的逻辑处理【synchronized】
- 可实现公平锁:
ReentrantLock
可以指定是公平锁还是非公平锁(默认非公平)synchronized
只能是非公平锁
- 可实现选择性通知(锁可以绑定多个条件):
synchronized
关键字与wait()
和notify()
/notifyAll()
方法相结合可以实现等待/通知机制ReentrantLock
类需要借助于Condition
接口与newCondition()
方法实现
- 等待可中断:正在等待的线程可以选择放弃等待,处理其他事情
Future
- 作用:
AQS
AQS就是一个抽象类,翻译过来就是抽象队列同步器
使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLock
,Semaphore
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
}
1. 原理
核心思想:
-
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
-
如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,
这个机制 AQS 是用 CLH 队列锁 实现的,即将暂时获取不到锁的线程加入到队列中。
- CLH队列是一个虚拟的双向队列
- AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配
- 在 CLH 同步队列中,一个节点表示一个线程
AQS 使用 int 成员变量 state
表示同步状态,通过内置的 线程等待队列 来完成获取资源线程的排队工作
// 共享变量,使用volatile修饰保证线程可见性
private volatile int state;
2. Semaphore作用
-
作用:
Semaphore
(信号量)可以用来控制同时访问特定资源的线程数量(想OS中咋用的) -
// 初始共享资源数量 final Semaphore semaphore = new Semaphore(5); // 获取1个许可 semaphore.acquire(); // 释放1个许可 semaphore.release();
当初始的资源个数为 1 的时候,Semaphore
退化为排他锁。
-
Semaphore 两种模式:
-
公平模式: 调用
acquire()
方法的顺序就是获取许可证的顺序,遵循 FIFO; -
非公平模式: 抢占式的。
-
-
应用场景:
Semaphore
通常用于那些资源有明确访问数量限制的场景比如限流
3. Semaphore原理
-
Semaphore
是共享锁的一种实现,它默认构造 AQS 的state
值为permits
-
调用
semaphore.acquire()
:相当于OS中P(S),state>=0表示可以获取成功,使用CAS操作state减1;state<0表示数量不足,创建Node结点加入阻塞队列,挂起当前线程 -
调用
semaphore.release()
:相当于OS中V(S),使用CAS操作state+1,释放许可证成功后,唤醒同步队列中的一个线程。被唤醒的线程重新尝试state-1,如果state>=0则获取令牌成功,否则重新进入阻塞队列,挂起线程
4. CountDownLatch
- 作用:允许count个线程阻塞在一个地方,直到所有线程任务执行完毕;一次性的,计数器的值只能在构造方法中初始化一次,使用完毕后不能再次使用
- 原理:共享锁的一种实现,默认构造AQS的state值为count
- 使用countDown()方法:CAS操作减少state,直到state为0
- 使用await()方法:
- state不为0,说明任务还没执行完毕,await()一直阻塞,之后的语句不会被执行
CountDownLatch
会自旋 CAS 判断state == 0
,是的话释放所有线程,await()之后的语句执行
- 场景:多线程读取多个文件处理,具体如下:
- 读取处理6个文件,没有执行顺序依赖,但返回给用户时要将几个文件的处理结果进行统计整理
- 定义:线程池+count为6的
CountDownLatch
对象 - 线程池读取任务,处理完后count-1,调用
CountDownLatch
对象的await()
方法,所有文件读取完后截止执行后面的逻辑
线程池
线程池就是管理一系列线程的资源池,有任务处理->线程池拿线程->处理完不销毁->线程等待下一个任务
1. 线程池作用
提供限制和管理资源的方式 && 维护一些基本统计信息
- **降低资源消耗。**重复利用已创建的线程降低创建销毁的消耗。
- **提高响应速度。**任务到达时,任务不需要等到线程创建就能立即执行。
- **提高线程可管理性。**线程池统一分配、调优和监控线程。
2. 创建线程池方法
ThreadPoolExecutor
构造函数Executor
框架的工具类Executors
3. 线程池饱和策略
线程达到最大数量,队列也被放满了任务,ThreadPoolExecutor.
定义的策略:
.AbortPolicy
:抛出异常拒绝新任务的处理。.CallerRunsPolicy
:调用自己的线程运行任务。.DiscardPolicy
:不处理新任务,直接丢弃。.DiscardOldestPolicy
:丢弃最老未处理的任务请求。
4. 常用阻塞队列
新任务到来->当前运行线程数==核心线程数?->√->新任务放队列
不同线程池用不同的阻塞队列
LinkedBlockingQueue
(无界队列):队列永远不会被放满SynchronousQueue
(同步队列):没有容量,不存储元素。DelayedWorkQueue
(延迟阻塞队列):内部采用“堆”
5. 线程池处理任务流程
6. 线程池大小
-
太大->增加上下文切换成本
-
太小->大量请求/任务堆积
-
CPU密集型任务(N+1):线程数设置为 N(CPU核心数)+1
-
I/O密集型任务(2N)
Future
- 核心思想:异步调用,主要用在多线程领域
- 作用:用在执行耗时任务的场景,避免程序一直原地等待耗时任务执行完成,执行效率太低
- 功能:在java中是个泛型接口,包含下面四个功能
- 取消任务;
- 判断任务是否被取消;
- 判断任务是否已经执行完成;
- 获取任务执行结果。
(简单理解就是:我有一个任务,提交给了 Future
来处理。任务执行期间我自己可以去做任何想做的事情。并且,在这期间我还可以取消任务以及获取任务的执行状态。一段时间之后,我就可以 Future
那里直接取出任务执行结果。)
-
Callable和Future的关系
-
FutureTask
提供了Future
接口的基本实现,常用来封装Callable
和Runnable
-
//FutureTask 有两个构造函数,可传入Callable或者Runnable对象 public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; } public FutureTask(Runnable runnable, V result) { // 通过适配器RunnableAdapter来将Runnable对象runnable转换成Callable对象 this.callable = Executors.callable(runnable, result); this.state = NEW; }
-
JVM
内存结构
运行时数据区域
JDK1.7运行时数据区域:线程共享 堆、方法区、直接内存;线程私有程序计数器、虚拟机栈、本地方法栈
JDK1.8运行时数据区域:
线程私有:程序计数器+俩栈
-
程序计数器:很小的内存空间,当前线程的行号指示器
- 作用:
- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如顺序、选择、循环
- 多线程下,程序计数器用于记录当前线程执行的位置
- 注意:程序计数器是唯一一个不会出现
OutOfMemoryError
的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
- 作用:
-
虚拟机栈:保存方法的局部变量、操作数、动态链接、方法返回地址
- 单位:栈帧,栈帧随着方法调用而创建,随着方法结束而销毁。无论方法正常完成还是异常完成都算作方法结束。
- 程序运行中栈的两种错误:
StackOverFlowError
:栈内存大小不允许动态扩展,线程请求栈深度>当前 JVM 栈最大深度,抛出OutOfMemoryError
:栈内存大小允许动态扩展,JVM 动态扩展栈时无法申请到足够的内存空间,抛出
-
本地方法栈
- 和虚拟机栈的区别:
- 虚拟机栈为虚拟机执行 Java 方法(字节码)服务
- 本地方法栈为虚拟机使用到的 Native 方法服务
- 本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
- 和虚拟机栈的区别:
线程共享:堆+方法区+直接内存
-
堆:运行时数据区域,保存所有类的实例和数组
-
JVM管理内存中最大的一块;所有线程共享的一块内存区域
-
垃圾收集器管理的主要区域,因此也被称作 GC 堆
-
堆内存结构:新生代(Eden,s0,s1)、老年代(tenured)、永久代/元空间(元空间使用本地内存)
- 对象都会首先在 Eden 区域分配
- 在一次新生代垃圾回收后,如果对象还存活,则会进入 S0 或者 S1,并且对象的年龄还会加 1
- 当它的年龄增加到阈值(默认为 15 岁,
-XX:MaxTenuringThreshold
设置),就会被晋升到老年代中
-
堆容易出现的
OutOfMemoryError
错误:java.lang.OutOfMemoryError: GC Overhead Limit Exceeded
:JVM花太多时间垃圾回收,只能回收很少的堆空间java.lang.OutOfMemoryError: Java heap space
:堆内存空间不足以存放新创建的对象
-
字符串常量池
-
作用:避免字符串重复创建,是JVM为提升性能+减少内存消耗 为String类专门开辟的一块区域
-
// 在堆中创建字符串对象”ab“ // 将字符串对象”ab“的引用保存在字符串常量池中 String aa = "ab"; // 直接返回字符串常量池中字符串对象”ab“的引用 String bb = "ab"; System.out.println(aa==bb);// true!!!
-
存放位置:JDK1.7之前-永久代;1.7之后-字符串常量池和静态变量移动到堆中
-
-
-
方法区:存储加载的类信息、class/method/field等元数据、常量、静态变量
-
直接内存(非运行时数据区域)
- 特殊的内存缓冲区,并不在java堆或方法区中分配,在本地内存上分配
- 不是虚拟机运行时数据区域,也不是虚拟机规范中定义的内存区域,但也被频繁使用
HotSpot虚拟机对象
1. 对象创建
- 类加载检查
- 检查这个指令的参数是否能在常量池中定位到这个类的符号引用
- 检查这个符号引用代表的类是否已被加载过、解析和初始化过
- 如果没有,那必须先执行相应的类加载过程。
- 分配内存
- 在类加载检查通过后,接下来虚拟机将为新生对象分配内存。
- 对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。
- 分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
- 内存分配的两种方式:
- 指针碰撞:
- 适用场合:堆内存规整(即没有内存碎片)的情况下。
- 原理:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界指针,只需要向着没用过的内存方向将该指针移动对象内存大小位置即可。
- 使用该分配方式的 GC 收集器:Serial, ParNew
- 空闲列表:
- 适用场合:堆内存不规整的情况下。
- 原理:虚拟机会维护一个列表,该列表中会记录哪些内存块是可用的,在分配的时候,找一块儿足够大的内存块儿来划分给对象实例,最后更新列表记录。
- 使用该分配方式的 GC 收集器:CMS
- 指针碰撞:
- 初始化零值
- 设置对象头
- 执行init方法
2. 对象的内存布局
3. 对象的访问定位
类加载机制
- 类加载器:通过类的全限定性类名(带包路径,如java.lang.String)获取该类的二进制字节流
- Bootstrap:加载java核心库(如$JAVA_HOME/jre/lib/rt.jar)
- 好处:不需要太多的安全检查,提升性能
- Extension:加载拓展库($JAVA_HOME/jre/lib/ext/*.jar)
- AppClass:根据java的类路径加载类,一般的java应用的类($CLASSPATH)
- 用户自定义:
- Bootstrap:加载java核心库(如$JAVA_HOME/jre/lib/rt.jar)
- 加载方式:双亲委派机制
- 过程:
- 收到类加载请求,不会自己加载,而是逐级向上委托,最终到达顶层的Bootstrap;
- 如果父类加载器完成,则返回;否则,交给子类尝试。
- 好处:
- 避免类的重复加载:java类由于加载机制,形成了优先级的层次关系
- 防止核心类被篡改:防止下层的同名类篡改核心API库
- 打破这个机制:
- 意义:由于类加载器受到加载范围的限制,某些情况下父类加载器无法加载需要的类(不属于其加载范围,但根据机制只能其加载),所以需要子类加载器加载。
- 方法:使用自定义加载器
- 如何:
- 1、找到ClassLoader类的loadClass()方法,拷贝
- 2、写自定义加载器类,粘贴
- 3、删去loadClass()中的双亲委派机制的部分
- 4、判断类是否为系统类,是的话使用双亲委派机制,否则自己的方法加载。
- 应用:
- Tomcat:webappClassLoader加载自己目录下的class文件,目的;1、隔离每个webapp下的class和lib;2、使用单独的加载器以免被恶意破坏;3、热部署(修改文件不用重启自动重新装载)
- 过程:
垃圾回收(GC)
1. 引用类型
2. 垃圾收集
-
垃圾:不再被使用的对象,死亡的对象
-
哪些垃圾需要回收?
-
引用计数法:每个对象添加一个引用计数器,当为0时,就表示死亡;
- 问题:循环引用,不可回收造成内存泄露;空间占用
-
可达性分析算法:以根对象集合(GC Roots)为起点,分析GC Roots连接的对象是否可达,解决了循环引用问题。
-
GC Roots:就是对象!
- 1、虚拟机栈中的栈帧中的局部变量所引用的对象;
- 2、java本地方法栈中(java本地接口)引用的对象;
- 3、方法区中静态变量引用的对象;(全局对象)
- 4、方法区中常量引用的对象(全局对象)
-
问题:多线程下更新了访问过的对象的引用
-
误报:原被引用的对象不再被引用。影响较小,顶多减少GC次数。
-
漏报:将已被访问过的对象设置为未被访问过。影响较大,可能会使引用对象被GC,导致jvm崩溃。
- 解决方法:Stop-the-world,等所有线程到达安全点,再进行Stop-the-world。
- Stop-the-world:用户运行至安全点(safe point)或者安全区域之后,就会挂起,进入暂停状态。
- 解决方法:Stop-the-world,等所有线程到达安全点,再进行Stop-the-world。
-
-
-
-
什么时候回收?
- Minor GC:young gc,发生在年轻代的gc
- Major GC:olg gc,发生在老年代的gc
- **Full GC:**回收整个堆的内存
- 触发条件:
- System.gc()
- 通过Minor GC进入老年代的平均大小 > 老年代的可用内存
- 老年代空间不足;
- 触发条件:
3. 垃圾回收算法(内存回收方法论)
4. 垃圾收集器(内存回收具体实现)
-
Serial:
-
单线程,所有线程stw,
-
新生代标记-复制,老年代标记-整理
-
缺点:需要停止所有工作线程,效率低
-
场景:对应用的实时性要求不高的client级别(桌面应用)的默认方式,单核服务器
-
-
ParNew:
-
Serial的多线程版本,stw, 复制算法
-
新生代标记-复制,老年代标记-整理
-
实际线程默认和cpu数量相同
-
优点:有效利用cpu
-
缺点:和Serial一样
-
场景:Sever模式下的新生代,和CMS配合
-
-
Parallel Scavenge:
-
新生代收集器、复制算法,多线程
-
与ParNew不同点:追求和精确控制高吞吐量,而ParNew尽可能缩短用户线程的停顿时间;
- 高吞吐:线程运行时间/线程运行时间 + 垃圾回收时间
- 例子:PS就是100秒回收一次垃圾,持续10秒,吞吐量为90%;PN就是50秒回收一次垃圾,持续7秒,吞吐量为86%。
-
场景:注重高效利用CPU
-
Serial Old
-
Serial的老年代版本,标记整理算法
-
场景:client、单核。与PS收集器搭配
-
-
Parallel Old
-
Parallel Scavenge的老年代版本,多线程,标记整理算法
-
JDK7,8默认老年代收集器
-
-
CMS
-
Concurrent Mark Sweep
-
多线程、标记清除算法
-
特点:获取最短回收停顿时间
-
- 流程:
- 1、初始标记:GC Roots直接关联的对象,需要Stw
- 2、并发标记:与用户线程并发,不需要Stw,执行Gc Roots Tracing
- 3、重新标记:修正并发标记期间,发生变动的一些标记,需要Stw
- 4、并发清除:标记清除
- 优点:并发收集,低停顿
- 缺点:
- 对CPU资源敏感
- 标记清除产生空间碎片
- 并发清除过程中会产生新的垃圾,只能等下一次
-
G1(Garbage-First)
-
标记整理 + 复制
-
特点:
- 并行与并发
- 分代收集:自己采用不同的收集方式去处理不同代的垃圾,不需要和其他收集器合作
- 空间整合:无空间碎片
- 可预测的停顿:可预测时间的停顿模型
-
原理:
-
将java堆分为大小相同的独立区域Region,新生代和老年代区域混合;
- 并发标记:知道哪些块基本是垃圾,就会从这些块下手(所以叫G1);
- 停顿预测模型:
- 根据之前垃圾回收的数据统计,估计出用户指定停顿时间内的回收块个数;
- “尽力”满足指定的目标停顿时间,基于目标选择回收的区块数量;
-
-
-
-
步骤;
-
1、Minor GC
- 复制算法、并行、stw
- 动态调整年轻区大小:根据历史数据,和用户指定的停顿时间目标
- 2、老年代收集:
- 初始标记:stw,伴随young gc,对Gc Roots直接关联对象进行标记
- 扫描根引用区:即survivor区的对象直接到老年代对象的引用。(因为进行了YGC,新生代中只有survivor区存在对象)
- 并发标记:寻找整个堆的存活对象,并发执行,可以被YGC中断
- 如果整个region都是垃圾,直接被回收
- 标记的同时计算region活性比例
- 重新标记:stw,完成最后的标记,使用了STAB算法
- 由于用户线程同时进行,需要修正之前的标记
- 采用了比CMS更快的初始快照法:SATB算法
- 清理:stw,复制算法。计算各个region的存活对象和垃圾比例,排序,识别可以混合回收的区域 。
- 并发清理:识别并清理全是垃圾的老年代region
- 3、混合式收集:
- 除了整个年轻代收集, 还有上个阶段标记出的老年代的垃圾最多的区块;
- 持续回收所有被标记的分区
- 恢复到年轻代收集,进行新的周期
- 必要时的Full Gc:
- G1的初衷就是避免Full GC
-
总结
-
7种收集器关系
如果两个收集器之间存在连线,就说明它们可以搭配使用
- 收集器总结
收集器 | 串/并行/并发 | 新/老 | 收集算法 | 目标 | 场景 |
---|---|---|---|---|---|
Serial | 串行 | 新 | 复制 | 响应速度优先 | 单CPU环境下的Client模式 |
Serial Old | 串行 | 老 | 标记-整理 | 响应速度优先 | 单CPU环境下的Client模式、CMS的后备预案 |
ParNew | 并行 | 新 | 复制 | 响应速度优先 | 多CPU环境时在Server模式下与CMS配合 |
Parallel Scavenge | 并行 | 新 | 复制 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
Parallel Old | 并行 | 老 | 标记-整理 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
CMS | 并发 | 老 | 标记-清除 | 响应速度优先 | 集中在互联网站或B/S系统服务端上的Java应用 |
G1 | 并发 | both | 标记-整理+复制算法 | 响应速度优先 | 面向服务端应用,将来替换CMS |
设计模式
单例模式
-
懒汉式 - 线程不安全
public class Singleton{ private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
-
懒汉式 - 线程不安全
public class Singleton{ private static Singleton instance; private Singleton(){} public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
-
双重检验锁
public class Singleton{ private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ // single check synchornized(Singleton.class){ if(instance == null){ //double check instance = new Singleton(); } } } return instance; } }
-
饿汉式 - static final field
public class Singleton{ //类加载时就初始化 private static final Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; } }
-
静态内部类 - static nested class
public class Singleton{ private static class SingletonHolder(){ private static final Singleton instance = new Singleton(); } private Singleton(){} public static final Singleton getInstance(){ return SingletonHolder.instance; } }
-
枚举 - Enum
public class Singleton{ private Singleton(){} /** *枚举类型是线程安全的,并且只会装载一次 */ public enum SingletonEnum{ INSTANCE; private final Singleton instance; SingletonEnum(){ instance = new Singleton(); } private Singleton getInstance(){ return instance; } } public static Singleton getInstance(){ return SingletonEnum.INSTANCE.getInstance(); } }
工厂模式
工厂模式最主要解决的问题就是创建者和调用者的耦合,那么代码层面其实就是取消对new的使用。
-
简单工厂模式
也叫静态工厂模式
你要去买一台手机,你不用关心手机是怎么生产出来的,里面的零件具体又是怎么制造的,这些通通都交给工厂去处理,你尽管去买手机就好了。
问题:
随着手机品牌增多,工厂生产也需要对应的增加,工厂内部就需要不断的调整。
从代码层面——对内部代码需要增加(也就是需要修改内部代码:那么就会违反OOP原则—开闭原则:一个软件实体应当对扩展开放,对修改关闭 -
工厂方法模式
当新的手机品牌出现,不是放在同一个工厂生产,而是自己拥有独立工厂生产。那么就解决了上面静态工厂模式违反关闭原则的问题。
工厂方法模式解决简单工厂模式是需要付出代价的!
看到上图工厂方法模式图里新增用虚线画的Huawei品牌,每新增一个品牌就需要增加,对应新的工厂,会发现需要花费很大的成本,现在才三个新的品牌,那么等到十个、一百个的时候就会变得更加的复杂和难以维护。 -
抽象方法模式
在工厂方法模式中,一个具体的工厂负责生产一类具体的产品,即一对一的关系,但是,如果需要一个具体的工厂生产多种产品对象,那么就需要用到抽象工厂模式了。
- 简单工厂 : 使用一个工厂对象用来生产同一等级结构中的任意产品。(不支持拓展增加产品)
- 工厂方法 : 使用多个工厂对象用来生产同一等级结构中对应的固定产品。(支持拓展增加产品)
- 抽象工厂 : 使用多个工厂对象用来生产不同产品族的全部产品。(不支持拓展增加产品;支持增加产品族)
MySQL
存储引擎
-
MySQL 支持哪些存储引擎
- MySQL 5.5.5 前:默认MyISAM;后:默认InnoDB
- 只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。
-
存储引擎架构
- 插件式架构,支持多种存储引擎(不同数据库表可以设置不同搜索引擎)
- 存储引擎是基于表的,而不是数据库
-
MyISAM 和 InnoDB 区别(7点)
- MyISAM 性能还行,特性不错,但不支持事务和行级锁,最大的缺陷就是崩溃后无法安全恢复。
索引
常见的索引结构有: B 树, B+树 和 Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyIsam,都使用了 B+树作为索引结构。(相当于数据的目录)
1. 优缺点
优点 :
- 使用索引可以大大加快 数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
缺点 :
- 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
- 索引需要使用物理文件存储,也会耗费一定空间。
2. 底层数据结构
B&B+树
-
为啥不用哈希表做MySQL索引数据结构?
- Hash 索引不支持顺序和范围查询,并且,每次 IO 只能取一个。
- 试想,
SELECT * FROM tb1 WHERE id < 500;
,树直接遍历比 500 小的叶子节点就够了;哈希还要把1-499数据hash计算一遍来定位
-
B树和B+树区别?(3点)
-
B+树比B树的优势
1.单一节点存储更多的元素,使得查询的IO次数更少;
2.所有查询都要查找到叶子节点,查询性能稳定;
3.所有叶子节点形成有序链表,便于范围查询。
3. 索引类型总结
- 主键索引
- 二级索引
- 二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。
- 聚簇索引&非聚簇索引
- 覆盖索引
- 联合索引
事务
1. ACID
原子性(Atomicity
) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
一致性(Consistency
): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
隔离性(Isolation
): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
持久性(Durability
): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的!
InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?
- 持久性是通过 redo log (重做日志)来保证的;
- 原子性是通过 undo log(回滚日志) 来保证的;
- 隔离性是通过 MVCC 或锁机制来保证的;
- 一致性则是通过持久性+原子性+隔离性来保证;
2. 并发事务问题
-
脏读(Dirty read)
某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的
-
丢失修改(Lost to modify)
在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。
-
不可重复读(Unrepeatable read)
在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
-
幻读(Phantom read)
在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。
3. 并发事务控制方式
锁
悲观控制模式
- 共享锁(S 锁) :又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。
- 排他锁(X 锁) :又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条记录加任何类型的锁(锁不兼容)。
MVCC
乐观控制模式
多版本并发控制(MVCC,Multiversion concurrency control) 在 MySQL 中实现所依赖的手段主要是: 隐藏字段、read view、undo log。
- undo log : undo log 用于记录某行数据的多个版本的数据。
- read view 和 隐藏字段 : 用来判断当前版本数据的可见性。
4. 隔离级别
-
读取未提交(READ-UNCOMMITTED) : 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
-
读取已提交(READ-COMMITTED) : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
-
可重复读(REPEATABLE-READ) : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。(InnoDB 存储引擎默认隔离级别)
-
可串行化(SERIALIZABLE) : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
隔离级别是怎么实现的?
- SERIALIZABLE 隔离级别:锁
- READ-COMMITTED 和 REPEATABLE-READ 隔离级别:基于 MVCC
锁机制
行级锁、表级锁
MVCC
RC和RR下 MVCC 的差异
在事务隔离级别 RC
和 RR
(InnoDB 存储引擎的默认事务隔离级别)下,InnoDB
存储引擎使用 MVCC
(非锁定一致性读),但它们生成 Read View
的时机却不同
-
在 RC 隔离级别下的
每次select
查询前都生成一个Read View
(m_ids 列表) -
在 RR 隔离级别下只在事务开始后
第一次select
数据前生成一个Read View
(m_ids 列表)
Redis
为什么用redis做mysql缓存?
1、高性能
- MySQL从硬盘读取,慢。将用户缓存数据存在redis中,下次直接从缓存获取,相当于直接操作内存。
- 为啥这么快?
- 基于内存,访问速度是磁盘的上千倍
- 基于reactor模式设计开发了一套高效事件处理模型,主要是单线程事件循环和io多路复用
- 内置多种优化后的数据结构实现
2、 高并发
- 单台设备的Redis的QPS(Query Per Second,每秒钟处理完请求的次数) 是 MySQL 的10倍
- 直接访问 Redis 能够承受的请求远远大于直接访问 MySQL
数据结构
5 种基础数据结构 :String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
3 种特殊数据结构 :HyperLogLogs(基数统计)、Bitmap (位存储)、Geospatial (地理位置)。
线程模型
缓存读写策略
1. 旁路缓存模式
- 适用:读请求比较多的场景
- 服务端同时维系 db 和 cache,以 db 的结果为准
- 缓存读写步骤:
- 写:
- 先更新 db
- 然后直接删除 cache
- 读:
- 从 cache 中读取数据,读取到就直接返回
- cache 中读取不到的话,就从 db 中读取数据返回
- 再把数据放到 cache 中
- 写:
2. 读写穿透
- 适用:读请求比较多的场景
3. 异步缓存写入
- 适用:读请求比较多的场景
持久化
redis内存数据库,但会把缓存数据存到硬盘
->自带两种持久化技术:AOF日志&RDB快照
->redis默认开启RDB快照,重启redis,之前的缓存数据会被重新加载
AOF日志
RDB快照
- AOF 文件的内容是操作命令;
- RDB 文件的内容是二进制数据。
过期删除&内存淘汰策略
过期删除
计算机网络
HTTP
基本概念
-
是什么?超文本传输协议
-
超文本
- 超越普通文本的文本,可能是文字、图片、视频等混合体
- HTML是最常见的超文本
-
传输
- 双向协议:数据在A,B之间传输,但允许中间有中转或接力(A,B可能是浏览器和百度网站)
- HTTP 是一个在计算机世界里专门用来在两点之间传输数据的约定和规范。
-
协议
所以,HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」。
-
-
状态码
-
常见字段
- Host 字段:客户端发送请求时,用来指定服务器的域名
- Content-Length 字段:服务器返回的数据长度
- Connection 字段:长链接(Keep-Alive)or not
- Content-Type 字段:数据格式
- Content-Encoding 字段:服务器返回数据的压缩格式(gzip…)
GET和POST
-
区别
-
语义
-
GET :获取资源
-
POST :修改数据
-
-
安全性
-
幂等
-
HTTP与HTTPS
- 区别
- 传输安全:HTTP 是明文传输,存在安全风险的问题;HTTPS加密传输
- 连接方式:HTTP是TCP;HTTPS是TCP基础上进行SSL/TLS
- 端口号不同:HTTP 是 80;HTTPS是 443
- 数字证书:HTTPS需要向CA申请数字证书
- HTTPS解决了HTTP哪些问题?
- 信息加密:交互信息无法被窃取,但你的号会因为「自身忘记」账号而没。
- 校验机制:无法篡改通信内容,篡改了就不能正常显示,但百度「竞价排名」依然可以搜索垃圾广告。
- 身份证书:证明淘宝是真的淘宝网,但你的钱还是会因为「剁手」而没。
HTTP/1.0 和 HTTP/1.1 和 HTTP/2.0
-
HTTP/1.0
- 短连接
-
HTTP/1.1
- 支持长连接
- 同一个tcp连接中可以同时发送多个HTTP请求和响应
- 增加host字段,http/1.0认为一台服务器只有一个ip地址,而现在存在多个虚拟主机共享一个ip
- 新的状态码100(continue),表示客户端可以继续发送完整的请求(先发送一个不包含内容的请求试探)
-
HTTP/2.0
- 兼容HTTP/1.1
- 头部压缩
- 二进制格式
- 并发传输
- 服务器主动推送资源
三次握手(建立TCP连接)
一次握手:客户端发送带有 SYN(SEQ=x) 标志的数据包 -> 服务端,然后客户端进入 SYN_SEND 状态,等待服务器的确认;
二次握手:服务端发送带有 SYN+ACK(SEQ=y,ACK=x+1) 标志的数据包 –> 客户端,然后服务端进入 SYN_RECV 状态
三次握手:客户端发送带有 ACK(ACK=y+1) 标志的数据包 –> 服务端,然后客户端和服务器端都进入ESTABLISHED 状态,完成TCP三次握手。
四次挥手(断开TCP连接)
第一次挥手 :客户端发送一个 FIN(SEQ=X) 标志的数据包->服务端,用来关闭客户端到服务器的数据传送。然后,客户端进入 FIN-WAIT-1 状态。
第二次挥手 :服务器收到这个 FIN(SEQ=X) 标志的数据包,它发送一个 ACK (SEQ=X+1)标志的数据包->客户端 。然后,此时服务端进入CLOSE-WAIT状态,客户端进入FIN-WAIT-2状态。
第三次挥手 :服务端关闭与客户端的连接并发送一个 FIN (SEQ=y)标志的数据包->客户端请求关闭连接,然后,服务端进入LAST-ACK状态。
第四次挥手 :客户端发送 ACK (SEQ=y+1)标志的数据包->服务端并且进入TIME-WAIT状态,服务端在收到 ACK (SEQ=y+1)标志的数据包后进入 CLOSE 状态。此时,如果客户端等待 2MSL 后依然没有收到回复,就证明服务端已正常关闭,随后,客户端也可以关闭连接了。
TCP v.s UDP
- 七点不同:
-
UDP 一般用于即时通信,比如: 语音、 视频 、直播等等。
-
TCP 用于对传输准确性要求特别高的场景,比如文件传输、发送和接收邮件、远程登录等等。
DNS(域名IP映射)
应用层协议,基于UDP协议之上
-
两种查询解析模式
-
迭代
主机->本地DNS->根服务器->TLD DNS->权威DNS->本地DNS->主机
-
递归
-
操作系统
死锁
Spring
概述
- Spring4.x主要模块:
- 最重要:Spring-Core(主要提供 IoC 依赖注入功能的支持) 模块
- Data Access/Integration中spring-orm : 提供对 Hibernate、JPA 、iBatis 等 ORM 框架的支持。
-
Spring,Spring MVC,Spring Boot 之间什么关系?
-
Spring MVC是Spring中很重要的模块,核心思想是将业务逻辑、数据、显示分离来组织代码
- M:Model模型;V:View视图;C:Controller控制器
-
Spring Boot 简化了Spring配置,如果需要构建 MVC 架构的 Web 程序,还是需要使用 Spring MVC 作为 MVC 框架,只是说 Spring Boot 帮你简化了 Spring MVC 的很多配置,真正做到开箱即用!
-
IoC(控制反转)
IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。
为什么叫控制反转?
-
控制 :指的是对象创建(实例化、管理)的权力
-
反转 :控制权交给外部环境(Spring 框架、IoC 容器)
-
SpringIOC有两个核心思想就是IOC控制反转和DI依赖注入
-
IOC 控制反转的基本思想是,将原来的对象控制从使用者,有了spring之后可以把整个对象交给spring来帮我们进行管理。
-
DI 依赖注入,就是把对应的属性的值注入到具体的对象中。spring提供标签和@Autowired和@Resource注解等方式注入,注入方式本质上是AbstractAutowireCapableBeanFactory的populateBean() 方法先从beanDefinition 中取得设置的property值,*例如autowireByName方法会根据bean的名字注入;autowireByType方法根据bean的类型注入,完成属性值的注入(涉及bean初始化过程)。*对象会存储在map结构中,在spring使用Map结构的singletonObjects存放完整的bean对象(涉及三级缓存和循环依赖)。整个bean的生命周期,从创建到使用到销毁的过程全部都是由容器来管理(涉及bean的生命周期)。
AoP(面向切面编程)
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
银行
-
为什么选择来银行?
银行是一个有稳定发展前景的行业,而且在当今的经济环境下,银行的角色非常重要。选择银行是因为希望能够在一个稳定的行业中工作,同时为社会做出一些贡献。银行是一个注重团队合作和个人成长的行业。我希望能够在一个团结、相互协作的环境中工作,学习和发展自己的能力,不断提高自己的技能和经验,为银行和自己的未来发展做出贡献。
我对银行的核心业务和金融知识有浓厚的兴趣。希望通过在银行的工作中,深入了解银行的业务和金融知识,并将其应用于日常工作中,以提高自己的专业技能和知识水平。
-
为什么选择邮储?
选择来邮政储蓄工作是因为邮政储蓄是一家好公司,待遇也是不错的。因为中国邮政作为百年企业,有很强大的支撑基础,邮政储蓄银行作为其下属的子公司,在各项发展中是很有保障的,同时,邮政储蓄银行作为一个新独立的金融机构,相对于其他商业银行而言,其具有一个非常广泛、普及的网点优势。首先,邮政作为一个百年企业,它值得我信赖并将青春托付。其次,邮政提供的平台非常大,它能够给我足够的空间去磨练自己,展示才华。
面试题目
1. 拼多多 3.23
项目:不动产估值系统
- 学到了什么?提到scrapy理论,问原理
- 多源异构数据集?数据类型是什么?
- 机器学习算法怎么实现的?用了哪些特征?
- 评估指标是啥(准确率)?r2的含义是什么?
- R2_score直观且不受量纲影响。其数值大小反映了回归贡献的相对程度,即在因变量Y的总变异中,回归关系所能解释的百分比,其值越高,说明模型的解释性越好。
- 还用过其他评估指标吗?还知道其他评估指标吗?
- 平均绝对误差、均方误差、均方根差、均方根百分误差、决定系数R2
- 前四种问题:量纲不同时,难以衡量模型效果好坏,并且可读性较差
- 训练集和测试集怎么划分的?为什么这么划分?(这么划分结果最好
- 为什么其他划分方式结果不好?有分析吗?
- 训练集测试集划分比例有什么影响?过拟合是什么意思?
- 个人能力比较突出的是什么?
- 华融实习都是自己做的吗?
- 遇到过什么问题?怎么解决?印象比较深花时间比较多的(提到了版本不一致报错)
- java遇到版本不一样问题怎么办?a库和b库都用到了c库
java:
-
类加载机制
-
垃圾回收机制
数据库:
-
redis和mysql有什么不同?为啥用redis做缓存?
-
redis数据结构?
算法:
上海[180.65.28.0, 186.75.28.0]
重庆[101.0.0.0,101,255,255,255]
…一共十万行
然后给ip地址(字符串),返回城市名称,没有对应城市返回""
反问:什么部门?做什么?跨境电商 海外业务 推荐搜索
2. 美团-履约 3.24
项目:
-
爬虫策略 数据量
-
用过大数据挖掘吗?spark这些?
MySQL:
-
介绍索引
-
B树和B+树区别
-
还有别的索引吗?都用的B+树吗?
-
聚簇索引 非聚簇索引
-
主键索引 非主键索引
java:
-
垃圾回收算法
-
java的锁synchronize,lock
-
介绍线程池
-
强引用 弱引用
算法:2. 两数相加 - 力扣
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummyHead=new ListNode(-1);
ListNode p=dummyHead;
int add=0;
while(l1!=null||l2!=null||add>0){
int a=l1==null?0:l1.val;
int b=l2==null?0:l2.val;
int ans=a+b+add;
p.next=new ListNode(ans%10);
add=ans/10;
p=p.next;
if(l1!=null)
l1=l1.next;
if(l2!=null)
l2=l2.next;
}
return dummyHead.next;
}
}
3. 联想 3.27
图书项目
- spring boot和spring的区别?
- 最大的区别在于Spring Boot的自动装配原理
- 比如:
- 创建Web程序时:Spring-导入几个Maven依赖,Spring Boot-只需要一个Maven依赖
- 开启功能:Spring-XML文件配置,Spring Boot-不用XML配置,只需要写一个配置类
- Spring Boot是一个微服务框架,延续了Spring框架的核心思想IOC和AOP,简化了应用的开发和部署,是Spring的完善和扩展
- mysql中有几种表?分别存的数据项是什么?
- user,book,category(图书类别),admin_role(用户角色),admin_permission(用户权限),admin_role_menu(用户角色菜单),admin_role_permission(用户角色权限)
- book表:id,cover,title,author,date,press,abs,cid(类别做外键)
- 项目中redis用什么客户端部署?
- Java 访问 Redis 主要是通过 Jedis 和 Lettuce 两种由不同团队开发的客户端(提供访问、操作所需的 API),Jedis 比较原生,Lettuce 提供的能力更加全面。
- 本项目用Spring Data Redis,Spring Data Redis是在 Lettuce 的基础上做了一些封装,与 Spring 生态更加贴合,使用起来也更简便。
- redis用的哪种数据结构存储图书?
- RedisTemplate是 Spring Data Redis 提供的一个完成 Redis 操作、异常转换和序列化的类
- String类型key,Set keys,List< Book >存图书列表
- List存取的api了解吗?
- append,get
- redis缓存是所有图书都存进去吗?
- 数据库组建怎么实现的?
- mysql中事务是怎么添加的?
- 步骤1:开启事务
set autocommit=0;
start transaction;可选的 - 步骤2:编写事务中的sql语句(select、insert、update、delete)
语句1;
语句2;
… - 步骤3:结束事务
commit;提交事务
rollback;回滚事务
- 步骤1:开启事务
- 一个mapper两个update怎么保证事务不冲突?(答锁,纠正我锁是保证数据一致性的
- MVCC
- undo log
- 隐藏字段
- ReadView
- MVCC
抵押品项目
怎么构建的数据集?
java
-
static和final什么时候用?为什么要用?
- static:
- 加载:类加载时初始化(加载)完成
- 可修饰:内部类、方法、成员变量、代码块
- 不可修饰:外部类、局部变量
- 作用:方便调用没有创建对象的方法/变量
- final
- 加载:final可以在编译(类加载)时初始化,也可以在运行时初始化,初始化后不能被改变
- 可修饰:成员变量、方法、类以及本地变量
- 一旦你将引用声明作 final,你将不能改变这个引用了,保证数据安全性
- static final:
- 可修饰:成员变量、方法、内部类
- 被static final修饰意义如下
- 成员变量:属于类的变量且只能赋值一次。
- 方法:属于类的方法且不可以被重写。
- 内部类:属于外部类,且不能被继承。
- static:
-
Object常用hashCode()和equals(),什么时候需要重写?
-
1)当我们需要重新定义两个对象是否相等的条件时,需要进行重写。比如通常情况下,我们认为两个不同对象的某些属性值相同时就认为这两个对象是相同的。
例如:我们在HashMap中添加元素时,我们认为当key相同时,两个元素就相同,但是默认的Object中的equals(),只是单纯的比较两个元素 的内存地址是否相同,不能满足我们的要求,所以需要重写。
-
2)当我们自定义一个类时,想要把它的实例保存在集合时,就需要重写equals()和hashCode()方法
-
-
ArrayList的底层数据结构
- Object[]数组
-
HashMap的底层数据结构?
- jdk1.8之前:数组+链表(链表散列)
- jdk1.8之后:链表长度>阈值(默认8),将其转化为红黑树,减少搜索时间
-
HashMap数组中怎么存的?
- 存的hash值,链表中存键值对
-
哈希冲突是什么?
-
synchronize怎么用?啥时候用?
- 修饰实例方法:进入同步代码前要获得 当前对象实例的锁 。
- 修饰静态方法:进入同步代码前要获得 当前 class 的锁。
- 修饰代码块
synchronized(object)
表示进入同步代码库前要获得 给定对象的锁。synchronized(类.class)
表示进入同步代码前要获得 给定 Class 的锁
算法(口述)
- 两个数组找交集,令时间复杂度最小(答用set 时间复杂度是多少?
- 排序 + 双指针:初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,且该数字不等于 pre ,将该数字添加到答案并更新 pre 变量,同时将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。
4. 蚂蚁-数金 3.28
押品项目
- 从哪些维度对数据进行挖掘?得到什么样的结果?哪些对算法模型有决定性作用
- 系统开发后端是不是增删改查这些?用的什么数据库
java
-
保证线程安全的办法?(答锁,问我还有其他吗,没答出来
- 互斥同步:synchronized是最常用的
- 非阻塞同步
- 无同步方案
-
说说对锁的了解(答乐观锁和悲观锁
-
乐观锁实现:版本号orCAS
-
悲观锁实现:
synchronized
和Lock
等独占锁就是悲观锁
-
-
悲观锁用什么实现?(答synchronized和ReentrantLock
-
synchronized和其他独占锁底层是怎么实现的?
- synchronized底层:
-
java为什么会存在线程安全的问题?底层原因是什么?(答并发,跟我说是表象,问更深层是什么原因
-
java本身jvm或者内存什么样的设计特点决定它存在这样的问题?(答线程共享堆和方法区资源,同时修改会发生安全问题,他还问更深层的是什么
-
线程共享:方法区,堆(有线程安全问题)
-
线程独享:栈、本地方法栈、计数器(不存在线程安全问题)
-
-
了解java内存模型吗?新增一个线程,线程里的变量存在于什么地方?(答栈
-
这个栈空间是共享的还是独享的?(答共享的,纠正我有一部分共享一部分独享,共享function,独享变量
变量独享,做复制,拷贝到工作内存,更改完需要回刷,产生XXx操作,是问题的根本原因
-
ConcurrentHashMap的源码了解(答了一下初始化源码
mysql
- 索引结构(答b+树
- b+树特点
- b+树每一层都是链表(我说只有叶子是,纠正我mysql不是标准b+树,是变种
- 查询语句建索引,abc三个字段,有一条语句“a=?,c=?”,“?”是占位符,这个语句能不能用到abc复合索引?(答能,问原因不会
redis
- 读数写数单线程,那为什么性能那么好?(三点
Spring boot
-
动态加载和动态配置化为开发带来了哪些便利?
-
你在开发中用到了什么动态加载和动态配置化?
(答spring用xml,sb用配置类,纠正:Spring也可以用注解不用xml配置,动态加载和用不用配置没啥关系
算法
- 线程安全的单例模式
- 快排
- 有序数组找第一个大于x的元素下标
其他
工作和学习中做的最有挑战或者最有成就感的一件事是什么?
5. 美团-酒旅 3.30
押品项目
- 难点?
- 对数据挖掘的结果是什么?什么数据对结果影响比较大?
- 再来一个数据集?怎么把它变成同构的?
- 算法还有提升空间吗?有多少提升空间?怎么提升?试过什么方法?提升了多少?
- 没用过的那些算法准确率有多少?
- 系统都有什么功能?
- 实习做的算法?为啥找开发?
图书项目
- 怎么用的maven?
- 为什么采用shiro框架?还知道什么安全框架?(答Spring Security
- Spring Security和shiro有什么区别?为什么没用Spring Security?
- 项目里redis用的哪种数据结构?键是什么?(答List,键是图书id,他质疑我键不对
Java
-
java异常机制的了解
在 Java 中,所有的异常都有一个共同的祖先
java.lang
包中的Throwable
类。
-
设计一个java线程池
-
Integer a=128; Integer b=128; System.out.println(a==b);
结果是啥?(应该是true,我答的false,他问我不是有常量池吗
-
public static void main(String[] args) { Integer numValue=new Integer(1); int a=1; int b=2; Integer nullValue=null; if(a==1) b=nullValue; else if(b==2) a=nullValue; System.out.println(numValue); System.out.println(nullValue); System.out.println(a); System.out.println(b); }
结果是啥?(代码大概是这样,结果是编译错误T_T,int型(b)不能为null,Integer可以为null
-
类加载机制?
-
知道哪些类加载器(ClassLoader)?
-
BootstrapClassLoader
(启动类加载器) :最顶层的加载类,主要用来加载 JDK 内部的核心类库 -
ExtensionClassLoader
(扩展类加载器) :主要负责加载%JRE_HOME%/lib/ext
目录下的 jar 包和类以及被java.ext.dirs
系统变量所指定的路径下的所有类。 -
AppClassLoader
(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
-
-
垃圾回收机制的了解?(答回收算法,纠正我分代收集不算一种算法
-
哪几种收集器用的标记-整理?哪几种用的复制?
操作系统
-
构造一个死锁
public class DeadLock { private class Running1 implements Runnable { /* * This method request two locks, first String and then Integer */ @Override public void run() { while (true) { synchronized (String.class) { System.out.println("Thread1 Aquired lock on String.class object"); synchronized (Integer.class) { System.out.println("Thread1 Aquired lock on Integer.class object"); } } } } } private class Running2 implements Runnable { /* * This method also requests same two lock but in exactly * Opposite order i.e. first Integer and then String * This creates potential deadlock, if one thread holds String lock * and other holds Integer lock and they wait for each other, forever */ @Override public void run() { while (true) { synchronized (Integer.class) { System.out.println("Thread2 Aquired lock on Integer.class object"); synchronized (String.class) { System.out.println("Thread2 Aquired lock on String.class object"); } } } } } public static void main(String[] args) { DeadLock deadLock = new DeadLock(); Running1 r1 = deadLock.new Running1(); Running2 r2 = deadLock.new Running2(); Thread thread1 = new Thread(r1); Thread thread2 = new Thread(r2); thread1.start(); thread2.start(); } }
在上面这个例子中,我们有两个线程,以相反的顺序去获取两个锁,最后陷入了Thread1持有String锁,等待Integer锁,Thread2持有Integer锁,等待String锁的无限等待,即死锁情景
MySQL
-
InnoDB和MyISAM的区别(7点
-
为什么InnoDB的性能更强大?
- MyISAM 因为读写不能并发,它的处理能力跟核数没关系。
- 在读写混合模式或者只读模式下,随着 CPU 核数的增加,InnoDB 的读写能力呈线性增长。
-
为什么要主键自增?
- 1.数据记录本身被存于主索引的叶子节点上
- 2.mysql会根据其主键将其插入适当的节点和位置
- 3.表使用自增主键,在每次插入新的记录时,记录会顺序添加到当前索引节点的后续位置
redis
-
说说对redis的了解(答高性能和高并发
-
redis为什么用单线程?
- 单线程编程容易并且更容易维护;
- Redis 的性能瓶颈不在 CPU ,主要在内存和网络;
- 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
-
哪几种底层数据结构?每种数据结构的使用场景?
-
String
-
存储常规数据
-
计数
-
分布式锁
-
-
List
-
信息流展示
-
消息队列
-
-
Set
-
存放的数据不能重复(点赞…
-
获取多个数据源交集、并集和差集(共同好友、音乐推荐…
-
随机获取数据源中的元素(抽奖系统…
-
-
Hash
- 对象数据存储(用户信息、文章信息…
-
Zset
-
随机获取数据源中的元素根据某个权重进行排序(朋友圈的微信步数排行榜…
-
存储的数据有优先级或者重要程度(优先级任务队列
-
-
-
io多路复用是怎么复用的?
-
目的:让单线程(进程)的服务端应用同时处理多个客户端的事件
-
含义:**“多路”指的是多个网络连接客户端,“复用”**指的是复用同一个线程(单进程)。
I/O 多路复用其实是使用一个线程来检查多个Socket的就绪状态,在单个线程中通过记录跟踪每一个socket(I/O流)的状态来管理处理多个I/O流。
-
算法
-
sql:student表里找成绩第二的
-
有序数组,遍历一次找出所有a+b=m的数字(要运行)
追问:数组是空怎么办?数组是[2,2,5,5]怎么办?
反问:对校招生的建议(告诉我美团注重基础
6. 快手-海外业务 4.20
java
-
基本数据类型,每种对应多少字节?
-
Integer a=127; Integer b=127; Integer c=128; Integer d=128; System.out.println(a==b);//返回true; System.out.println(c==d);//返回false;
直接使用Integer包装类赋值,范围在[-128,127]之间不会生成新的对象,直接把缓存中的对象拿来用,超过了范围就相当于在堆中new一个对象,然后双等于就是比较两者的内存地址是否一样
-
说一下hashmap,为什么容量是2的幂次方,怎么定位的?
指定初始容量,容量会变成大于这个值的2的次幂的最小值;
- 原因:源码:(n - 1) & hash
- 好处:
- &运算比%快
- 保证索引在capacity中,不会越界
- 当n为2次幂时,(n - 1) & hash = hash % n
-
静态方法先执行吗?父类和子类谁先执行?(静、父类
-
执行顺序:
父类的静态代码块
子类的静态代码块
执行父类的构造代码块
执行父类的构造方法
执行子类的构造代码块
执行子类的构造方法
执行子类的A方法
-
-
说一下线程池
-
说一下JVM的内存管理(不会…
mysql
-
说一下四种隔离级别
-
mysql默认的隔离级别?
可重复读
-
读取已提交不能阻止不可重复读,那么mysql怎么解决的呢?用的什么锁?
-
读取未提交原理:
- 事务对当前被读取的数据不加锁
- 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级共享锁,直到事务结束才释放
-
读取已提交原理:
- 事务对当前被读取的数据加 行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁
- 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放
-
可重复读原理:
- 事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加行级共享锁,直到事务结束才释放
- 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放
-
可串行化原理:
- 事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放
- 事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放。
-
算法:
-
手写单例模式
-
未排序数组中找第k个最大数,都有什么算法?实现堆和快排,时间复杂度是多少?
7. 邮储-北京 4.25
hr:
项目中一般扮演什么样的角色?
项目:
- 校企联合还是实习项目?
- 怎么进行评估的 balabala(感觉面试官不太懂机器学习
八股:
-
线程之间如何通信?
线程通信就是当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺。
- 共享内存:线程之间共享程序的公共状态,线程之间通过读-写内存中的公共状态来隐式通信。
- volatile共享内存
- 消息传递:线程之间没有公共的状态,线程之间必须通过明确的发送信息来显示的进行通信。
- wait/notify等待通知方式
join方式
- wait/notify等待通知方式
- 管道流
- 管道输入/输出流的形式
- 共享内存:线程之间共享程序的公共状态,线程之间通过读-写内存中的公共状态来隐式通信。
-
什么是死锁?怎么解除死锁?
- 死锁:多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都无法向前推进
-
https如何建立安全通道?
HTTP明文传输->三个风险:窃听、篡改、冒充
- 客户端发起HTTPS请求
- 服务端将数字证书和公钥发送给客户端
- 客户端验证数字证书是否合法,然后使用公钥将自己生产的随机密钥进行加密
- 客户端将加密后的密钥发送给服务端
- 服务端使用自己的私钥将加密后的密钥进行解密,得到对称加密算法的密钥。接下来的数据可以通过这个密钥进行加密后传。
8. 阿里-ICBU 4.26
30min电话面
项目
- 做图书阅读管理系统的目的?
- 为什么要做抵押品估值系统?
- 估值模型是怎么设计的?
- 系统怎么存储?几张表?
- 模型上线之后怎么评优?怎么迭代?
Springboot
-
在开发过程中主要解决什么问题?(答简化开发过程
-
以前哪个环节比较耗时?用Springboot简化了
-
常用的注解?
-
加事务的注解?(@Transactional
-
事务注解的参数?
- propagation(事务的传播行为)、isolation(事务的隔离级别)、timeout(超时时间)、readONLY(是否只读)、rollbackFor(回滚)、noRollbackfor(不回滚)
-
AOP的含义?
- 面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
- 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
-
IOC的含义?【背诵默写】
- SpringIOC有两个核心思想就是IOC控制反转和DI依赖注入
- IOC 控制反转的基本思想是,将原来的对象控制从使用者,有了spring之后可以把整个对象交给spring来帮我们进行管理。
- DI 依赖注入,就是把对应的属性的值注入到具体的对象中。spring提供标签和@Autowired和@Resource注解等方式注入,注入方式本质上是AbstractAutowireCapableBeanFactory的populateBean() 方法先从beanDefinition 中取得设置的property值,*例如autowireByName方法会根据bean的名字注入;autowireByType方法根据bean的类型注入,完成属性值的注入(涉及bean初始化过程)。*对象会存储在map结构中,在spring使用Map结构的singletonObjects存放完整的bean对象(涉及三级缓存和循环依赖)。整个bean的生命周期,从创建到使用到销毁的过程全部都是由容器来管理(涉及bean的生命周期)。
-
了解过AOP和IOC的实现吗?如果自己做一套IOC怎么做?
-
实例化交给框架做带来的好处是什么?
MyBatis
- MyBatis是做什么的?(操作数据库
- MyBatis操作方式有什么优势?(提了JPA,说了一下二者区别
- MyBatis比JDBC的优势?(减少代码量、更灵活…
- 说一下对ORM的了解(为了解决面向对象与关系数据库存在的互不匹配的现象的技术
Java
- 怎么生成一个线程?
- 线程池有什么作用?
- 怎么结束一个线程池?(shutdown
- 多线程里原子性、可见性、有序性是什么概念?
- 为什么会出现可见性的概念?
9. 字节-今日头条-4.28
项目
- 数据库怎么设计的?用到什么索引?
- 介绍一下系统的服务架构,完成了什么功能?
- 实验室做了什么?
计算机网络
- POST和GET的区别?
Linux
- 杀死进程的方法?
Java
- JVM中堆和栈的区别?创建对象存储在哪里?
- 堆和栈数据结构的区别?
操作系统
- 进程和线程的区别?
- 软硬链接的区别?
MySQL
-
外键是什么?
-
介绍一下两种存储引擎?(MyISAM、InnoDB
-
truncate、delete和drop的区别?
区别点 drop truncate delete 执行速度 快 较快 慢 命令分类 DDL(数据定义语言) DDL(数据定义语言) DML(数据操作语言) 删除对象 删除整张表和表结构,以及表的索引、约束和触发器。 只删除表数据,表的结构、索引、约束等会被保留。 只删除表的全部或部分数据,表结构、索引、约束等会被保留。 删除条件(where) 不能用 不能用 可使用 回滚 不可回滚 不可回滚 可回滚 自增初始值 - 重置 不重置
算法
- 链表加法(说想法、空间复杂度
10. 思特奇-5.12
项目
- 主要做什么?
- 学到了什么?(技术、沟通、解决问题
java
- ArrayList的扩容机制(默认10,每次1.5倍扩充
- 线程池怎么创建?使用场景?
- 线程冲突怎么处理?
- long和double怎么类型转换?
Mysql
-
怎么优化sql?
分库分表、索引、缓存
-
索引为啥要用B+树?有什么好处?
- 索引节点没有数据,比较小,能够完全加载到内存中
- 叶子节点之间都是链表的结构,所以B+Tree也是可以支持范围查询的,而B树每个节点key和data在一起,则无法区间查找
- B+树中因为数据都在叶子节点,每次查询的时间复杂度是稳定的,因此稳定性保证了
Redis
-
有哪几种数据结构?
string、list、set、zset、hash
Springboot
-
用过哪几种配置文件,分别有什么作用?
-
注解是怎么起作用的?比如说像@Repository
通过反射进行处理,可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
其他
- 了解微服务吗?
码量、更灵活…
4. 说一下对ORM的了解(为了解决面向对象与关系数据库存在的互不匹配的现象的技术
Java
- 怎么生成一个线程?
- 线程池有什么作用?
- 怎么结束一个线程池?(shutdown
- 多线程里原子性、可见性、有序性是什么概念?
- 为什么会出现可见性的概念?
9. 字节-今日头条-4.28
项目
- 数据库怎么设计的?用到什么索引?
- 介绍一下系统的服务架构,完成了什么功能?
- 实验室做了什么?
计算机网络
- POST和GET的区别?
Linux
- 杀死进程的方法?
Java
- JVM中堆和栈的区别?创建对象存储在哪里?
- 堆和栈数据结构的区别?
操作系统
- 进程和线程的区别?
- 软硬链接的区别?
MySQL
-
外键是什么?
-
介绍一下两种存储引擎?(MyISAM、InnoDB
-
truncate、delete和drop的区别?
区别点 drop truncate delete 执行速度 快 较快 慢 命令分类 DDL(数据定义语言) DDL(数据定义语言) DML(数据操作语言) 删除对象 删除整张表和表结构,以及表的索引、约束和触发器。 只删除表数据,表的结构、索引、约束等会被保留。 只删除表的全部或部分数据,表结构、索引、约束等会被保留。 删除条件(where) 不能用 不能用 可使用 回滚 不可回滚 不可回滚 可回滚 自增初始值 - 重置 不重置
算法
- 链表加法(说想法、空间复杂度
10. 思特奇-5.12
项目
- 主要做什么?
- 学到了什么?(技术、沟通、解决问题
java
- ArrayList的扩容机制(默认10,每次1.5倍扩充
- 线程池怎么创建?使用场景?
- 线程冲突怎么处理?
- long和double怎么类型转换?
Mysql
-
怎么优化sql?
分库分表、索引、缓存
-
索引为啥要用B+树?有什么好处?
- 索引节点没有数据,比较小,能够完全加载到内存中
- 叶子节点之间都是链表的结构,所以B+Tree也是可以支持范围查询的,而B树每个节点key和data在一起,则无法区间查找
- B+树中因为数据都在叶子节点,每次查询的时间复杂度是稳定的,因此稳定性保证了
Redis
-
有哪几种数据结构?
string、list、set、zset、hash
Springboot
-
用过哪几种配置文件,分别有什么作用?
-
注解是怎么起作用的?比如说像@Repository
通过反射进行处理,可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
其他
- 了解微服务吗?