java后端的秋招经历(含面经和答案)

java后端的秋招经历

关于秋招其实没有太多的面试经历,但确实在面试中学到了很多,只能给出一些小小建议,不要害怕面试,当面试多了就形成一套体系,然后不断往你自己的体系中丰富筹码面试后一定要及时总结,查漏补缺
本文主要总结了招银,中兴,美团,猿辅导,电信云,携程的面试经历,希望对大家有点帮助(具体面试时间没怎么记录,大家随便看看就好~)

招银

大概6月份开始投的提前批,第一家投了招银,当时其实就刚学完java框架,项目过了一遍,刷了一些题就去面试了。(具体学习过程可以看这篇博文非科班的java学习之路
笔试:选择题还有3道编程题,一道很简单,一道是以填空的形式出的,就大概框架有,但会有几行空的代码需要填,推敲一下也不难,最后一道有一点难度,但提前批的总体都比较简单。
由于笔试成绩比较好,直接pass了电话面到了视频面。
视频面
1.手撕多线程,一个线程输出1,一个线程输出2,两个线程交替输出,最后结果1,2,1,2····
网络上有挺多的,可以自己去搜索一下线程交替执行

2.手撕连接数据库

Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(url,username,password);
Statement stmt = conn.createStatement();
String sql = "SELECT id, name, url FROM websites";
ResultSet rs = stmt.executeQuery(sql);

3.springboot常用注解
传送门来啦Spring相关注解

时间太久了,记不太清,印象深刻的就这几个问题,其他的还是比较常规,视频面很基础,但太基础了,有种大学了让你背出师表一样,有印象但忘了。
招银由于基础不扎实挂了,回来赶快复习了一遍基础。

中兴

中兴提前批没有笔试,直接筛完简历现场面试。
1.手撕快排

分治法:比大小,比分区
从数组中取出一个数,作为基准数;将比这个数大或等于的数全放在他的右边,小于他的数放在他左边;对左右区间重复,直到各区间只剩一个数
挖第一个坑作为基准数
从后往前找,找到比基准数小的数挖坑,并把该数放到前一个坑位(即第一个基准数坑位)
从前往后找,找到比基准数大的数挖坑,并把该数放到前一个坑位(即第二个比基准数小的坑);重复,直到前后碰头,将基准数放到最后那个坑位。至此,比基准数小的都在左边,比基准数大的都在右边。然后对左右区间循环,直到区间剩一个数。
  Public void sorted(int[] arr,int start,int end){
    if(start<end){
       int index=find(arr,start,end);
       sorted(arr,start,index-1);
       sorted(arr,index+1,end);
    }
  }
  Public int find(int[] arr,int start,int end){
    int i=start;
    int j=end;
    int x=arr[start];
    while(i<j){
       //从后向前找比基准值小的值,找到后挖出此数据填到前一个坑中
       while(i<j&&arr[j]>=x){
         j--;
       }
       if(i<j){
         arr[i]=arr[j];
         i++;
       }
      //从前向后找比基准值大或等的值,找到后挖出此数据填到前一个坑中
       while(i<j&&arr[i]<x){
         i++;
       }
       if(i<j){
          arr[j]=arr[i];
          j--;
       }
   }
   arr[i]=x;//把基准数填入最后一个坑中
   return i;
 }

2.项目中用到最复杂的SQL语句写一下
这个看自己的个人项目啦~

3.项目中如何防止CRSF攻击(本人准备的项目是web这边的论坛项目,项目内容可以参考这篇博文
CSRF攻击:某网站盗取cookie中的登陆凭证,模拟用户身份访问服务器,利用表单向服务器提交数据来谋取利益。一般在提交表单时发生。
Spring Security防止CSRF攻击,Security会在表单中生成一个隐藏的tocken,表单中就有一个随机tocken,每次请求都不一样。

4.如何保证用户登录安全
这个问题其实很广,可以聊到MD5双重加密,以及session和cookie的安全性问题。MD5双重加密主要针对用户密码,不仅要在传递时加密,对于存储在数据库中也要加密,这样能保证即使数据库被入侵也不会泄漏密码。Session 不安全的根本原因其实就是二点,第一个就是会话传递机制导致的(Cookie 和 URL参数)劫持问题,另外就是会话固定的问题。
解决方案:

  1. 仅仅使用 Cookie 来存放 Session ID;Session的数据时保存在服务器端的,Session ID 可以使用 Cookie 和 URL 参数来传递,相对来说,Cookie 更安全( URL 暴露的频率更高),假如你不考虑 Cookie 被禁用的问题,你应该只使用 Cookie 来传递。
  2. Cookie 和 Session 的过期时间保持一致。Session 的存活时间是“变化的”,只要会话一初始化,Session 文件的修改时间就会更新,换句话说,设置该 GC 时间为 2 小时过期,但是只要用户间隔 2 小时一直保持会话更新(比如刷新页面),则 Session 一直会存在。 GC 到期后,由 PHP 来控制删除 Session 文件,但是并不是每次就会删除过期文件,所以不能依赖它(但是可以通过自定义 Session 管理器来实现这个机制)。 考虑到 Session GC 不可依赖,所以间接的可以设置 Cookie 对应的过期时间(session.cookie_lifetime 指令)等于 GC 时间,这样一到过期时间,由于 Cookie 失效了,等同于 Session 也失效了(虽然 Session 对应的文件并没有删除,假如攻击者知道 Session ID 还是会带来安全问题)。

另外一个重点就是分布式session的问题如下:

  1. Nginx:负载均衡 分布式session无法跨域的问题
  2. 粘性session:同一个id分给同一个机器,但这样无法保证负载均衡
  3. 同步,将各个服务器的session同步,但这样服务器的解耦不够
  4. 共享session,单独拿一个服务器放session,这样如果服务器崩了就全部无法获得session
  5. 存在cookie或数据库 存入redis

5.多平台登录如何保证用户数据安全
没有确切答案,楼主基于自己的项目做了相关的一些回答。关于多平台登录问题,在项目中主要利用Security实现系统认证和授权。Security是基于filter拦截大量请求,只需要写一个类就可以完成。
编写confige类继承WebSecurityConfigureAdapter类
重写configure的各种方法,传的参数不同:
委托模式:
AuthenticationManager:认证的核心接口
AuthenticationManagerBuilder:用于构建AuthenticationManager对象的工具
ProviderManager:AuthenticationManager接口的默认实现类
providerManager将认证委托给AuthenticationProvider(每个AuthenticationProvider负责一种登陆方式,密码,QQ,微信等);在这里就可以解决多平台登录的问题。
Authentication:用于封装认证信息的接口,不同的实现类代表不同类型的认证信息
认证完之后会把认证信息封装到token里,token会被security的filter获取,存储到securityContext里,判断有没有权限都是从securityContext取出判断。所以想使用我们自己的认证,需要把结果存在SecurityContext里。
详细内容也可以参考博文Spring Security相关Spring Security和用户权限控制

6.Redis用作缓存时怎么和数据库保持一致性?
答案参考数据库和redis数据的一致性

7.kafka在项目中怎么用的?
用作系统消息通知,详情参考项目相关讲解Kafka异步消息队列相关

美团

一面

1.tcp/ip架构
OSI七层模型:
应用层:微计算机用户提供接口和服务
表示层:数据处理(编码解码,加密解密等)
会话层:管理(建立,维护,重连)通信会话
传输层:管理端到端的通信连接
网络层:数据路由(决定数据在网络的路径)
数据链路层:管理相邻节点之间的数据通信
物理层:数据通信的光电物理特性

TCP/IP四层模型
应用层:HTTP/FTP/…
传输层:TCP/UDP
网络层:IP/ICMP
网络接口层:Ethernet/ARP/RARP

2.jvm模型,垃圾回收算法
这应该算是基础问题了,jvm模型有:堆,栈,方法区,本地方法栈,程序计数器。
垃圾回收算法

3.数据库隔离级别,Innodb和myIsam的区别
隔离级别:读未提交,读已提交,可重复读,串行化
MyISAM和InnoDB区别:

MyISAMInnoDB
事务支持不支持支持
数据行锁定不支持支持
外键约束不支持支持
全文索引支持不支持
表空间大小较小较大,约为2倍

MyISAM:节约空间 速度较快
InnoDB:安全性高,事务的处理,多表多用户操作

4.可重复读的MVCC和读已提交的MVCC的区别
MVCC:多版本并发控制:在使用读已提交和可重复读隔离级别的事务在执行普通的select操作时访问记录的版本链的过程,可以使不同事务的读写,写读并发执行,从而提升系统性能,这两种隔离级别的不同点在于生成readView的时机不同,读已提交在每次进行普通select操作前都会生成一个ReadView,而可重复读只在第一次进行普通select操作前生成一个readView,之后的操作都重复使用这个

5.final修饰的方法可不可以重载
可以重载,不可以重写

6.倒数第k个链表节点
很经典的一道题

 public static Node FindKthToTail(Node head, uint k)
    {
        Node ahead = head;
        Node behind = null;

        for (int i = 0; i < k - 1; i++)
        {
            ahead = ahead.Next;
        }

        behind = head;

        while (ahead.Next != null)
        {
            ahead = ahead.Next;
            behind = behind.Next;
        }

        return behind;
    }
二面

1.怎么优化b+树
这算是一个开放性问题,其实没有啥标准答案,面试官出题的目的主要是想了解你对B+树的掌握程度,B+树因为“矮胖”,数据量大时,查询速度变快了,但插入速度变慢了,如何让插入速度变快?楼主当时是说的分库分表,但其实对B+树本身没有进行优化,回答得不好,有同学有比较好的回答可以评论一下,大家一起学习呀~
在这里插入图片描述
2.最近看的书,在书中学的和实践的差别
这个就自己根据自己情况回答啦~

3.项目单元测试的覆盖率
略~

4.kafka消息的延迟时间
kafka的重点问过的几个问题也写了一篇博客kafka相关知识点,大家参考一下呀~

猿辅导

一面

1.删除链表倒数第K个节点(力扣19)
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.

 public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode fast=head;
        ListNode slow=head;
        ListNode res=slow;
        while(n>0){
            fast=fast.next;
            n--;
        }
        if(fast==null) return res.next;
        while(fast.next!=null){
            slow=slow.next;
            fast=fast.next;
        }
        slow.next=slow.next.next;
        return res;
    }

2.删除排序链表中的重复元素(力扣82)
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。

示例 1:
输入: 1->2->3->3->4->4->5
输出: 1->2->5

public ListNode deleteDuplicates(ListNode head) {
        if(head==null) return head;
        if(head.next==null) return head;
        ListNode pre=new ListNode(-1);
        ListNode res=pre;
        ListNode cur=head;
        pre.next=head;
        while(cur!=null&&cur.next!=null){
            if(pre.next.val==cur.next.val){
                while(cur.next!=null&&cur.next.val==pre.next.val){
                    cur=cur.next;
                }
                pre.next=cur.next;
                cur=cur.next;
            }else{
                pre=pre.next;
                cur=cur.next;
            }
        }
        return res.next;
    }

3.java集合类
参考博文:java集合类相关

二面

1.合并K个有序链表(力扣23)
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

public ListNode mergeKLists(ListNode[] lists) {
        PriorityQueue<ListNode> queue=new PriorityQueue<>((v1,v2)->v1.val-v2.val);
        for(ListNode node:lists){
            if(node!=null){
                queue.add(node);
            }
        }
        ListNode res=new ListNode(0);
        ListNode temp=res;
        while(!queue.isEmpty()){
            ListNode node=queue.poll();
            temp.next=node;
            temp=temp.next;
            if(node.next!=null){
                queue.add(node.next);
            }
        }
        return res.next;
    }

2.奇偶链表(力扣328)
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL

public ListNode oddEvenList(ListNode head) {
        if(head==null) return head;
        ListNode odd=head.next;
        ListNode temp=odd;
        ListNode res=head;
        while(temp!=null && temp.next!=null){
            res.next=temp.next;
            res=res.next;
            temp.next=res.next;
            temp=temp.next;
        }
        res.next=odd;
        return head;
    }

3.Spring的启动过程
Spring的启动过程

4.Spring事务的传播机制
Spring支持编程式事务管理以及声明式事务管理两种方式。

  1. 编程式事务管理
    编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
  2. 声明式事务管理
    声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
    编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。
    显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。
    事务的传播机制

事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。
常用的事务传播机制如下:

PROPAGATION_REQUIRED
Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行
PROPAGATION_REQUES_NEW
该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可
PROPAGATION_SUPPORT
如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务
PROPAGATION_NOT_SUPPORT
该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
PROPAGATION_NEVER
该传播机制不支持外层事务,即如果外层有事务就抛出异常
PROPAGATION_MANDATORY
与NEVER相反,如果外层没有事务,则抛出异常
PROPAGATION_NESTED
该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。

传播规则回答了这样一个问题:一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行。

5.Spring事务的原理
Spring的事务是通过Spring AOP实现的,aop面向切面编程,关键在于代理模式,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。动态代理可以减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
**JDK代理:**基于接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。

Proxy.newProxyInstance(ClassLoader,Interfaces,InvocationHandler);

CGLIB动态代理:如果代理类没有实现 InvocationHandler 接口(或者说是基于父子类的),那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的

6.Spring事务是否可以用于静态方法中?
@Transactional适用于非静态方法,但不适用于任何特定原因的静态方法。将方法或类注释为@Transactional并使其成为Spring Bean时,Spring有效地为该类创建代理(使用JDK动态代理或CGLIB代理).这意味着无论何时使用您的类(来自Spring托管代码),都不是您的代码立即被调用,而是首先执行所需操作的代理,然后调用您的代码(在缓存支持的情况下)甚至可能根本不会调用代码.
这里要记住的一个关键是调用代码(如果你愿意,调用站点)根本不会改变,并且JVM使用相同的字节码调用所需的目标方法(代理方法)(invokevirtual)或调用接口).
考虑到这一点,不支持静态的原因在于无法为静态方法创建代理!当然Java Dynamic Proxies不能这样做,CGLIB也不能.支持这样的特性需要改变调用代码的字节码,因为调用静态方法是通过字节码中的invokestatic实现的,该方法对目标方法进行硬编码.

