Java面试考点十道题

    xiaoxiao2022-07-13  142

    第一题

    建立链接前需要server端先监听端口,因此server端建立链接前的初始状态就是listen状态,这时client端准备建立连接,先发送一个syn同步包,发送完同步包后,client端的链接状态变成SYN_SENT状态。server端收到syn后,同意建立链接,会向client端回复一个ack。

    由于tcp是双工传输,server端也会同时向client端发送一个syn,申请server向client方向建立链接。发送完ack和syn后,server端的链接状态就变成了SYN_RCVD。

    client端收到server端的ack后,client端的链接状态就变成了ESTABLISHED状态,同时,client端向server端发送ack,回复server端的syn请求。

    server端收到client端的ack后,server端的链接状态也就变成了ESTABLISHED状态。此时建立完成,双方随时可以进行数据传输。


    第二题

    jvm内存模型主要指运行时的数据区,包括五个部分:

    栈,也叫方法栈,是线程私有的,线程在执行每个方法时都会同时创建一个栈帧,用来存储局部变量表、操作栈、动态链接、方法出口等信息。调用方法时执行入栈,方法返回时执行出栈。

    本地方法栈与栈类似,也是用来保存线程执行方法时的信息,不同的是,执行java方法使用栈,而执行native方法使用本地方法栈。

    程序计数器保存着当前线程所执行的字节码位置,每个线程工作时都有一个独立的计数器。程序计数器为执行java方法服务。执行native方法时,程序计数器为空。

    栈、本地方法栈、程序计数器这三个部分都是线程独占的。

    堆是jvm管理的内存中最大的一块,堆被所有的线程共享,目的是为了存在对象实例,几乎所有的对象实例都在这里分配。当堆内存没有可用空间时,会抛出OOM异常。根据对象存活的周期不同,jvm把堆内存进行分代管理,由垃圾回收器进行对象的回收管理。

    方法区也是各个线程共享的内存区域,又叫非堆区。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,jdk1.7中的永久代和1.8中的metaspace都是方法区的一种实现。


    第三题

    关于Spring中bean的创建过程顺序:

    1.调用bean的构造方法创建bean;

    2.通过反射调用setter方法进行属性的依赖注入;

    3.如果实现BeanNameAware,会设置给Bean的name;

    4.如果实现了BeanFactoryAware,会把bean factory设置给bean;

    5.如果实现了ApplicationContextAware,会给bean设置ApplicationContext;

    6.如果实现了BeanPostProcessor接口,则执行前置处理方法;

    7.实现了InitializingBean接口的话,执行afterProperties方法;

    8.执行自定义的init方法;

    9.执行BeanPostProcessor接口的后置处理方法。


    第四题

    线程池提交任务的步骤:

    1.判断线程池中的线程数是否大于设置的核心线程数,创建核心线程执行任务;

    2.判断缓冲队列是否满了,如果没满,放入队列等待执行;

    3.判断是否达到了线程池设置的最大线程数,如果没有,就创建新的线程来执行任务;

    4.如果满了,执行拒绝策略;

     

    1.向线程池提交任务时,会首先判断线程池中的线程数是否大于设置的核心线程数,如果不大于,就创建一个核心线程来执行任务。

    2.如果大于核心线程数,就会判断缓冲队列是否满了,如果没有就放入队列,等待线程空闲时执行任务了。

    3.如果队列已经满了,则判断是否达到了线程池设置的最大线程数,如果没有达到,就创建新线程来执行任务。

    4.如果已经达到最大线程数,则执行指定的拒绝策略。


    第五题

    synchronized和ReenTrantLock的区别

    1.两者都是可重入锁

    “可重入锁”的概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才可能释放锁。

    2.synchronized依赖于JVM而ReenTrantLock依赖于API

    synchronized是依赖于JVM实现的,虚拟机团队在JDK1.6为synchronized关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的。ReenTrantLock是JDK层面实现的,(也就是API层面,需要lock()和unlock方法配合try/finally语句块来完成),所以我们可以查看它的源代码,来看他是如何实现的。

    3.ReenTrantLock比synchronized增加了一些高级功能

    主要是1.等待可中断;2.可实现公平锁;3.可实现选择性通知(锁可以绑定多个条件)

    中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。

    ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获的锁。ReenTrantLock默认情况是非公平的,可以通过ReenTrantLock类的ReentrantLock(boolean fair)构造方法来制定是否公平的。

    4.synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与new Condition()方法。Condition是KDL1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。在使用notify/notifyAll()方法进行通知时,被通知的线程是由JVM选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程,这样会造成很大的效率问题。而Condition实例的signalAll()方法只会唤醒注册在该Condition实例中的所有等待线程。


    第六题

    B+树比B树更适合做索引

    A.叶节点之间有指针相连,B+树更适合范围检索;

    B.非叶节点只保存关键字和指针,同样大小非叶节点,B+树可以容纳更多的关键字,可以降低树高,查询时磁盘读写代价更低;

    C.B+树的查询效率比较稳定。任何关键字的查找必须走一条从根节点到叶子结点的路。所有关键字查询的路径长度相同,效率相当。


    第七题

    动态规划的场景需要满足三个条件:

    第一、子问题的求解必须按顺序进行的。

    第二、相邻的子问题之间有关联关系。

    第三、最后一个子问题的解就是初始问题的解。

     

    分冶法的场景需要满足三个要求:

    第一,可以分解为子问题。

    第二、子问题的解可以合并为原问题的解。

    第三、子问题没有关联。


    第八题

    A.数据不一致时,如果服务对耗时不是特别敏感可以增加重试,如果服务对耗时敏感可以通过异步补偿任务来处理失败的更新,能保证最终一致性就可以

    B.缓存挂掉时,所有的请求都会穿透到db,使用快速失败,减少DB的瞬间压力

    D.查询数据不存在时,在缓存中保存一个空对象进行标记,防止相同ID再次访问db

    答案解析:

    1.数据不一致的问题,可以说只要使用缓存,就要考虑如何面对这个问题。缓存不一致产生的原因一般是主动更新失败,例如更新db后,更新redis因为网络原因请求超时。或者是异步更新失败导致。

    解决办法:如果服务对耗时不是特别敏感可以增加重试,如果服务对耗时敏感可以通过异步补偿任务来处理失败的更新,或者短期的数据不一致不会影响业务,那么只要下次更新时可以成功,能保证最终一致性就可以了。

     

    2.缓存穿透。产生这个问题的原因可能是外部的恶意攻击,例如,对用户信息进行了缓存,但恶意攻击者使用不存在的用户ID频繁请求接口,导致查询缓存不命中,然后穿透db查询依然不命中。这时会有大量请求穿透缓存访问到db。

    解决办法:一是对不存在的用户,在缓存中保存一个空对象进行标记,防止相同ID再次访问db。不过有时这个方法并不能很好解决问题,可以导致缓存中存储大量无用数据。

    另一个方法就是使用bloomfilter过滤器,bloomfilter的特点是存在性检测,如果bloomfilter中不存在,那么数据一定不存在,如果bloomfilter中存在,实际数据也有可能会不存在。非常适合解决这类问题。

     

    3.缓存击穿,就是某个热点数据失效时,大量针对这个数据的请求会穿透到数据源。解决这个问题可以使用互斥锁更新,保证同一个进程中针对同一个数据不会并发请求到db,减少db压力。

    另一个方法就是使用随机退避方式,失效时随机sleep一个很短的时间,再次查询,如果失败再执行更新。

    还有一个方法就是针对多个热点key同时失效的问题,可以在缓存时使用固定时间加上一个小的随机数,避免大量热点key同一时刻失效。

     

    4.缓存雪崩。产生的原因是缓存挂掉,这时所有的请求都会穿透到db。

    解决方法:一个是使用快速失败的熔断策略,减少db的瞬间压力。

    另一个就是使用主从模式和集群模式来尽量保证缓存服务的高可用


    第九题.

    Kafka

    Kafka只保证一个分区内的消息有序,不能保证一个topic的不同分区之间的消息有序。

    为了保证较高的处理效率,所有的消息读写都是在主patition中进行,其他副本分区只会从主分区复制数据。Kafka会在Zookeeper上针对每个Topic维护一个成为ISR(in-syne replica),就是已同步的副本集。如果某个主分区不可用了,Kafka就会从ISR集合中选择一个副本作为新的主分区。

    消息的发送有三种方式:同步、异步以及oneway。同步模式下后台线程中发送消息时同步获取结果,这也是默认模式。

    异步的模式允许生产者批量发送数据,可以极大的提高性能,但是会增加丢失数据的风险。oneway模式只发送消息不需要返回发送结果,消息可靠性最低,但是低延迟、高吞吐、适用于对可靠性要求不高的场景。


    第十题.

    关于MySQL的锁说法

    MyISAM的特点是支持全文索引,查询效率比较高,缺点是不支持事务、使用表级锁;

    InnoDB使用行级锁,有可能会出现死锁,并发访问效率比较高;

    共享锁也就是读锁,其他事务可以读,但不能写;

    排他锁就是写锁,其他事务不能读取,也不能写;

     

    答案解析:

    MyIASAM使用表级锁,InnoDB使用行级锁。表锁开销小,加锁快,不会出现死锁;但是锁的粒度大,发生锁冲突的概率高,并发访问效率低。行级锁开销大,加锁慢,有可能会出现死锁,不过因为锁的粒度最小,发生锁的冲突概率低,并发访问效率高。

    共享锁就是读锁,其他事务可以读,但不能写。mysql可以通过lock in share mode语句显示使用共享锁。

    排他锁就是写锁,其他事务不能读取,不能写。对于update、delete、insert语句,InnoDB会自动给涉及的数据集加排他锁,或者使用select for update显示使用排他锁


    以上来自拉勾网。萌发兴趣特此摘抄做笔记。

    最新回复(0)