面试复盘 | 携程Java后端一面+二面+HR面(已意向)

一面(8.19)

  • 聊聊你研究生的课题(智能机器人相关)

  • 几个做的啊,分工是什么样的

  • 如果以后想把你们做的东西商业化 产品化,怎么去收集用户的信息?
    云?用户定期上传数据到云服务器,公司负责处理这些数据(不懂。朝着云的方向扯了一些)

  • 进程与线程的区别
    调度:进程是资源管理分配的基本单位,线程是程序执行调度的基本单位。
    切换:线程上下文切换比进程上下文切换要快得多。
    拥有资源: 进程是拥有资源的一个独立单位,线程不拥有系统资源,但是可以访问隶属于进程的资源。
    系统开销: 创建或撤销进程时,系统都要为之分配或回收系统资源,如内存空间,I/O设备等,OS所付出的开销显著大于在创建或撤销线程时的开销,进程切换的开销也远大于线程切换的开销。

  • 线程一般存在什么地方?线程里有什么东西?
    额。有没有大佬懂的评论区指导一波,当时卡住了。。

  • 说说看自己对LRU的理解。实现思路
    最近最少使用机制,是操作系统中用来实现页面替换的一种优秀的算法。本质上是一种双向链表的数据结构,用哈希表来辅助维护里面的节点位置。插入时先看看哈希表里有键值冲突:没有的话,用键值新构造一个节点,插入到链表中,并移到链表头,再看看有没有越界,越了的话要把尾部节点删除;同时放入哈希表中。有的话,用新值代替旧值,然后移到链表头。若读取,判断键对应的节点存不存在,存在的话读值并把节点移到头部。其中涉及到把节点移到链表头、删除链表尾的操作。附上实现,面试官让口述:

    public class Solution {
      /**
       * lru design
       * @param operators int整型二维数组 the ops
       * @param k int整型 the k
       * @return int整型一维数组
       */
      class DoubleListNode {
          int key;
          int val;
          DoubleListNode pre;
          DoubleListNode next;
          DoubleListNode() {}
          DoubleListNode(int key, int val) {
              this.key = key;
              this.val = val;
          }
      }
    
      DoubleListNode head = new DoubleListNode(-1, -1);
      DoubleListNode tail = new DoubleListNode(-1, -1);
      HashMap<Integer, DoubleListNode> map = new HashMap<>();
    
      public int[] LRU (int[][] operators, int k) {
          // write code here
          ArrayList<Integer> ans = new ArrayList<>();
          head.next = tail;
          tail.pre = head;
          int size = 0;
    
          for(int i = 0; i < operators.length; i++){
              int tmp_key = operators[i][1];
              DoubleListNode node = map.get(tmp_key);
              if(operators[i][0] == 1){
                  if(node == null){
                      DoubleListNode new_node = new DoubleListNode(tmp_key, operators[i][2]);
                      map.put(tmp_key, new_node);
                      add2head(new_node);
                      size++;
                      if(size > k){
                          DoubleListNode weiba = removeTail();
                          map.remove(weiba.key);
                          size--;
                      }
                  }else{
                      node.val = operators[i][2];
                      add2head(node);
                  }
              }else{
    //                 node = map.get(tmp_key);
                  if(node == null) ans.add(-1);
                  else{
                      ans.add(node.val);
                      move2head(node);
                  }
              }
          }
          int[] res = new int[ans.size()];
          for(int i = 0; i < ans.size(); i++){
              res[i] = ans.get(i);
          }
          return res;
      }
    
      void move2head(DoubleListNode node){
          removeNode(node);
          add2head(node);
      }
    
      void removeNode(DoubleListNode node){
          node.pre.next = node.next;
          node.next.pre = node.pre;
      }
    
      void add2head(DoubleListNode node){
          node.pre = head;
          node.next = head.next;
          head.next.pre = node;
          head.next = node;
      }
    
      DoubleListNode removeTail(){
          DoubleListNode weiba = tail.pre;
          removeNode(tail.pre);
          return weiba;
      }
    }
  • 你是怎么理解线程安全的,为什么会出现线程安全的问题呢
    线程安全就是说多线程访问同一段代码,不会产生不确定的结果 。
    理解线程安全的两个方面:执行控制和内存可见。
    执行控制的目的是控制代码执行(顺序)及是否可以并发执行。
    内存可见控制的是线程执行结果在内存中对其它线程的可见性。根据Java内存模型的实现,线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存。
    具体的就是去保证原子性、有序性和可见性。
    为什么,我当时回答的是多线程操作共享资源时无法保证原子性、有序性和可见性,面试官让结束后再思考一下,请教大佬们指点。

  • 怎么解决,有哪些方法
    加锁,volatile,原子类,并发包里的一堆东西,按需所求。

  • 同步锁有什么问题啊,jdk对此做了啥改进
    java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在用户态与内核态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间、专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值和变量等,以便内核态调用结束后切换回用户态继续工作。
    如果线程状态切换是一个高频操作时,会消耗很多CPU处理时间;对于那些需要同步的简单代码块,获取锁、阻塞挂起操作消耗的时间比用户代码执行的时间还要长,这种同步策略显然得不偿失的。
    synchronized会导致竞争不到锁的线程进入阻塞状态,所以说它是java语言中一个重量级的同步操纵,被称为重量级锁,为了缓解上述性能问题,引入了轻量锁与偏向锁,默认启用了自旋锁,他们都属于乐观锁。
    锁升级:最开始的时候处于一个无锁的状态,加锁时首先是偏向锁,当前获取到锁资源的线程,会优先让它再去获取这个锁,如果他没有获取到这个锁,就会升级到一个轻量级锁,比如用CAS来尝试获取锁,如果没有获取成功就自旋,如果自旋了一定次数后,就会升级到synchronized这个重量级锁,保证了性能。

  • 自旋锁会消耗cpu吗,为啥
    线程自旋是需要消耗cpu的,说白了就是让cpu在做无用功,如果一直获取不到锁,那线程也不能一直占用cpu自旋做无用功,所以需要设定一个自旋等待的最大时间。

  • 反问:携程主要有哪些业务,对我做个简短的评价

    小结:感觉这个面试官是秋招第一次面试,一开始就说:你等几分钟,我研究一下你的简历hhh,后面问问题的时候每问一个问题都要等个几十秒,嘴上还咕哝着:我想想问个什么好呢,哈哈哈太可爱了。整体上回答的还可以,问的不深。

