0%

面试总结20180410

2018

项目情况

  • 项目介绍

JavaSE

集合

  • 集合概述

  • list与set的异同

  • hashtable与hashmap异同

    以上三点可以从下图简要分析(集合总结2.png):


    map的key不能重复,value可以重复

JVM

  • JVM

    JVM是Java Virtual Machine(Java虚拟机)的缩写,

    JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

    Java虚拟机包括一套字节码指令集一组寄存器一个栈一个垃圾回收堆一个存储方法域

    JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

    JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。

    http://www.cnblogs.com/sunada2005/p/3577799.html

  • JVM调优

      JVM三大性能调优参数:–Xmx -Xms –Xss
      
      –Xmx -Xms是对堆的性能调优参数,一般两个设置是一样的,
          如果不一样,当Heap(堆)不够用,会发生内存抖动。一般都调大这两个参数,并且两个大小一样。
          
      ----------------------------------------------------------------------------------------------
      -Xss是对每一个线程栈的性能调优参数,影响堆栈调用的深度。是指设定每个线程的堆栈大小。这个就要依据你的程序,看一个线程大约需要占用多少内存,可能会有多少线程同时运行等。
      
      -Xmx:代表最大堆。是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出OutOfMemory异常。
      
      -Xms:代表最小堆(初始堆大小)。是指设定程序启动时占用内存大小。一般来讲,大点,程序会启动的快一点,但是也可能会导致机器暂时间变慢。
      
      -Xmn:代表新生代(Young Gen)。
      
      上三个参数的设置都是默认以Byte为单位的,也可以在数字后面添加[k/K]或者[m/M]来表示KB或者MB。而且,超过机器本身的内存大小也是不可以的,否则就等着机器变慢而不是程序变慢了。
    

    jvm调优总结:

    https://www.cnblogs.com/ceshi2016/p/8447989.html

  • 借助于示例理解:

      对于JVM内存配置参数:
      -Xmx10240m -Xms10240m -Xmn5120m -XXSurvivorRatio=3
      其最小内存值和Survivor区总大小分别是()
      
      10240m,2048m
      
      --------------------------------------解析----------------------------------------------------
      -Xmx10240m:代表最大堆
      -Xms10240m:代表最小堆(初始堆大小)
      -Xmn5120m: 代表新生代(Young Gen)
      -XXSurvivorRatio=3:代表Eden:Survivor = 3    
      根据Generation-Collection算法(目前大部分JVM采用的算法),一般根据对象的生存周期将堆内存分为若干不同的区域,
      一般情况将新生代分为Eden+两块Survivor;    
      计算Survivor大小, Eden:Survivor = 3,总大小为5120,3x+x+x=5120  x=1024
      
      所以,2x=1024*2=2048,即Survivor区总大小是2048,
      -Xms初始堆大小,即最小内存值为10240m
      
      (ps:新生代大部分要回收,采用Copying算法,快!
      老年代 大部分不需要回收,采用Mark-Compact算法)
    
  • JVM调优相关1.png


https://www.nowcoder.com/questionTerminal/970cdaaa4a114cbf9fef82213a7dabca

