面试知识点总结(持续中...)

380 阅读18分钟

一、基础

1、面向对象的特征?

面向对象的三个基本特征是:封装、继承、多态。

  • 封装: 也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
  • 继承: 面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
  • 多态: 同一行为,通过不同的事物,可以体现出来的不同的形态;使用多态的前提:1. 存在继承或者实现关系;2. 子类或实现类必须重写父类方法;3. 父类引用指向子类对象

2、抽象类和接口有什么区别?

1)语法层面的区别:

  • 抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
  • 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

2)设计层面的区别:

  • 抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。

3、说说反射的用途及实现?

  • 概念: 在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
  • 主要用途: 各类通用框架的功能实现上。
  • 实现: 通过 getClass();通过 Class.forName();使用类.class 类加载器实现,getClassLoader()

4、说说自定义注解的场景及实现

  • 概述: 注解其实就是代码里的特殊标记,它可以用于替代配置文件,
  • 使用场景: 类属性自动赋值;代替配置文件功能,像spring基于注解的配置;类属性的校验
  • 实现: java中有四种元注解:@Retention、@Inherited、@Documented、@Target
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Target({ElementType.FIELD,ElementType.METHOD})
@interface MyAnno{
    public String name() default "zhangsan";
    public String email() default "hello@example.com";
}
class  User{

    @MyAnno(name = "zhang")
    private String name;

    @MyAnno(name = "zhang@example.com")
    private String email;


    @MyAnno(name = "sayHelloWorld")
    public String sayHello(){
        return "";
    }
}

5、HTTP 请求的 GET 与 POST 方式的区别?

mp.weixin.qq.com/s?__biz=MzI…

6、session 与 cookie 区别?

  • COOKIE: 在网站中,http请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。cookie的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是哪个了。cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie只能存储一些小量的数据。

  • SESSION: session和cookie的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie是存储在本地浏览器,而session存储在服务器。存储在服务器的数据会更加的安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些session信息还是绰绰有余的。

  • COOKIE和SESSION结合使用: 存储在服务端:通过cookie存储一个session_id,然后具体的数据则是保存在session中。如果用户已经登录,则服务器会在cookie中保存一个session_id,下次再次请求的时候,会把该session_id携带上来,服务器根据session_id在session库中获取用户的session数据。就能知道该用户到底是谁,以及之前保存的一些状态信息。

7. http与https的区别?

  • 主要区别:

    1)http是无状态的超文本传输协议,是明文传输的;而https相当于是http协议+ssl/tls协议。他是通过证书认证的安全加密密文传输协议。

    2)两种协议的的连接方式是完全不同的,端口号也不同;http端口号是80;https是443.

  • 客户端使用https方式与web服务器通信时的步骤:

    1)客户端使用https的url访问web服务器;要求与web服务器之间建立ssl通道。

    2)web服务器收到请求后,会向客户端发送一份网站证书(携带加密公钥)。

    3)客户端与web服务器协商ssl/tls加密等级。

    4)客户端根据协商的加密等级,建立会话秘钥,然后通过网站的公钥对 密钥进行加密,并传输给web服务器。

    5)web服务器根据手中的私钥解析出会话密钥。

    6)web服务器利用密钥和客户端通信。

8、单例模式?

指令重排序:
    1. memory = allocate(); 分配对象内存空间
    2. ctorInstance() 初始化对象
    3. instance = memory, 设置instance指向为其分配的内存空间
    
// 懒汉模式
public class Singleton{
    private Singleton () {}
    // 注意:需要关键字volatile修饰(禁止指令重排序)
    private volatile static Singleton singleton;
    // 注意:使用双重检验锁验证
    public static Singleton getInstance(){
        if(singleton == null){
            synchronized(Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }   
        
        return singleton;
    }
}
// 饿汉模式
public class Singleton{
    private Singleton (){}
    private static Singleton singleton = new Singleton();
    
    public static Singleton getInstance(){
        return singleton;
    }
}
// 兼顾懒汉和饿汉模式(使用内部类的形式,线程安全,还实现了懒加载)
public class Singleton(){
    private Singleton () {}
    private static class SingletonHolder{
        private static final Singleton instance = new Singleton();
    }
    public static Singleton getInstance(){
        return SingleHolder.instance;
    }
}
// 使用枚举
public class Singleton(){
   private Singleton () {}
   