二面(8.25)

  • 手写一个堆排序
    上来就来这有点难顶,写了二十多分中。。。一开始想用PrioityQueue偷懒的被面试官制止了(hh他笑着说:你住手),要求自己用数组模拟堆来写,真不容易,还好不久前看过(背过)。

    class Solution {
      public int[] sortArray(int[] nums) {
          heapSort(nums);
          return nums;
      }
    
      public void heapSort(int[] nums) {
          int len = nums.length - 1;
          buildMaxHeap(nums, len);
          for (int i = len; i >= 1; --i) {
              swap(nums, i, 0);
              len -= 1;
              maxHeapify(nums, 0, len);
          }
      }
      public void buildMaxHeap(int[] nums, int len) {//构建堆
          for (int i = len / 2; i >= 0; --i) {
              maxHeapify(nums, i, len);
          }
      }
      public void maxHeapify(int[] nums, int i, int len) {
          for (; (i << 1) + 1 <= len;) {
              int lson = (i << 1) + 1;
              int rson = (i << 1) + 2;
              int large;
              if (lson <= len && nums[lson] > nums[i]) {
                  large = lson;
              } else {
                  large = i;
              }
              if (rson <= len && nums[rson] > nums[large]) {
                  large = rson;
              }
              if (large != i) {
                  swap(nums, i, large);
                  i = large;
              } else {
                  break;
              }
          }
      }
      private void swap(int[] nums, int i, int j) {
          int temp = nums[i];
          nums[i] = nums[j];
          nums[j] = temp;
      }
    }
    