多线程

  • 线程和进程

    1.进程和线程都是一个时间段的描述,是CPU工作时间段的描述,不过是颗粒大小不同

    2.进程中包含线程

    3.举例:

    开个QQ,开了一个进程;开了迅雷,开了一个进程。
    在QQ的这个进程里,传输文字开一个线程、传输语音开了一个线程、弹出对话框又开了一个线程。

  • 线程创建方式

    • 1.继承Thread类

        class MyThread extends Thread{
            ......
            @Override
            public void run(){
                ......
            }
        }
        
        MyThread mt = new MyThread();//创建线程
        mt.start();//启动线程
      
    • 2.实现Runnable接口

        class MyThread implements Runnable{
        
            ......
            @Override
            public void run(){
                ......
            }
        }
        
        MyThread mt = new MyThread();
        Thread td = new Thread(mt);//创建线程
        td.start();//启动线程
      
    • 对比总结

        无论用哪种方法,都要new一个Thread类对象,用Thread类的start方法来启动线程。
      
  • 线程生命周期(线程生命周期2.png)

  • 线程各个状态

    就绪状态
    创建线程对象后,调用了线程的start()方法(此时线程只是进入了线程队列,等待获取CPU服务(cpu可能正在执行其他程序),具备运行条件,但是并不一定已经开始运行了)

    运行状态
    处于就绪状态的线程,一旦线程获取到CPU的服务之后,就进入到了运行状态,开始执行run()方法里面的逻辑。

    终止
    线程的run()方法执行完毕,或者人为线程调用了stop()方法(该做法已经被淘汰),线程便进入终止状态。

    阻塞
    一个正在执行的线程在某些情况下,由于某种原因而暂时让出了CPU资源,暂停了自己的执行,便进入了阻塞状态,如调用 sleep()方法。

    JDK

  • JDK7和JDK8的异同(JDK8的新特性)

框架部分

  • springmvc的单例模式

    https://blog.csdn.net/qianyiyiding/article/details/77104736

    https://zhuanlan.zhihu.com/p/21733226

    即:spring MVC中的controller是单例模式,但是是多线程,各个线程之间不影响!

    设置为多例模式:@Scope(“prototype”)

      @RestController
      @RequestMapping(value = "hello")
      @Scope("prototype")
      public class HelloController {
    

    https://blog.csdn.net/qianyiyiding/article/details/77104736

  • mybatis与hibernate异同

  • Spring的AOP与IOC

      spring是J2EE应用程序的开源框架,是轻量级的IoC和AOP的容器框架,可以单独使用,也可以和其他框架组合使用
    

    AOP:

      java面向对象思想的拓展,将系统中非核心的业务提取出来,进行单独处理。比如事务、日志和安全等体现java的灵活。
      
      spring中面向切面的实现有两种方式,一种是动态代理,一种是CGLIB,动态代理必须要提供接口,而CGLIB实现是继承。
      
      **关于IoC和DI**
    

    https://www.iteye.com/blog/jinnianshilongnian-1413846

    IOC与DI:

      1)(IOC)控制反转:组件**依赖关系**的创建和管理置于spring容器,由容器控制,而不是由代码直接控制,将控制权转向了容器。
      
      2)(DI)依赖注入:组件之间的依赖关系由容器在运行期决定 ,由容器动态的将某种依赖关系注入到组件之中,实现的程序的解耦。 
      
      spring中有三种注入方式,一种是set注入,一种是接口注入,另一种是构造方法注入。
      
      IoC是什么 之●为何是反转,哪些方面反转了:
      
      有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;
      而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,
      对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
    

    https://zhuanlan.zhihu.com/p/90939765

    spring websocket api

    • Spring 4.0的websocket的支持

    http://wiselyman.iteye.com/blog/2003336

  • SSM概述(SSM概述.png):

数据库部分

索引

  • 数据库索引(创建索引.jpg)

    JDBC

  • 自己添加:

      jdbc
      
      多看
      SQL进阶
      java基础    
    