   public static Singleton getInstance(){
      return SigletonEnum.INSTANCE.getInstance();
   }
   private enum SigletonEnum {
       INSTANCE;
       private Singleton singleton;
       // JVM 保证枚举的构造方法只被调用一次
       SigletonEnum(){
           singleton = new Singleton();
       }
       private Singleton getInstance(){
           return singleton;
       }
   }
}


9、volatile 修饰符的有过什么实践?

实践一: volatile 修饰 64位的long 和 double 变量,使其能原子性的进行读写操作。因为这两种类型的操作是分为2部分的,第一次读取一个32位的,然后再读取剩下的32位的,这个过程并不是原子性的。加上volatile关键字可以实现原子性操作(加锁)。

实践二:volatile修饰符通过设置内存屏障,实现变量的可见性。

10、快速排序

public class SuperClass {

    public static void main(String[] args) {
        int [] arr = {11, 4, 30, 2, -1, 9, 6, 7, 99, 77};
        SuperClass superClass = new SuperClass();
        superClass.sort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr));
    }

    /**
     * 递归排序
     * @param arry
     */
    public void sort(int [] arry, int low, int high){
        int pos = 0;
        if (low < high){
            pos = findPos(arry, low, high);
            this.sort(arry, low, pos - 1);
            this.sort(arry, pos + 1, high);
        }
    }

    /**
     * 寻找临界点
     * @param arr
     * @param low
     * @param high
     * @return
     */
    public int findPos(int [] arr, int low, int high){
        int val = arr[low];
        while (low < high){
            while (low < high && val <= arr[high]){
                high--;
            }
            arr[low] = arr[high];
            while (low < high && val >= arr[low]){
                low++;
            }
            arr[high] = arr[low];
        }
        // 找到第一个值,将第一个值作为临界点,将数组一分为二
        arr[low] = val;
        return low;
    }
}

11、说一下自己对 IOC 、AOP 的理解?

  • IOC(控制反转),它和DI(依赖注入)其实是两个角度对同一件事的解释,可以简单理解为DI去实现ioc。控制反转其实就相当是把获取对象的控制反转过来,将对象的创建和获取交给ioc容器去完成。对象中只是被动的去接受依赖对象。获取依赖对象的过程反转了。
  • AOP(面向切面编程):是一种编程思想,通过分离横切关注点来增强模块性,在不改变现有代码的情况下,实现对功能的增强,动态的将代码切入到指定的方法的编程思想。
事务管理、日志的统一处理、权限的校验、异常的处理都是可以通过aop来进行统一处理。

12、Spring 中用到了那些设计模式,讲一下自己对于这些设计模式的理解?

  • 1、简单工厂模式
  • 2、工厂模式
  • 3、单例模式
  • 4、适配器模式
  • 5、包装器模式
  • 6、代理模式
  • 7、观察者模式
  • 8、策略模式
  • 9、模版方法模式

13、Spring Bean 的作用域和生命周期了解吗?

  • 5种作用域:singleton、prototype、request、session、application作用域。

  • 生命周期:

-> 定义

-> 初始化(实现接口或配置init-method)

-> 使用

-> 销毁(实现接口或配置destory-method)

14、事务的特性?

事务:是指逻辑上的一组操作,这组操作不能被分割,要么成功,要么失败。 四大特性:

  • 原子性:事务不能被分割,要么全部执行,要么全部不执行。
  • 一致性:事务执行前后,数据的必须保持完整性。
  • 隔离性:多个用户在执行数据库操作时,多个用户的事务执行,之间是不能有任何干扰的。
  • 持久性:一个事务被提交之后,对数据库的改变就是持久的。就算是数据库发生故障,也不应该对其有任何影响。

15、Spring 事务中的隔离级别?

如不考虑事务的隔离级别,则会引起脏读、幻读、不可重复读。

  • 脏读: 一个事务读取了另一个事务的还未提交的数据,一旦该事务回滚,则读取的数据就无效。
  • 幻读: 一个事务读取了几行记录之后,另一个事务插入了几行数据,这时候幻读就发生了,在之后的查询中就会发现,有些记录不是第一个事务插入的。
  • 不可重复读: 在同一个事务中,因为是多次读取,有可能前一个读取和后一个读取的数据,因为另一个事务的更新产生不一致的情况。