只在public方法上生效?

当采用代理来实现事务时,(注意是代理),@Transactional注解只能应用在public方法上。当标记在protected、private、package-visible方法上时,不会产生错误,但也不会表现出为它指定的事务配置。可以认为它作为一个普通的方法参与到一个public方法的事务中。

如果想在非public方法上生效,考虑使用AspectJ(织入方式)。

三面

1.手撕并查集
力扣没有原题,但有道相似的,重点不是题,重点是并查集的套路一定要会呀!
交换字符串中的元素(力扣1202)
给你一个字符串 s,以及该字符串中的一些「索引对」数组 pairs,其中 pairs[i] = [a, b] 表示字符串中的两个索引(编号从 0 开始)。

你可以 任意多次交换 在 pairs 中任意一对索引处的字符。
返回在经过若干次交换后,s 可以变成的按字典序最小的字符串。

示例 1:
输入:s = “dcab”, pairs = [[0,3],[1,2]]
输出:“bacd”
解释:
交换 s[0] 和 s[3], s = “bcad”
交换 s[1] 和 s[2], s = “bacd”

示例 2:
输入:s = “dcab”, pairs = [[0,3],[1,2],[0,2]]
输出:“abcd”
解释:
交换 s[0] 和 s[3], s = “bcad”
交换 s[0] 和 s[2], s = “acbd”
交换 s[1] 和 s[2], s = “abcd”