servlet与CGI

  • servlet与CGI(Common Gateway Interface 公共网关接口)

      Servlet的生命周期分为5个阶段:加载、创建、初始化、处理客户请求、卸载。
      (1)加载:容器通过类加载器使用servlet类对应的文件加载servlet
      (2)创建:通过调用servlet构造函数创建一个servlet对象
      (3)初始化:调用init方法初始化
      (4)处理客户请求:每当有一个客户请求,容器会创建一个线程来处理客户请求
      (5)卸载:调用destroy方法让servlet自己释放其占用的资源
    

    JSP九大内置对象

  • JSP九大内置对象

    • 内置对象特点:

        1.由JSP规范提供,不用编写者实例化
        2.通过Web容器实现和管理
        3.所有JSP页面均可使用
        4.只有在脚本元素的表达式或代码段中才可使用(<%=使用内置对象%>或<%使用内置对象%>)
      
    • 常用内置对象:

        1.输出输入对象:request对象、response对象、out对象
        2.通信控制对象:pageContext对象、session对象、application对象
        3.Servlet对象:page对象、config对象
        4.错误处理对象:exception对象    
      

      String、StringBuffer、StringBuilder

  • 区别

      String:适用于少量的字符串操作的情况
      StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
      StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况
    
  • 从JVM角度分析String慢的原因

      那么JVM就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。
    
  • StringBuffer与StringBuilder

      StringBuilder是线程不安全的,而StringBuffer是线程安全的
      StringBuffer        JDK1.0出现
      StringBuilder   JDK1.5出现
    

    mybatis缓存

  • 一级缓存和二级缓存

      mybatis的一级缓存:
        MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,
      当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,
      不需要再进行一次数据库查询了。
      
        MyBatis会在一次会话的SqlSession对象中创建一个本地缓存(local cache),
      对于每一次查询,都会尝试*根据查询的条件*去本地缓存中查找是否在缓存中,如果在缓存中,
      就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户。
      
        一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,
        在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。
        不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
      
        一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,
        第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
        当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
      
        二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,
        多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,
        二级缓存是跨SqlSession的。
      
        二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,
        不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,
        第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
        Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。
    
  • 参考

    https://www.cnblogs.com/little-fly/p/6251451.html
    http://www.jb51.net/article/116961.htm

2019

spring中使用了哪些设计模式?

https://juejin.im/entry/5c6611a2f265da2de1658a13

JDK动态代理和CGLIB动态代理

https://juejin.im/post/5c3e9c37f265da61263862f1
https://juejin.im/post/5bbff7daf265da0aef4e330c

2020

