记录面试

深信服

  1. TCP连接建立过程
    三次握手四次挥手,主要记住fin wait -1;-2;last ack等

  2. 介绍Reactor模型(基于事件驱动
    三个主要角色:Reactor 监听和分配事件 ; Acceptor:处理连接 ;Handler 执行任务;
    Reactor通过select监听客户端,收到事件之后通过dispatch分发,连接事件给Acceptor,使用accept处理,然后Handler对象负责后续的事件(单线程下它就处理,多线程下就分配给worker线程);
    单Reactor单线程;Redis是单Reactor单进程
    单Reactor多线程:多线程是应用在Handler的;Handler只负责响应事件,主要需要说明白的地方在于worker线程处理完毕任务之后,如何提交任务?
    主从Reactor多线程:主Reactor负责监听socket;从Reactor主要用于做和数据交互or事件业务的处理;
    特点:虽然本身依然是同步的,但相应快;扩展和复用性比较高

  3. 为什么用docker
    容器技术是资源隔离(使用namespace),共享内核;虚拟机是完全隔离
    首先隔离性好,主库从库不会相互影响(装在一个主机上会怎么样呢?)

  4. docker数据安全
    使用数据卷挂载来存数据;

  5. 想把数据文件从docker拿出来:(迁移docker下的mysql数据库
    导出源数据-导入目标数据库

  6. 线程进程的区别
    进程切换开销是大于线程的:启动新的进程需要分配独立地址空间,维护代码段;堆栈段;
    为什么需要进程:让多个程序并发;为什么需要线程:线程切换效率高,且方便通信
    协程是异步机制,可以由程序员手动管理

  7. 多线程打印一个list里面的全部东西,加锁&&不加锁

package com.company;

public class jiaoti {

    static class Solution implements Runnable {
        static int value = 0;

        // 实现接口就必须重写方法;
        @Override
        public void run() {
            while (value <= 100) {
                // 对整个类加锁
                synchronized (Solution.class) {
                    // 交替打印数字
                    System.out.println(Thread.currentThread().getName() + value++);
                    Solution.class.notify();
                    try{
                        Solution.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new Solution()).start();
        new Thread(new Solution()).start();
    }
}

生产者消费者模式

  1. 单链表成环:快慢指针
  2. topK 问题:对
  3. 越过中间层访问:增加校验环节

美团第一次一面

七层网络协议
网络层:ip地址和路由选择; ip arp
数据链路层:硬件地址寻址;差错校验 mac
物理层:建立,维护,断开物理连接

三次握手,四次挥手,以及目的,
确保双方能进行可靠的传输,三次握手保证建立可靠的数据传输通道,四次挥手为了保证数据完成被接受后才能断开连接

线程进程的区别
进程调度方式:先进先出;处理机分配给最先进入就绪队列的进程;短作业优先;CPU优先分给最短的,会抢占,会饿死;时间片轮转;按一定的时间间隔把cpu分配给队列中的进程;

int和Integer,自动装拆箱发生的时间,new Integer,直接生成怎么样
Integer A = new Interger(100) int B = 100 A==B 这个是TRUE;因为会自动装拆箱爱那个

常见集合主要是从Collection和Map中派生出来的
Collection下面包含List,Set,Queue;分别包含ArrayList;ArrayQueue(双端队列),LinkedList,HashSet,TreeSet(归根到底是红黑树,不归根到底就几乎TreeMap)

list实现类(好像很啰嗦的样子)
ArrayList和LinkedList数据结构,他们是否线程安全
线程安全的是CopyOnWriteArrayList;ConcurrentLInkedList(乐观锁)

对哪些锁有了解,ReentrantLock,Syn,公平锁,非公平锁,对它们的理解(不熟)
项目中的锁就说syn吧
Syn是不公平的,Re可以保证公平
Syn依靠JVM,Re依靠jdk,需要lock(),unlock();

Syn的底层如何实现
同步代码块是依靠monitorenter和moniterexit实现的;enter指向同步代码块开始的位置,exit指向结束的位置。同时,线程获取锁也是获取monitor(它存在于java对象的对象头)

同步方法:JVM通过检查ACC_SYNCHRONIZED标识来确定该方法是一个同步方法

在java6之前monitor的实现依靠操作系统内部的互斥锁;需要进行用户态到内核态之间的切换,因此同步操作是重量级操作;后续JVM提供了三种Monitor的实现:偏斜锁、轻量级锁、重量级锁

锁升级原理
本质上是JVM优化synchronize的机制,针对不同竞争的状况自动切换适合的锁
偏向锁:锁对象的对象头有treadid字段,第一次访问时threadid是空,jvm让其持有偏向锁;再将threadid设置为线程id;当再次进入的时候就判断threadid与线程id是否一致,一致可以直接使用此对象
轻量级锁:如果threadid和线程id不一致,就进行锁升级,升级为轻量级锁并通过自旋来获取锁
重量级锁:如果自旋一定次数之后依然没有获得锁,就升级为重量级锁

锁升级目的
降低锁的性能消耗(一开始就重量级锁就调用了系统当中的互斥锁

锁膨胀
无锁-偏向锁-轻量级锁-重量级锁,是锁膨胀不可逆,不是锁降级不可逆
偏向锁:不存在多线程竞争,总是由同一个线程多次获得
轻量级锁:有第二个线程申请同一个对象,偏向锁立即神机(这里是申请,没有竞争)
重量级锁:两个线程开始竞争锁了

锁消除
编译的时候对运行上下文进行扫描并去除不可能存在竞争的锁

锁粗化
避免频繁加锁和释放锁

volatile和syn区别
对volatile变量的读写都直接写入主存,保证变量的可见性,但是不保证原子性。
syn则是使用内存屏障来保证操作的可见性
syn使用范围更广(变量,方法,类),vo只能用在变量
syn保证原子性,vo不保证
vo不会被编译器优化而且禁止指令重排,syn可以被编译器优化

ThreadLocal
线程本地变量:访问ThreadLocal的每个线程有一个本地拷贝,多线程操作变量的本质是操作本地内存的变量。
原理:每个线程有一个属于自己的ThreadLoacalMap,内部维护Entry数组,key为ThreadLocal本身,value是泛型值
问题:内存泄漏,ThreadLocalMap中使用的Key(ThreadLocal)是弱引用,因此只要gc机制一启动,map的key就没有了,vavlue倒是还在
这么设计的目的:让ThreadLocal被清空的同时ThreadLocalMap也被清空;key被回收为null;value也为null了。

CAS(在Unsafe类中)
CPU的同步原语,属于硬件对并发的支持;当需要读写的内存值和旧的预期值相同时,通过原子的方式来更新为新值
问题1:ABA,不能确保旧的预期值失败会更换过;解决方案:使用带有标记的原子引用类AtomicStampedReference:
问题2:只能保证一个变量的原子操作

AQS(锁框架
定义锁实现机制,子类根据state字段决定是否可以获得锁,无法获得的锁的线程由AQS管理(应该是放在同步队列里面),无需子类锁关心

ReadWriteLock,解决ReentrantLock的局限,实现读写分离,读共享,写独占,提升读写的性能;

线程池,分类,拒绝策略
好处:降低新建,销毁简称的资源消耗;任务到达的时候无需等待线程的创建即可立即执行;线程池可以对线程进行统一的分配和监控,避免无限制创建线程;

分类:newCachedThreadPool,几乎没有线程和数量限制,容易OOM;newFixedThreadPool:指定工作线程数量的线程池,队列无限长;newSingleThreadExcetor:只有唯一的工作线程,保证任务顺序执行;newScheduleThreadPool:固定长度线程池;
IO密集型使用多线程可以加速程序运行,cpu密集型只有在真正多核的cpu才能通过多线程加速;

execute和submit;执行的任务就不需要返回值,无法判断是否成功,submit的方法是需要返回值的,线程池会返回future类对象

运行机制
线程池添加任务-小于核心线程数,可以创建核心线程;大于就扔到阻塞队列-如果阻塞队列满,没到达最大线程数,就创建新的线程;如果最大线程数也达到了,就执行饱和策略

饱和(拒绝)策略
默认:丢弃任务,报错;
丢弃任务,不报错
丢弃阻塞队列队首任务,将最新任务加入
在线程池之外直接调用run方法执行

jvm内存区域
程序计数器:作为当前线程行号的指示器,记录虚拟机正在执行的线程指令地址;(线程私有)
堆:所有线程共享的一块内存
JVM栈,本地方法栈:虚拟机栈用于管理java方法的调用,本地方法栈用于管理本地方法的调用(都是线程私有的)
方法区,存储被虚拟机加载的类信息、常量、静态变量等

为什么要有GC
减轻开发人员工作,增加系统的安全性稳定器,减少内存泄漏;gc主要任务时回收不再被对象引用的内存空间;
缺点就是gc需要跟踪内存,处理碎片,影响程序的执行效率

GC算法之可达性分析
GC root 有哪些:虚拟机栈和本地方法栈引用的对象;方法区静态属性和常量池引用的变量;

各种对应的GC算法
标记清除:(使用可达性分析)需要遍历两遍,第一次标记存活对象和垃圾,第二次回收垃圾;
标记整理法:第一步和标记清除法一样,第二步是把存活的对象向前移动,把垃圾对象进行回收;适合垃圾少存活对象多的情况
复制算法:内存一分为二,每次使用一块,一块使用完毕就把存活的移到另一块,然后回收内存空间
分代收集算法:内存划分为新生代老年代,新生代采用复制算法,老年代采用标记清除,或者标记整理;因为新生代要回收的太多了,老年代相对较少;

各种算法的优缺点
标记清除:效率不行(需要两次遍历),可能会产生不连续的空间分片,因此遇到大对象可能需要full GC
标记整理:需要整理的过程
基于复制算法:浪费空间,内存使用率比较低

GC回收器
JDK 8 :Parallel Scavenge + Serial Old JDK 9 : G1;
Serial:单线程的收集器,需要stw,采用复制算法;
Serial Old:老年代版本,使用标记整理算法
ParNew:Serial的多线程版本,stw 2,复制2;
Parallel Scavenge:新生代收集器,复制,并发;特点是jvm会动态设置参数以提供最优的停顿时间
Parallel Old:也是标记整理算法
CMS:并发的mark sweep;多线程(初始标记,从gcroot向下一步,stw;并发标记:可达性分析,多线程;重新标记:并发标记会有漏的,因为没有stw;并发清除;CMS主要问题是浮动垃圾(并不是重新标记就可以完全解决,这个能够理解)还有大量的碎片,还有就是回收线程数多,导致用户程序的执行速度下降
G1: 初始标记(STW)和CMS一样,标记的是gcroot直接关联的对象;并发标记,也是做可达性分析,不stw,所以还有;最终标记:stw;最不一样的是清理阶段,它会分块进行排序,根据期望的停顿时间来回收,但是这里有一点基于复制的算法,就是把回收的那一部分复制到Region之中,然后清理掉全部空间;

MinorGC只对新生代进行GC,Eden满就开始GC;FullGC针对新生代和老年代

Full GC触发条件
Minor GC后,晋升需要的空间大于老年代可用的空间
Eden往Survivor复制的时候,Survivor容不下,且老年代剩余空间不足,所以就需要Full GC(但也不一定,也可能会执行空间分配担保原则,先检查是否允许担保,云溪就看看老年代最大可用的空间是不是大于平均晋升所需空间,是的话就young GC一次,但是有风险;不允许担保就Full GC,允许担保也可能Young GC之后还要Full GC;

Spring AOP和IOC
AOP:面向切面编程:与业务无关的代码抽取出来集中管理,然后容器负责动态管理代码;
实现AOP主要靠代理;jdk通过反射实现动态代理,cglib通过继承实现的;

IOC:控制翻转;Spring的Bean工厂负责新建对象,并在调用的时候进行实例化;首先用工厂模式创建bean,然后通过反射注入数据;IOC实现的原理就是反射

单例模式(6种都要说),单例模式解决了什么问题,适用什么场景,需要再了解一个
概念:保证一个类仅有一个实例,这个实例可以被全局访问
为什么需要单例模式:节约公共资源,方便控制
静态内部类的单例模式为什么可以保证实现线程安全:因为只有访问内部类,才会被实例化,同时static关键字保证只被初始化一次

对索引的了解,联合索引,数据结构,聚簇索引,非聚簇索引,联合索引,最左匹配,最左匹配的失效情况,
遇到范围查询就失效
为什么联合索引是最左匹配:它是先对最左的索引进行排序,然后再在这个基础上对第二个索引进行排序,所以第一个索引是绝对有序但第二个就不是了

主库同步原理
slave连接到master时,会开启binlog dump线程,当binlog发生变化,dump线程通知slave并发送相应的binlog内容给slave;然后从库的I/O线程接受binlog后,写入relay log;sql线程根据relay log执行相应的内容。

什么消息队列,MQ

项目索引是学号
分了几个模块
客户端:发送请求
中间层:登录
Server层:负责写/读数据库
模块拆分的考虑:读写分离

数据表的索引怎么建立

  1. 主键一定要有索引
  2. 经常查询的(高频)
  3. 经常出现在where,group by, order by后面的字段

项目的难点和问题
难点是整个框架的构建,思路
问题是主从同步的搭建

写题:
整数对查找
两数之和

美团 第二次一面(被打捞)

为什么要从NIO迭代到Reactor模式
先说明Reactor和NIO
NIO在accpect之后需要进行读/写/连接等操作,是单线程的,如果客户端访问量太大会导致无法接受新的连接,然后进一步进行超时重传,加重NIO负担;因此就希望将后续的读写连接等操作成为多线程的。
最大的区别在有一组NIO线程来处理IO操作:Handler和Reactor、Acceptor是由不同的线程实现的。Handler这里有一点异步的思想(处理多线程的时候)

没搞懂的地方:异步这里如何实现线程安全

业务场景是:读多写少,通过客户端的写场景基本是对于数据的修改而不是批量的插入;

数据不能是HTTP请求吗?为什么是TCP(问老王)

展开论述Reactor模型:分类,我用的哪种,为什么,更深入的了解
主从Reactor,主负责分发任务,处理连接;从负责接受连接后分发的Handler进行业务处理

服务拆分的边界:
高内聚,低耦合:每个服务只完成自己职责内的任务
闭包原则:需要改变一个微服务的时候不需要修改其他服务
避免环形依赖和双向依赖
服务定义的借口要具有可扩展性

展开讲讲NIO和IO多路复用
它是同步阻塞模型;
IO多路复用的核心在于一个进程监视多个文件句柄,为了高效针对海量用户服务,要让进程可以同时处理很多个tcp连接,一旦某个句柄就绪,就可以通知程序进行相应读写操作,如果没有就绪则阻塞

IO多路复用底层函数的对比分析:select 和 poll 和 epoll
本质是让单线程监听多个文件描述符
参考再次梳理NIO的博客,这里就不写了。。只写关键字

epoll:
epoll_create
epoll_ctl
epoll_wait

水平触发
边缘触发

NIO具体在哪一步调用epoll的函数(需要说清楚那个链路)
Selector的实现是SelectorImp;它将职责委托给了不同的平给,在Linux当中就是EpollSelectorImpl,

threadLoacl一般的使用场景(我说了数据库连接),底层如何保证数据库连接不会错乱?
使用完threadlocal需要手动remove
用于实现数据隔离,每个访问该变量的线程都有一个本地副本
对象实例与threadlocal的映射关系是存放在一个map里面的

HashMap扩容机制
HashMap底层数据结构;树化;有没有线程安全的map(要说>2种)
追问concurrenthashmap底层如何保证线程安全(这里1.8说的很乱

HTTP和HTTPS的对比,带s的有什么优势
HTTPS具体的加密过程?

TCP如何保证报文的可靠传输?滑动窗口拥塞控制这些,我答得不太好
操作系统的进程和线程有啥区别
进程的通信方式

Java的动态代理分类,底层的原理,它们的区别(不熟
jdk,cglib
算法题:回文链表

小鹏汽车(很特别的一次面试,关注点和其他面试不太一样

感觉有的时候没听完整面试官的话就直接回答了

如何new一个对象
面向对象和面向过程的区别
局部变量和成员变量
分别聊封装继承多态
你说到封装要避免对外部暴露,只留下接口;请问封装有多少个访问级别(追问是不是public blabla
一个类可以继承几个类?可以实现几个接口
父类有哪些属性可以被子类继承;确定除了private修饰的其他可以继承吗?(不确定
具体介绍重载,重写
overwrite 和 override 哪个是重写哪个是重载?我说重载是overload面试官说不对比overload;然后叫我说重载和重写的区别;
final关键字的理解 修饰在变量的时候具体是怎么不可变的
final除了修饰在类和方法,还能修饰在哪里?(成员变量);
成员变量和局部变量的区别
介绍自己对String比较了解的地方(因为final的时候我扯到了,但是也没扯的很有逻辑)
为什么String被final修饰之后就是线程安全了?是在类上面就是线程安全类了吗?然后对于String的修改没说清楚
StringBuilder和StringBuffer:可改变对象的值但不需要重新new一个对象;两者性能一样吗?
Java基础还有什么能和我分享的吗(我真不知道了。。。脑子里是反射和注解但又不知道是不是java基础,也不知道
对索引的了解:开始自己从MySQL的索引拓展
最左前缀:说的有点没有逻辑
如何选择字段创建索引,为什么
如果ab两个字段都是一样高频的,如何选择索引,ab其中一个为主键和外键?

posted @ 2021-09-28 17:18  concise_d  阅读(51)  评论(0)    收藏  举报