2024.05.10高德后端一面

  • 高德
  • 一面
  • 实习
大约 14 分钟全民制作人ikun

2024.05.10高德后端一面

Java和Python各自的优势,Java底层和Python底层怎么实现跨平台的?

优势:

  • Java:静态类型、跨平台、性能优异、广泛应用、强大标准库
  • Python:简单易学、动态类型、丰富第三方库、跨平台、高级编程特性、广泛应用

跨平台:

  • Java的跨平台性是通过虚拟机实现的。由于JVM负责将字节码翻译成本地机器码,所以只需要在目标平台上安装一个适用于该平台的JVM,就可以运行相同的Java程序。

  • Python 的跨平台性是通过解释器实现的。只需要安装适用于目标平台的Python解释器,就可以在该平台上运行Python程序。

Java使用线程池的时候有哪些主要参数?如果核心线程数已满,那么新任务是进入队列等待还是直接创建线程执行?

    /**
     * 用给定的初始参数创建一个新的ThreadPoolExecutor。
     */
    public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
                              int maximumPoolSize,//线程池的最大线程数
                              long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列
                              ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
                              RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
                               ) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

一共有七个参数。

如果核心线程数已满,那么新任务的处理流程如下:

  • 如果workQueue未满,则将新任务添加到队列中等待执行。
  • 如果workQueue已满但线程数未达到maximumPoolSize,则创建新线程来执行该任务。
  • 如果workQueue已满且线程数已达到maximumPoolSize,则根据拒绝策略来处理该任务,比如直接抛出异常、使用自定义的处理器等。

###Java的线程有哪几个主要状态?哪几个状态是可能被阻塞的?

在Java中,线程有以下几种主要状态:

  1. 新建(New)状态:线程对象被创建,但还没有调用start()方法。
  2. 运行(Runnable)状态:线程已经被调用start()方法。
  3. 阻塞(Blocked)状态:线程因为某种原因停止运行,如等待I/O操作完成、获取锁等。
  4. 等待(Waiting)状态:线程处于等待状态,等待其他线程通知以继续执行。
  5. 超时等待(Timed Waiting)状态:线程处于等待状态,到达指定时间后会自动返回就绪状态。
  6. 终止(Terminated)状态:线程已经执行完毕或因异常终止。

可能被阻塞的状态包括:

  1. 阻塞(Blocked)状态:线程正在等待获取一个排他锁,当其他线程释放该锁时,该线程会重新进入就绪状态。
  2. 等待(Waiting)状态:线程正在等待另一个线程执行一个特定的动作,当该动作发生时,该线程会自动进入就绪状态。
  3. 超时等待(Timed Waiting)状态:线程正在等待一段时间,当时间到或被其他线程中断时,该线程会自动进入就绪状态。

总之,线程可以在上述几种状态之间切换,而阻塞状态是线程在运行过程中可能进入的一种特殊状态。

线程执行过程中中断是由JVM发起的还是操作系统内核发起的,线程处于运行态是否能够接受中断?

线程中断是由Java虚拟机(JVM)发起的,而不是操作系统内核。JVM会监控线程的执行状态,在特定条件下向线程发起中断信号。

当线程处于"运行"状态时,是可以接受中断的。此时JVM会向线程发送中断信号,线程的执行会被中断,进入中断处理流程。

为什么现在Thread.stop()方法不建议使用了?

  1. stop这种方法本质上是不安全的
  2. 使用Thread.stop停止线程会导致它解锁所有已锁定的监视器,即直接释放当前线程已经获取到的所有锁,使得当前线程直接进入阻塞状态

推荐使用interrupt来中断线程

Java中ReentrantLock和Synchronized有哪些区别?

区别:

  1. 用法不同:synchronized 可以用来修饰普通方法、静态方法和代码块,而 ReentrantLock 只能用于代码块。
  2. 获取锁和释放锁的机制不同:synchronized 是自动加锁和释放锁的,而 ReentrantLock 需要手动加锁和释放锁。
  3. 锁类型不同:synchronized 是非公平锁,而 ReentrantLock 默认为非公平锁,也可以手动指定为公平锁。
  4. 响应中断不同:ReentrantLock 可以响应中断,解决死锁的问题,而 synchronized 不能响应中断。
  5. 底层实现不同:synchronized 是 JVM 层面通过监视器实现的,而 ReentrantLock 是基于 AQS 实现的。