1.Java 的类加载器相关

  • 1.Java 的类加载器的种类都有哪些?

      1、根类加载器(Bootstrap)                --C++写的 ,看不到源码
      2、扩展类加载器(Extension)            --加载位置 :jre\lib\ext 中
      3、系统(应用)类加载器(System\App)    --加载位置 :classpath 中
      4、自定义加载器(必须继承 ClassLoader)
    
  • 2.类什么时候被初始化?

      1)创建类的实例,也就是 new 一个对象
      2)访问某个类或接口的静态变量,或者对该静态变量赋值
      3)调用类的静态方法
      4)反射(Class.forName("com.lyj.load"))
      5)初始化一个类的子类(会首先初始化子类的父类)
      6)JVM 启动时标明的启动类,即文件名和类名相同的那个类
      
      只有这 6 中情况才会导致类的类的初始化。
      
      类的初始化步骤:
      
          1)如果这个类还没有被加载和链接,那先进行加载和链接
          2)假如这个类存在直接父类,并且这个类还没有被初始化
          (注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
          3)加入类中存在初始化语句(如 static 变量和static 块),那就依次执行这些初始化语句。
    
  • 3.Java 类加载体系之ClassLoader和双亲委托机制

      java 是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,
      这四类安全沙箱分别是:
      
      1)类加载体系
      2).class 文件检验器
      3)内置于 Java 虚拟机(及语言)的安全特性 
      4)安全管理器及Java API 
      
      这里主要讲解类的加载体系:
      java 程序中的 .java 文件编译完会生成 .class 文件,而 .class 文件
      就是通过被称为类加载器的ClassLoader加载的,而 ClassLoder 在加载过程中会
      使用“双亲委派机制”来加载 .class 文件,先上图:
    

      1.BootStrapClassLoader:启动类加载器,该ClassLoader是jvm在启动时创建的,
      用于加载 $JAVA_HOME$/jre/lib 下面的类库(或者通过参数-Xbootclasspath指定)。
      由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,
      所以不能直接通过引用进行操作。
      
      2.ExtClassLoader:扩展类加载器,该ClassLoader是在sun.misc.Launcher里
      作为一个内部类ExtClassLoader定义的(即 sun.misc.Launcher$ExtClassLoader),
      ExtClassLoader 会加载 $JAVA_HOME/jre/lib/ext 下的类库
      (或者通过参数-Djava.ext.dirs 指定)。
      
      3.AppClassLoader:应用程序类加载器,该ClassLoader同样是在sun.misc.Launcher里
      作为一个内部类AppClassLoader 定义的(即 sun.misc.Launcher$AppClassLoader),
      AppClassLoader 会加载java环境变量CLASSPATH所指定的路径下的类库,而CLASSPATH所指定的路径 
      可以通过System.getProperty("java.class.path")获取;当然,该变量也可以覆盖,
      可以使用参数-cp,例如:java -cp 路径 (可以指定要执行的 class 目录)。
      
      4.CustomClassLoader:自定义类加载器,该ClassLoader 是指我们自定义的ClassLoader,
      比如tomcat的StandardClassLoader 属于这一类;当然,大部分情况下使用AppClassLoader
      就足够了。
    


    前面谈到了ClassLoader 的几类加载器,而ClassLoader使用双亲委派机制来加载class文件的。

  • ClassLoader的双亲委派机制是这样的(这里先忽略掉自定义类加载器 CustomClassLoader):

      1)当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
      
      2)当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给
      BootStrapClassLoader去完成。
      3)如果BootStrapClassLoader加载失败(例如在$JAVA_HOME$/jre/lib 里未查找到该class),
      会使用ExtClassLoader来尝试加载;
      4)若 ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader
      也加载失败,则会报出异常ClassNotFoundException。
    
  • 下面贴下ClassLoader 的loadClass(String name, boolean resolve)的源码:

      protected synchronized Class<?> loadClass(String name, boolean resolve)
      throws ClassNotFoundException {
          // 首先找缓存是否有 class
          Class c = findLoadedClass(name);
          if (c == null) {
              //没有判断有没有父类
              try {
                      if (parent != null) {
                          //有的话,用父类递归获取 class
                          c = parent.loadClass(name, false);
                      } else {
                          //没有父类。通过这个方法来加载
                          c = findBootstrapClassOrNull(name);
                      }
              } catch (ClassNotFoundException e) {
                      // ClassNotFoundException thrown if class not found
                      // from the non-null parent class loader
              }
              if (c == null) {
                  // 如果还是没有找到,调用 findClass(name)去找这个类
                  c = findClass(name);
              }
          }
          if (resolve) {
              resolveClass(c);
          }
          return c;
      }
      
      代码很明朗:首先找缓存(findLoadedClass),没有的话就判断有没有 parent,
      有的话就用 parent 来递归的 loadClass,然而 ExtClassLoader 并没有设置 parent,
      则会通过 findBootstrapClassOrNull 来加载 class,而findBootstrapClassOrNull 
      则会通过 JNI 方法”private native Class findBootstrapClass(String name)“来
      使用 BootStrapClassLoader 来加载 class。
      
      然后如果parent未找到class,则会调用findClass来加载class,findClass是一个
      protected 的空方法,可以覆盖它以便自定义class加载过程。
      另外,虽然ClassLoader加载类是使用loadClass方法,但是鼓励用ClassLoader的子类重写
      findClass(String),而不是重写 loadClass,这样就不会覆盖了类加载默认的双亲委派机制。
    
  • 4.双亲委派托机制为什么安全

      举个例子,ClassLoader加载的class文件来源很多,比如编译器编译生成的class、或者网络下载的字节码。
      而一些来源的class 文件是不可靠的,比如我可以自定义一个java.lang.Integer类来覆盖jdk中默认的 
      Integer类,例如下面这样:
      
      package java.lang;
      public class Integer {
          public Integer(int value) {
              System.exit(0);
          }
      }
      
      初始化这个Integer 的构造器是会退出JVM,破坏应用程序的正常进行,如果使用双亲委派机制的话
      该Integer类永远不会被调用,以为委托BootStrapClassLoader加载后会加载JDK中的Integer类
      而不会加载自定义的这个,可以看下下面这测试个用例:
      
      public static void main(String... args) {
          Integer i = new Integer(1);
          System.err.println(i); 
       }
    
       执行时 JVM 并未在 new Integer(1)时退出,说明未使用自定义的 Integer,于是就保证了安全性。 
    
  • 4.mybatis(mapper)调用过程

      Mapper方法的执行过程:先获取Mapper对象,该对象是JdbcProxy代理对象。
      代理对象回调接口里,会根据Method,执行org.apache.ibatis.session.SqlSession对应的方法,
      同时需要完成参数的转化.
    

    https://blog.csdn.net/bingospunky/article/details/79220894
    https://www.jianshu.com/p/3c56bf3313ce

  • 5.hashMap