```

  • 问商城项目
    简历写了一个看视频跟着做的商城网站(SSM+curd),没啥好问的,面试官问了一些开放题:怎么避免库存超卖,redis可以用在什么地方,有什么作用,怎么保证用户信息的安全等等;
  • 对threadLocal的理解、底层原理
    ThreadLocal是 JDK java.lang 包下的一个类,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,并且不会和其他线程的局部变量冲突,实现了线程间的数据隔离。ThreadLocal的应用场景主要有:
    (1)保存线程上下文信息,在需要的地方可以获取
    (2)线程间数据隔离
    (3)数据库连接;
    底层原理:每个线程的内部都维护了一个 ThreadLocalMap,它是一个键值对数据格式,key 是一个弱引用,也就是 ThreadLocal 本身,而 value 是强引用,存的是线程变量的值。也就是说 ThreadLocal 本身并不存储线程的变量值,它只是一个工具,用来维护线程内部的 Map,帮助存和取变量。
  • 使用threadLocal会出现什么问题
    ThreadLocal 在 ThreadLocalMap 中是以一个弱引用身份被 Entry 中的 Key 引用的,因此如果 ThreadLocal 没有外部强引用来引用它,那么 ThreadLocal 会在下次 JVM 垃圾收集时被回收。这个时候 Entry 中的 key 已经被回收,但是 value 又是一强引用不会被垃圾收集器回收,这样 ThreadLocal 的线程如果一直持续运行,value 就一直得不到回收,这样就会发生内存泄露。
  • ThreadLocal的key是哪种引用类型?为啥这么设计?
    ThreadLocalMap 中的 key 是弱引用,而 value 是强引用才会导致内存泄露的问题
    1. 若key 使用强引用:这样会导致一个问题,引用的 ThreadLocal 的对象被回收了,但是 ThreadLocalMap 还持有 ThreadLocal 的强引用毫无意义,如果没有手动删除,ThreadLocal 不会被回收,则会导致内存泄漏。
    2. 若key 使用弱引用:这样的话,引用的 ThreadLocal 的对象被回收了,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal 也会被回收。value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候会被清除(清理key为null的记录),使用完了ThreadLocal最好在手动的remove一下。
    3. 比较以上两种情况:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果都没有手动删除对应 key,都会导致内存泄漏,但是使用弱引用可以多一层保障,弱引用 ThreadLocal 不会内存泄漏,对应的 value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候被清除,算是最优的解决方案。
  • 什么是内存泄漏
    内存泄漏是指用户向系统申请分配内存进行使用,可是使用完了以后却没有释放,结果那块内存用户不能访问(也许你把它的地址给弄丢了),而系统也不能再把它分配给需要的程序。
  • Java有哪些引用类型?分别有哪些使用场景
    1. 强引用,任何时候都不会被;垃圾回收器回收,如果内存不足,宁愿抛出OutOfMemoryError
      使用场景:我们平常大部分使用的场景都是使用了强引用,比如new创建对象,反射获得一个对象等。
    2. 软引用,只有在内存将满的时候才会被垃圾回收器回收,如果还有可用内存,垃圾回收器不会回收。
      软引用可以和一个引用队列进行关联,如果这个软引用的对象被垃圾回收,就会将这个软引用加入到关联的队列中去。 可用于高速缓存。
    3. 弱引用(WeakReference),生命周期更短,只要垃圾回收器运行,就肯定会被回收,不管还有没有可用内存。
      使用场景: 弱引用用于生命周期更短的,对内存更敏感的场景中,比如占用内存很大的Map,java api中就提供了WeakHashMap使用,就会是的大Map被及时清理掉。
    4. 虚引用(PhantomReference),虚引用等于没有引用,任何时候都有可能被垃圾回收。虚引用必须和引用队列联合使用,引用队列的作用和软弱引用一样。
      使用场景: 我觉得他的使用场景应该在判断一个对象是否被垃圾回收了,什么时候引用队列有新的引用入队了,就说明他被回收了。
  • 反问:部门业务(火车票、机票),技术栈介绍

    小结:二面主要考察的还是算法与基础知识,不过问的没那么广了,主要对ThreadLocal相关的技术原理展开,自己答的一般,平时确实没用过这个东西,只能硬着头皮去讨论,不过面试官似乎很有耐心,先等我写了好久的代码,然后还给我解释自己不会的地方,很nice

HR面(9.3)

  • 自我介绍
  • 项目介绍
  • 未来对项目的更新迭代有哪些打算
  • 跟小伙伴合作的时候出现过分歧吗?怎么解决
  • 老家在哪,家庭成员
  • 哪位家人对自己影响最大,为什么
  • 为什么想来上海,携程对你吸引最大的点在哪里
  • 手里有其他公司的offer吗
  • 你对之前的两位面试官印象如何,简单评价一下(我来评价面试官?)
  • 想不想知道他们对你的评价?(hh好可爱的hr)
  • 反问:未来部门分配,导师制度等

总结:携程整个流程非常顺利,基本上笔试以及每一轮的面试之间都是个了一周左右。面试官看起来都像是35左右的大哥,对候选人很友好,至少态度还是很礼貌的,能感觉到对方还是有一些期待的!问的问题还算常规,一面广度,二面深度,HR面综合。(HR的声音太好听了,我说什么她都回答:好呀)

面试中遇到的问题(没回答好的问题)

  1. 关于线程内部有哪些东西,线程的本质以及它和jvm之间的关系
  2. redis在交易类系统中一般会有哪些应用,当时只回答了把热点数据存redis,应该还有很多其他的应用
#面试复盘##面经##携程##Java工程师#
全部评论
线程和进程在Linux里都是用进程描述符(结构体task_strut)来表示的,在Linux中pthread_create是封装了系统调用clone,和fork一样最终都是调用do_fork函数,会创建一个新的进程描述符。
4 回复 分享
发布于 2021-09-12 19:51
请问这是提前批吗
1 回复 分享
发布于 2021-09-12 13:50
这面经太优秀了吧
1 回复 分享
发布于 2021-09-13 00:37
哈哈哈 一个部门的
点赞 回复 分享
发布于 2021-09-12 09:10
楼主三面和意向之间隔多久
点赞 回复 分享
发布于 2021-09-12 09:31
点赞 回复 分享
发布于 2021-09-12 12:01
请问是一二三面连着面试吗?
点赞 回复 分享
发布于 2021-09-12 12:16
lru页面替换算法代价太高。一般都是模拟实现的老化算法和工作集时钟算法。像redis这种也是类lru。真正实现并不会采用算法中那种,太消耗资源。
点赞 回复 分享
发布于 2021-09-12 13:50
机票部门的😂我也是这个hr小姐姐,不过我是上周五面试的,还在等后续
点赞 回复 分享
发布于 2021-09-12 15:39
你们都知道部门吗,我hr面为啥hr告诉我还没分部门啊。。。。。
点赞 回复 分享
发布于 2021-09-12 16:06
手写堆排序
点赞 回复 分享
发布于 2021-09-12 16:51
欢迎来做同事!
点赞 回复 分享
发布于 2021-09-12 16:55
请问hr面是怎么通知的呀?是邮件还是电话呢?
点赞 回复 分享
发布于 2021-09-12 19:30
请问你通过那个法大大平台签署offer了吗?那个法大大不太靠谱的亚子
点赞 回复 分享
发布于 2021-09-12 19:47
羡慕这个八股文水平😭我能理解到这个程度就好了
点赞 回复 分享
发布于 2021-09-12 20:29
怎么感觉都是多线程,还问其他八股文了吗🤔
点赞 回复 分享
发布于 2021-09-12 20:45
要笔试吗?看了面试感觉我又行了。但想想美团的笔试我又没了😂
点赞 回复 分享
发布于 2021-09-12 21:28
老哥,有说定级嘛。明天也要hr面了,前两天加面了,不知道怎么问hr我的定级
点赞 回复 分享
发布于 2021-09-12 22:38
理想汽车Java岗位考虑吗
点赞 回复 分享
发布于 2021-09-12 23:06
请问楼主投递的是啥部门呢
点赞 回复 分享
发布于 2021-09-13 00:11

相关推荐

11-12 23:03
武汉大学 C++
开心的豹子最喜欢夏天:C C有技术壁垒和学历壁垒,Java是个人都能学
投递牛客等公司
点赞 评论 收藏
分享
80 463 评论
分享
牛客网
牛客企业服务