JVM中的老年代和新生代各自有哪些垃圾回收算法?

  • 在分代算法中对于不同区域采用的具体算法也是不同的,新生代存放的大部分数据是朝生夕灭的,所以新生代使用的是效率最高的复制算法

  • 而老生代使用的是标记-清除或标记-整理算法,如果标记-清除可以满足需要那么就使用效率更好的标记-清除算法,如果标记-清除算法不能满足需要就使用标记-整理算法。

了解类加载器吗?类加载器的类型有哪些?

实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。

主要有一下四种类加载器:

  • 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
  • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  • 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java类。一般来说,Java 应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。
  • 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。

HTTP工作在哪一层?TCP呢?

HTTP:应用层

TCP:传输层

介绍一下TCP和UDP?TCP的流量控制是由发送方和接收方哪一方来控制的,同问TCP的拥塞控制?如果视频会议等使用UDP,那丢包率过高会议开不下去,UDP怎么解决?

好的,我来介绍一下TCP和UDP的区别:

  1. TCP (传输控制协议)是面向连接的协议,提供可靠的数据传输。它能确保数据完整性,并能处理数据包的丢失和重发。相反,UDP (用户数据报协议)是无连接的,它只是简单地将数据包发送出去,不管对方是否能收到。

  2. 流量控制是由TCP的发送方和接收方共同控制的。发送方通过检查接收方的接收窗口大小来控制发送速率,而接收方根据自身的缓冲区容量来控制发送方的发送速率。这样可以防止接收方被淹没。相比之下,UDP没有流量控制机制。

  3. TCP的拥塞控制是由发送方单独来控制的。发送方会根据网络的拥塞程度动态调整发送速率,以防止网络拥塞。UDP没有拥塞控制机制。

  4. 对于视频会议这类对实时性要求高的应用,使用UDP会更好。因为UDP不会因为丢包而重传,可以保证数据尽快送达。即使丢包率较高,只要剩下的数据包足以维持基本画面和声音质量,也能勉强维持会议。相比之下,TCP的重传机制会导致较大的延迟,不适合实时通信。

如何解决?

介绍一下慢启动?TCP的缺点是什么?如果现在就是网络情况很差(丢包率很高),网络中数据包阻塞,超时重传等机制都失效,TCP如何解决

  1. 慢启动机制:

    • TCP连接建立时,最初发送窗口大小被设置为1个数据包。
    • 每成功收到一个ACK,发送窗口大小就会指数级增加,直到达到阈值或遇到拥塞。
    • 这种由小到大逐步增加发送速率的过程称为慢启动。
    • 慢启动可以防止连接初期就向网络发送大量数据,避免拥塞的发生。
  2. TCP的缺点:

    • 对于高延迟、高丢包的网络环境,TCP的性能会大大降低。
    • 频繁的超时重传和拥塞控制机制会导致吞吐量下降。
    • 对实时性要求高的应用(如视频会议)不太适用,会引入较大延迟。
    • 需要维护连接状态,占用较多系统资源。
  3. 在网络状况很差的情况下:

    • TCP的超时重传和拥塞控制机制可能会失效。
    • 这时可以考虑使用UDP协议,配合前向纠错(FEC)等技术来弥补丢包。
    • 另外可以尝试使用QUIC协议,它在UDP基础上提供了可靠传输、流量控制等功能,性能更好。
    • 也可以考虑使用WebRTC等实时通信框架,它们通常会自动选择最佳的传输协议。

MySQL索引了解吗?底层数据结构是什么?

索引用来加快查询速度,底层数据结构是B+树。

如果现在建立了联合索引“ABC”,查询语句条件是A = x and B > x and C = x;会走索引吗?(x不是具体值)

走a,b索引,c失效

了解MySQL的日志吗?有哪些日志?前面提到的几个日志中哪几个和MySQL的事务有关?

  • redo log 事务的持久性
  • undo log 事务的原子性
  • binlog 事务的持久性

了解MySQL底层的架构吗?

小林codingopen in new window

查询语句执行流程

问了用过的中间件有哪些?

Redis,RabbitMQ、

Redis的底层架构是什么?

Redis (Remote Dictionary Server) 是一个开源的内存数据结构存储系统,它的底层架构主要包括以下几个核心组件:

  1. 内存数据结构:Redis的数据模型是基于内存的,支持多种数据结构,如字符串、列表、集合、有序集合等。

  2. 事件驱动架构:Redis采用了事件驱动的I/O多路复用模型,使用单线程的方式处理客户端请求。利用epoll、kqueue等I/O多路复用技术,可以高效地处理大并发的客户端连接。

  3. 持久化机制:Redis提供了两种持久化机制:快照(RDB)和日志(AOF)。

  4. 复制机制:Redis支持主从复制,从库可以异步复制主库的数据,提高可用性和读性能。

  5. 集群架构:Redis Cluster是Redis的分布式解决方案,通过将数据分片存储在多个节点上,实现水平扩展。

  6. 内存管理:Redis会尽可能将数据保存在内存中,但也支持设置内存上限和淘汰策略。