https://blog.csdn.net/woshimaxiao1/article/details/83661464

  • 6.redis存储方式

      Redis是一个由ANSI C语言编写,性能优秀、支持网络、可持久化的K-K内存数据库,
      并提供多种语言的API。它常用的类型主要是 String、List、Hash、Set、ZSet 这5种。
    

      Redis在互联网公司一般有以下应用:
      String:缓存、限流、计数器、分布式锁、分布式Session
      Hash:存储用户信息、用户主页访问量、组合查询
      List:微博关注人时间轴列表、简单队列
      Set:赞、踩、标签、好友关系
      Zset:排行榜
    

    https://www.cnblogs.com/weknow619/p/10464139.html

  • 7.MySQL数据库索引的4大类型及相关的索引创建

      1.普通索引
          这是最基本的MySQL数据库索引,它没有任何限制。它有以下几种创建方式:
          
          a:创建索引:
              CREATE INDEX indexName ON mytable(username(length));
              
          b:修改表结构:
              ALTER mytable ADD INDEX [indexName] ON (username(length)) 创建表的时候直接指定
              CREATE TABLE mytable( ID INT NOT NULL, username VARCHAR(16) NOT NULL, INDEX [indexName] (username(length)) );  
          
          c:删除索引的语法:
              DROP INDEX [indexName] ON mytable;   
          
      2.唯一索引
          它与前面的普通索引类似,不同的就是:MySQL数据库索引列的值必须唯一,但允许有空值。
          如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:
      
          a:创建索引:
          CREATE UNIQUE INDEX indexName ON mytable(username(length))
          b:修改表结构
          ALTER mytable ADD UNIQUE [indexName] ON (username(length))
          c:创建表的时候直接指定
          CREATE TABLE mytable( ID INT NOT NULL, username VARCHAR(16) NOT NULL, UNIQUE [indexName] (username(length)) );   
      
      3.主键索引
          它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引:
          CREATE TABLE mytable( ID INT NOT NULL, username VARCHAR(16) NOT NULL, PRIMARY KEY(ID) );  
          当然也可以用 ALTER 命令。记住:一个表只能有一个主键。
    
      4.组合索引
    

    https://www.cnblogs.com/xingzc/p/5757697.html

  • 7.ArrayList和LinkedList

      ArrayList和LinkedList都是实现了List接口的容器类,用于存储一系列的对象引用。
      他们都可以对元素的增删改查进行操作。
    
      ArrayList和LinkedList的大致区别如下:
    
      1.ArrayList是实现了基于 动态数组 的数据结构,LinkedList是基于 链表结构 。
      2.对于随机访问的get和set方法,ArrayList要优于LinkedList,因为LinkedList要移动指针。
      3.对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。
    
  • 8.查询前10条数据和后10条数据:

      select top 10 * from tablename
      
      或者按时间正序or倒序,limit10
      select * from aaa where create_time<="2017-03-29 19:30:36"
      order by create_time desc/asc
      limit 10
      
      如果按照某个字段排序后,再limit10查询,速度有区别?貌似没区别
    
  • 9.SQL调优,查询调优

  • 10.shiro、springsecurity框架

  • 11.springcloud组件

  • 12.SpringMVC调用流程(8大步骤)