class Solution {
    public String smallestStringWithSwaps(String s, List<List<Integer>> pairs) {
        int len=s.length();
        DSU dsu=new DSU(len);
        for(List<Integer> list:pairs){
            dsu.union(list.get(0),list.get(1));
        }
        HashMap<Integer,List<Integer>> map=new HashMap<>();
        for(int i=0;i<len;i++){
            int key=dsu.find(i);
            map.computeIfAbsent(key,unused->new ArrayList<>()).add(i);
        }
        StringBuilder res=new StringBuilder(s);
        for(List<Integer> ids:map.values()){
            if(ids.size()>1){
                sort(res,ids);
            }
        }
        return res.toString();
    }
    private void sort(StringBuilder res,List<Integer> ids){
        int len=ids.size();
        char[] arr=new char[len];
        for(int i=0;i<len;i++){
            arr[i]=res.charAt(ids.get(i));
        }
        Arrays.sort(arr);
        for(int i=0;i<len;i++){
            res.setCharAt(ids.get(i),arr[i]);
        }
    }

2.跳表的结构
推荐文章跳表的数据结构

3.Redis的持久化
参考博文Redis持久化和主从复制原理

4.Redis的IO模型
推荐博文Redis的IO模型

5.同步异步和阻塞非阻塞
推荐文章同步异步和阻塞非阻塞的区别

6.select和epoll
推荐文章select、epoll和poll实现

电信云

一面

1.boolean有多少位?
网上有多种答案,如下:
1、1个bit
理由是boolean类型的值只有true和false两种逻辑值,在编译后会使用1和0来表示,这两个数在内存中只需要1位(bit)即可存储,位是计算机最小的存储单位。
2、1个字节
理由是虽然编译后1和0只需占用1位空间,但计算机处理数据的最小单位是1个字节,1个字节等于8位,实际存储的空间是:用1个字节的最低位存储,其他7位用0填补,如果值是true的话则存储的二进制为:0000 0001,如果是false的话则存储的二进制为:0000 0000。
3、4个字节
理由来源是《Java虚拟机规范》一书中的描述:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节。
可以看出,boolean类型没有给出精确的定义,《Java虚拟机规范》给出了4个字节,和boolean数组1个字节的定义,具体还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的。这其实是运算效率和存储空间之间的博弈,两者都非常的重要。

2.垃圾回收器
参考博文jvm垃圾回收器

3.volatile的可见性
volatile不具有原子性,只具有可见性。先从JMM说起,JMM(Java Memory Model)是Java的一种内存模型,与Java并发编程有关的一种模型。

JMM是Java虚拟机规范中所定义的一种内存模型,JMM是标准化的,屏蔽掉了底层不同计算机的区别

JMM描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存中和从内存中读取变量这样的底层细节
JMM规定:

  1. 所有的共享变量都存储于主内存中。(这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题)
  2. 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本
  3. 线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量
  4. 不同线程之间也不能直接访问对方的工作内存中的变量,线程间变量的值的传递需要通过主内存中转来完成

8大数据原子操作

  1. lock:锁定,作用于主内存的变量,把一个变量标记为一个线程独占状态。
  2. unlock:解锁,主内存变量,将一个变量从锁定状态释放出来
  3. read:作用于主内存的变量,把一个变量从主内存传输到线程的工作内存中
  4. load:作用于工作内存的变量,把read中从主内存得到的变量放入工作内存的变量副本中
  5. use:作用于工作内存的变量,把一个变量从工作内存传输到执行引擎
  6. assign: 作用于工作内存的变量,把执行引擎中收到的值赋给工作内存的变量
  7. store:作用于工作内存的变量,把工作内存中的值传送到主内存中
  8. write:作用于工作内存的变量,把store中的值传到主内存的变量中

保证可见性:
JVM内存交互层面:volatile修饰的变量的read,load,use操作和assign,store,write操作必须是连续的,修改后保证立即同步回主内存,使用时必须从主内存刷新,由此保证可见性
底层实现:通过汇编lock前缀指令,锁定变量缓存行区域并写回主内存==>缓存锁定

编译后Java代码会被变异成字节码.class字节,在运行时会被加载到JVM中,JVM会将.class转换成具体的CPU执行指令,CPU加载这些指令逐条执行。CPU的速度比内存快,所以CPU内核有自己的高速缓存区,当内核运行的线程执行一段代码时,先将这段代码的指令集进行缓存行填充到高速缓存,非volatile变量在CPU执行完成后写回高速缓存,再刷新到内存中。在刷新之前,可能会有别的线程介入,变量仍是旧的。volatile修饰的共享变量在转成汇编时会加一个lock前缀的指令,CPU发现这个指令时,会做两件事:1,将当前内核高速缓存行的数据立即回写到内存;2,使其他内核缓存了该内存地址的数据无效。

缓存锁定: 缓存一致机制会阻止同时修改被两个以上处理器缓存的内存区域数据,一个处理器的缓存回写到内存,会导致其他处理器的缓存无效。
MESI协议:早期是通过在总线加lock锁的方式实现,但这种方式太大,后提出缓存一致协议。当CPU写数据时,发现是共享变量,其他CPU也存在副本,则发出信号通知其他CPU将该变量的缓存设为无效,其他CPU用这个变量时会先去嗅探是否有对该变量修改的信号,若变量无效则从内存中重读。
在这里插入图片描述

volatile为什么不能保证原子性?
线程1改变counter值后发出修改通知,修改主存,并让其他线程工作内存中的counter值失效,重新读入,但如果线程已经将数据加载到执行引擎,此时无法改变执行引擎中的counter值,所以无法保证其原子性

4.set和list的区别
java基础集合类

二面

别问,问就是怼项目,先自己介绍项目,然后问对哪一模块比较熟悉,做了多久完成,如果让你再做一次需要多久时间,省下来的时间是在项目中学到了什么省下来的?自己展开论述

携程

一面

1.用过的MYSQL优化
之前写的博文MYSQL语句优化,全是干货,推荐呀~

2.观察者模式
参考博文:23种设计模式

3.ACID实现原理
参考博文:ACID实现原理

4.MYSQL的redo log和bin log
参考博文:MYSQL持久化和主从复制

5.MYSQL的主从复制
参考博文:MYSQL持久化和主从复制

总结

其实还有很多面试问题忘记了,能记住的大部分都是自我觉得比较有难点的,面试完只有一个感受,读书不能只读表面,要学会自己去深究,就拿猿辅导来说,在猿辅导面试之前,从没有被问过Spring事务,就算被问到也就仅限于Spring事务传播机制这些表面问题,但当时学习的时候看着很迷惑,不是很了解Spring事务到底是什么,然后自己下来查了很多关于Spring事务的东西,其实就是百度,看博客,看见别人博客里写的哪点看不懂就继续百度深挖,直到自己心里对整个概念有大致理解。看的时候也会想自己是不是在浪费时间,感觉不会有人问,但从无到有的学习过程真的让人很有成就感就坚持看下去了,所以在猿辅导面试被问到时楼主内心窃喜,希望大家在学习中多多深挖,理解了的知识才是自己的!

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值