16、Spring 事务中的事务传播行为?

事务的传播行为:业务层方法之间的相互调用,事务的传递情况。

17、悲观锁和乐观锁?

乐观锁和悲观锁是实现并发控制的两种手段;

  • 乐观锁: 是相对于悲观锁定义的,使用乐观锁的前提是数据出现并发修改的情况比较少,所以是在数据进行更新的时候,才会对正式的数据进行检测,如果发现冲突了,就抛出错误。相比于悲观锁乐观锁通常是不会采用一些数据库的锁机制。最常用的方式采用数据库的版本号记录。(不借助数据库的锁机制)

    乐观锁常用的实现方式: 1、增加单独递增的version字段。 2、使用version作为筛选条件。 3、每次操作version都会递增。

    //查询出商品信息,version = 1
    select version from items where id=1
    //修改商品库存为2
    update items set quantity=2,version = 3 where id=1 and version = 2;
    

    但上述的方式在一旦发上高并发的时候,就只有一个线程可以修改成功,那么就会存在大量的失败。

    所以有一条比较好的建议,可以减小乐观锁力度,最大程度的提升吞吐率,提高并发能力!如下:

    //修改商品库存
    update item 
    set quantity=quantity - 1 
    where id = 1 and quantity - 1 > 0
    
  • 悲观锁: 当我们要对一个数据中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行上锁。这种借助数据库的锁机制在改数据之前进行先锁定,再修改的方式被称为悲观并发锁。

    悲观锁常用实现方式:

      1、在对记录进行修改前,现尝试为该行数据加上排他锁
      2、如果加锁失败,则说明该行数据正在被修改。可以选择是否抛出异常还是等待。
      3、如果成功加锁,则正常对数据进行修改。事务执行完之后,会自动释放锁。
    
    //0.开始事务
    begin; 
    //1.查询出商品库存信息
    select quantity from items where id=1 for update;
    //2.修改商品库存为2
    update items set quantity=2 where id = 1;
    //3.提交事务
    commit;
    

    以上,在对id = 1的记录修改前,先通过for update的方式进行加锁,然后再进行修改。这就是比较典型的悲观锁策略。

    上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认行级锁。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

18、如何选择乐观锁和悲观锁?

注意两点:

  • 1、乐观锁并未真正的加锁,效率较高。但是一旦将锁粒度掌握的不好,可能在高并发情况下会出现大量的失败情况。
  • 2、悲观锁实现了锁的机制,效率较低。但是更新失败的几率较低。

随着互联网三高架构(高并发、高性能、高可用)的提出,悲观锁已经越来越少的被使用到生产环境中了,尤其是并发量比较大的业务场景。

19、synchronized 和 ReenTrantLock 相同和不同点?

相同点:

  • 可重入性
  • 都实现了多线程同步

不同点:

  • 实现机制不同
  • 性能不同
  • 功能不同:ReenTrantLock 独有的功能:可以实现公平和非公平锁;提供了一个Condition类,可以分组唤醒线程;提供可以中断等待锁的线程机制,lock.lockInterruptibly();

20、volatile 和 synchronized 的区别?

  • volatile

      1. 保证修饰变量的的可见性(通过内存屏障和禁止指令重排序);
      2. 无法保证修饰变量其原子性操作。
    
  • synchronized

      1、锁代码块和锁方法都是针对实例化的对象加锁;  
      2、保证原子性和可见性(使用互斥锁)。
    

比较: volatile和synchronized相比不需要加锁,更轻量级; volatile只能实现可见性;但是synchronized可以实现可见性和原子性。

21、可重入锁与非可重入锁的区别?

  • 不可重入锁: 不可重入锁,即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞。
  • 可重入锁: 广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁。

22、线程池解决什么问题,为什么要用线程池?

解决问题:主要是某些任务方法,如果去串形化执行,及其耗时。这时可以合理使用多线程的去执行,提高cpu的使用率,使任务的执行效率得到极大提升。

总结:1)单个任务处理时间比较短。2)需要处理的任务数量很大