先记住几个概念:

前端控制器 DispatcherServlet
处理器映射器 HandlerMapping
处理器执行链 HandlerExecutionChain
处理器适配器 HandlerAdapter
视图解析器ViewResolver

调用步骤:

1、客户端请求到前端控制器(dispatcherServlet)
2、前端控制器(dispatcherServlet)请求处理器映射器(HandlerMapping),
3、处理器映射器(HandlerMapping)根据url查找相应的处理器(Handler),返回处理器执行链(HandlerExecutionChain)给前端控制器(DispatcherServlet)
4、前端控制器(DispatcherServlet)请求处理器适配器(HandlerAdapter),
5、处理器适配器(HandlerAdapter)执行处理器(Handler),生成ModelAndView,返回ModelAndView给前端控制器(DispatcherServlet)
6、前端控制器(DispatcherServlet)请求视图解析器(ViewResolver)
7、视图解析器(ViewResovler)返回视图对象给前端控制器(DispatcherServlet)
8、最后渲染视图

13.优化Mysql数据库的方法

回答一:

表优化,库优化,sql 优化,引擎优化。分库分表,设置合理字段、合理的字段索引数量,
读写分离,少用多层嵌套子查询,少用分组查询,少用多条件查询,少用模糊查询,查询中少用计算或统计等等。
优化的方式很多

回答二:

mysql 数据库优化从两方面入手。
1.通过优化配置参数
如合适的 innodb 池大小,取消反向解析,合理的连接数,合理的超时时长,合理的相关 cache 等
2.通过操作的优化
如,合理的表结构,合理的索引,合理的查询语录(可通过分析慢查询日志找出可优化的,再通过 explain 去测试语句,找出可优化的点进行优化)。
如果都有优化了还有瓶颈、最后就是分表、分库、扩硬件、主从读写分离

回答三(SQL级别):

创建索引
索引不会包含有 NULL 值的列    
一般情况下不鼓励使用 like 操作,如果非使用不可,如何使用也是一个问题。
like “%aaa%” 不会使用索引而 like “aaa%”可以使用索引。
不要在列上进行运算
不使用 NOT IN 和<>操作


14.redis和mysql数据不一致怎么解决

1 如果是 redis cluter 集群 
  因为 redis cluter 集群采用异步复制,在故障切换的过程中删除操作有可能丢失,所以只是删除缓存操作是有可能 redis mysql 不一致的,需要根据业务做特殊处理。
2 如果是单机 redis
    在更新数据的时候先加写锁,然后删除缓存,在加载缓存的时候加读锁,可有避免数据在修改过程中其他线程加载旧数据到 redis

15.Mybatis原理

mybatis 是数据持久层框架,基本原理是:

1.创建 SqlSessionFactoryBuilder 对象,调用 build(inputstream)方法读取并解析配置文件,
返回 SqlSessionFactory 对象

2.由 SqlSessionFactory 创建 SqlSession 对象,没有手动设置的话事务默认开启

3.调用 SqlSession 中的 api,传入 Statement Id 和参数(mybatis dao xml 映射),
内部进行复杂的处理,最后调用 jdbc 执行 SQL 语句,封装结果返回    

16.Spring的底层实现原理

SpringIOC 的底层实现原理是:

传统开发方式:Person person = new Person
这种开发方式耦合度太高,不符合 java 编程思想(高内聚,低耦合)。