了解zset吗?先不说压缩表,为什么底层使用跳表而不是B+树?

Zset底层使用压缩列表或跳表,原因:

  • redis 是纯纯的内存数据库。进行读写数据都是操作内存,跟磁盘没啥关系,因此也不存在磁盘IO了,所以层高就不再是跳表的劣势了。

  • B+树是有一系列合并拆分操作的,换成红黑树或者其他AVL树的话也是各种旋转,目的也是为了保持树的平衡。而跳表插入数据时,只需要随机一下,就知道自己要不要往上加索引,根本不用考虑前后结点的感受,也就少了旋转平衡的开销。

因此,redis选了跳表,而不是B+树。

B+树和跳表都是logn,那到底哪个更快(logn更小)?为什么B+树的查询是logn?

常用的消息队列是哪个?总结一下什么场景下会使用到消息队列?有什么好处?

RabbitMQ

消息队列在实际应用中包括如下四个场景:

  • 应用耦合:多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败;
  • 异步处理:多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间;
  • 限流削峰:广泛应用于秒杀或抢购活动中,避免流量过大导致应用系统挂掉的情况;
  • 消息驱动的系统:系统分为消息队列、消息生产者、消息消费者,生产者负责产生消息,消费者(可能有多个)负责对消息进行处理

RabbitMQ的底层架构是什么?

RabbitMQ的底层架构主要包括以下几个方面:

  1. 消息存储:

    • RabbitMQ将消息以append的方式写入磁盘文件中,以提高读写性能。
    • 文件名以".rdq"为扩展名,从0开始依次累加。
    • RabbitMQ内部使用"ets"表来记录消息在文件中的映射信息,以便快速查找。
  2. 持久化与垃圾回收:

    • RabbitMQ支持将重要消息持久化到磁盘的WAL(Write-Ahead Logging)日志文件中,确保数据可靠性。
    • 为提高性能和降低磁盘占用,RabbitMQ会定期触发垃圾回收机制,清理不再使用的队列和交换器对象。
  3. 分布式部署与高可用性:

    • RabbitMQ支持分布式部署和集群模式,通过多个节点组成集群提供更高的吞吐量和容错能力。
    • 集群节点之间会进行自动同步和故障转移,确保消息的可靠传输和持久化。

聊一下在计算机这方面自己做得比较满意的地方有什么?

做了道循环顺序执行三个线程的题,输出ABCABCABCABC…

package com.cxk;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {

    private static Lock lock = new ReentrantLock();// 通过JDK5中的Lock锁来保证线程的访问的互斥
    private static int state = 0;// 通过state的值来确定是否打印

    static class ThreadA extends Thread {

        @Override
        public void run() {
            for (int i = 0; i < 10; ) {
                try {
                    lock.lock();
                    while (state % 3 == 0) {// 多线程并发,不能用if,必须用循环测试等待条件,避免虚假唤醒
                        System.out.print("a");
                        state++;
                        i++;
                    }
                } finally {
                    lock.unlock();// unlock()操作必须放在finally块中
                }
            }
        }

    }

    static class ThreadB extends Thread {

        @Override
        public void run() {
            for (int i = 0; i < 10; ) {
                try {
                    lock.lock();
                    while (state % 3 == 1) {// 多线程并发,不能用if,必须用循环测试等待条件,避免虚假唤醒
                        System.out.print("b");
                        state++;
                        i++;
                    }
                } finally {
                    lock.unlock();// unlock()操作必须放在finally块中
                }
            }
        }

    }

    static class ThreadC extends Thread {

        @Override
        public void run() {
            for (int i = 0; i < 10; ) {
                try {
                    lock.lock();
                    while (state % 3 == 2) {// 多线程并发,不能用if,必须用循环测试等待条件,避免虚假唤醒
                        System.out.print("c");
                        state++;
                        i++;
                    }
                } finally {
                    lock.unlock();// unlock()操作必须放在finally块中
                }
            }
        }

    }

    public static void main(String[] args) {
        new ThreadA().start();
        new ThreadB().start();
        new ThreadC().start();
    }
}