线程池定义:是指在初始化一个多线程应用程序过程中,创建的一个线程集合。然后在执行方法时对这些线程可以做到重用,不必每次去创建。

使用线程池的好处:

1、线程的重用:因为线程的创建和销毁的开销很大,所以重用可以大大减少开销。
2、线程池可以对线程做到有效的管理:可以提供定时、定期执行某些特定任务。
3、可以控制最大的并发数目,提高系统的资源利用率。

23、线程池使用时的注意事项(ThreadPoolExecutor)?

  • corePoolSize: 核心线程数;

  • maximumPoolSize: 线程最大线程数;

  • workQueue: 阻塞队列,存储等待执行的任务,很重要会对线程池运行过程产生影响。

  • keepAliveTime:线程没有任务时最多保持多久时间终止。

  • unit: keepAliveTime的时间单位;

  • threadfactory: 线程工厂,用来创建线程。

  • rejectHandler: 当拒绝处理任务时的策略。

      1、ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
      2、ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
      3、ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
      4、ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
    

    创建线程池方法:

     // 普通线程池
     ExecutorService executorService = Executors.newCachedThreadPool();
     // 单线程线程池
     ExecutorService executorService = Executors.newSingleThreadExecutor();
     // 带时间调度线程池 
     ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
    

    使用方法:

      execute(): 提交语句,交给线程池运行。
      submit(): 提交任务,能够返回执行结果。execute + Future
      shutdown(): 关闭线程池,等待任务都处理完。
      shutdownNow(): 关闭线程池,不等任务处理完。
    

24、线程池的拒绝策略?

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常;
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常;
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务;
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务;

25、jvm的内存模型?

java的内存模型(JMM)主要是描述了各个java程序访问线程共享变量的一些细节,以及在jvm中将变量从存储到读取的一些底层细节。java内存模型是对共享数据的可见性、原子性、有序性的规则和保障。当然java在设计时也提供了一些工具类如synchronized和volatile等。

1、其中synchronized是通过添加互斥锁来实现【可见性】【原子性】【有序性】
	a)获取互斥锁
    b)清空工作内存
    c)从主内存copy到工作内存中
    d)执行代码逻
    e)将工作内存共享刷新入主内存中
    f)释放互斥锁
 2、 其中volatile是通过利用硬件mesi缓存一致性性协议(嗅探机制)和添加内存屏障限制重排序来实现【可见性】【有序性】
 		比如创建对象:1、分配内存空间 2、调用构造器,初始化实例。3、返回地址给引用

26、CMS垃圾回收的过程?
CMS(Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

从名字中的Mark Sweep这两个词可以看出,CMS收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:

初始标记(STW): 暂停所有的其他线程(STW),并记录下直接与root相连的对象,速度很快 ;

并发标记: 同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。

重新标记(STW): 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短

**并发清除: **开启用户线程,同时GC线程开始对未标记的区域做清扫。

27、根集包括哪些对象,为什么这些对象会作为根集对象?

GcRoots对象:

  1. java虚拟机栈中的引用的对象

  2. 方法区中静态属性所引用的对象

  1. 方法区中常量所引用的对象

4)本地方法栈native方法中所引用的对象

27、java中的锁升级? 锁分4种状态:无锁态、偏向锁、轻量级锁(自旋锁)、重量级锁;

synchronized锁的是对象,对象就是Object,Object在heap中的布局,如下图所示

前面8个字节就是markword,后面4个字节是class pointer就是这个对象属于哪个类的,People就是People.class,Cat类就是Cat.class,在后面实例数据就是看你类里面字段的具体大小了,int age就是4个字节,string name就是英文1个字节, 中文2个字节(String的中文字节数要看用的编码集合,如果是utf-8类型的,那么中文占2到3个字节,如果是GBK类型的,那么中文占2个字节),最后前面三项加起来不能被8整除的,就是补齐到能够被8整除。下图就是markword(8*8=64位)的分布图,锁升级就是markdown里面标志位的变化。

A、加锁之前的对象内存信息:

B、加锁之后的对象内存信息

29、jvm类加载的过程和双亲委派?

1)jvm类加载过程

2)双亲委派过程

  1. 类加载详细过程

二、进阶

三、项目经验