Spring ioc 就是把对象交给 spring 进行管理,需要的时候就去工厂拿就可以了,实现了低耦合,高内聚。
原理是:
首先加载 xml 配置文件,通过 dom4j 去解析 xml 文件,然后通过工厂模式和反射去创建对象。
Springaop 的实现原理:
aop,面向切面编程,是 Spring 两大核心之一,Springaop 是通过代理的方式实现切面编程的。
    主要是以下两种代理方式:
    一种是基于 JDK 的动态代理(目标对象实现了接口)
    一种是基于 cglib 的动态代理(目标对象没有实现接口)

17.Collection 和 Collections 的区别。

Collection 是集合类的上级接口,继承与他的接口主要有 Set 和 List。
Collections 是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

18.HashMap 工作原理是什么?

回答一:
1.8 之前,hashmap 的数据结构是:数组+链表
1.8 以后,hashmap 的数据结构是:数组+链表+红黑树
1.8 以后,做了很大的改变,使用红黑树,可以大大提高查询效率。
可以深挖一下红黑树的应用。
就其他方面来说,
1.8 插入元素时,使用的是头插法,在插入元素出现 hash 碰撞,会引起红黑树数据结构进行对 node 链表数据的插入。    
回答二:
原理是hash表
能快速定位到指定的 key 对应的 value,实现使用的是数组加一个链式结构,
1.8 之前用的是链表,1.8 之后用的是链表加红黑树。
插入数据的基本流程就是计算 k 的 hashcode 然后用这个 hashcode 去和整个数组空间去进行按位于,
得到具体需要放置的数组索引。
然后就是一个比对和放置的过程,对比当前的数据链是否有相同的,没有就放,有就不放。
取出数据规则差不多也是根据 key 的 hashcode 去得到数组索引位,然后比对

19.请解释什么是值传递和引用传递?

值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身。
所以对引用对象进行操作会同时改变原对象。
一般认为 Java 内的传递都是值传递。    

20.内存溢出和内存泄露区别

内存溢出:
内存溢出就是常见的 OOM,说白了就是申请的内存小了,可能原因 JVM 内存太小,
对象所需内存太大,还有可能就是程序设计问题。解决方式要么修改 jvm 参数,要么修改程序。

内存泄露:
内存泄露就是对象本应该被回收,但是其他地方还在使用它的引用,导致无法释放内存,
引起这种的原因一般是非静态内部类中创建了静态实例,或者是单例对象,因为单例的静态特性,
会使它生命周期和应用的生命周期一样长,如果一个对象已经不需要了,但单例对象还依旧持有该对象的引用,
就会导致不能被正常回收,致使内存泄露。集合容器也可能导致内存泄露,因为集合很大的时候,没有来得及清理,
也会导致内存泄露。避免的最好的方式就是养成良好编码习惯,该销毁的对象要销毁,涉及到上下文的优先考虑全局。

21.请介绍一下 Syncronized 锁,如果用这个关键字修饰一个静态方法,锁住了什么?如果修饰成员方法,锁住了什么?

Synchronized 修饰静态方法以及同步代码块的 Synchronized (类.class)用法锁的是类,
线程想要执行对应同步代码,需要获得类锁。
Synchronized 修饰成员方法,线程获取的是当前调用该方法的对象实例的对象锁。

22.MySQL的四种事务隔离级别

一.事务的基本要素(ACID)

1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

二.MySQL事务隔离级别

三、事务的并发问题
    1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
    2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,
    导致事务A多次读取同一数据时,结果 不一致。
    3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,
    但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,
    就好像发生了幻觉一样,这就叫幻读。

    小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。
    解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

23.spring是如何解决循环依赖问题的

如何理解“依赖”呢,在Spring中有:
构造器循环依赖
field属性注入循环依赖

https://www.cnblogs.com/sun-sun/p/10521334.html
https://www.jianshu.com/p/8bb67ca11831

redis做同步锁

  • redis分布式加锁解锁

      ***synchronized处理并发.wmv***
      com.imux.wxsell.service包下的RedisLock类:
      
      package com.imux.wxsell.service;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.data.redis.core.StringRedisTemplate;
      import org.springframework.stereotype.Component;
      import org.springframework.util.StringUtils;
      
      /**
       * redis lock
       */
      @Component
      @Slf4j
      public class RedisLock {
          @Autowired
          private StringRedisTemplate stringRedisTemplate;
      
          /**
           * 加锁
           *
           * @param key   productId - 商品的唯一标志
           * @param value 当前时间+超时时间>>>即过期的时刻
           * @return
           */
          public boolean lock(String key, String value) {
              //setIfAbsent >>>>>>>redis的SETNX方法
              if (stringRedisTemplate.opsForValue().setIfAbsent(key, value)) {//对应setnx命令
                  //可以成功设置,也就是key不存在
                  return true;
              }
      
              //判断锁超时 - 防止原来的操作异常,没有运行解锁操作  防止死锁
              String currentValue = stringRedisTemplate.opsForValue().get(key);
              
              //如果锁过期
              if (!StringUtils.isEmpty(currentValue) &&
                      Long.parseLong(currentValue) < System.currentTimeMillis()) {//currentValue不为空且小于当前时间
                  //获取上一个锁的时间value
                  String oldValue = stringRedisTemplate.opsForValue().getAndSet(key, value);//对应getset,如果key存在
      
                  //假设两个线程同时进来,key被占用了。获取的值currentValue=A(get取的旧的值肯定是一样的),两个线程的value都是B,key都是K.锁时间已经过期了。
                  //而这里面的getAndSet一次只会一个执行,也就是一个执行之后,上一个的value已经变成了B。只有一个线程获取的上一个值会是A,另一个线程拿到的值是B。
                  if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
                      //oldValue不为空且oldValue等于currentValue,也就是校验是不是上个对应的商品时间戳,也是防止并发
                      return true;
                  }
              }
              return false;
          }
    

          /**
           * 解锁
           *
           * @param key
           * @param value
           */
          public void unlock(String key, String value) {
              try {
                  String currentValue = stringRedisTemplate.opsForValue().get(key);
                  if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
                      stringRedisTemplate.opsForValue().getOperations().delete(key);//删除key
                  }
              } catch (Exception e) {
                  log.error("[Redis分布式锁] 解锁出现异常了,{}", e);
              }
          }
      }
      
          //使用锁的类方法:
          @Autowired
          private RedisLock redisLock;//redis锁
          //超时时间
          private static final int TIMEOUT = 10 * 1000;//超时时间 10s
      
          ------------------------------------------------------------
          @Override
          public void orderProductMockDiffUser(String productId) {
      
              ************//加锁************
              long time = System.currentTimeMillis() + TIMEOUT;
              if (!redisLock.lock(productId, String.valueOf(time))) {
                  throw new OrderException("", "很抱歉,人太多了,换个姿势再试试~~");
              }
      
              //1.查询该商品库存,为0则活动结束
              int stockNum = stock.get(productId);
              if (stockNum == 0) {
                  throw new OrderException("", "活动结束");
              } else {
                  //2.下单
                  orders.put(TableIdUtil.tableId(), productId);
                  //3.减库存
                  stockNum = stockNum - 1;//不做处理的话,高并发下会出现超卖的情况,下单数,大于减库存的情况。
                  虽然这里减了,但由于并发,减的库存还没存到map中去。新的并发拿到的是原来的库存
                  try {
                      Thread.sleep(100);//模拟减库存的处理时间
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  stock.put(productId, stockNum);
              }
      
              ************//解锁************
              redisLock.unlock(productId, String.valueOf(time));
      
          }
    

springboot的核心功能,start的原理

springcloud的网关,其他主要组件

feign等远程调用流程原理