Java开发工程师常见的面试总结走起.....

这个月您的技术栈提升了吗?

时间过得挺快的,十月份还剩一周了,回顾过去,展望未来,技术旅程,重在积累实践总结上。

面试

大家都知道进入一家不错的企业都要经历一个很重要的环节那就是面试,面试是检验候选人是否符合岗位要求,技术面试一般都是有浅往深问,这个时候最考验候选人的技术能力了,思路大概是:

  1. 首先考察候选人的基础知识掌握情况;
  2. 再通过深度考察是否有技术热情和深度以及技术的广度;
  3. 提出一些质疑和挑战来考察候选人能否与有不同意见,这个最好能回答不同好的意见具有加分项;

面试准备

要想面试成功拿到offer,面试准备这个环节必不可少的,有一句俗语说的很对“好记性不如烂笔头”,对于自己需要掌握的技术知识点要经常积累。
遇到比较难的问题不要逃避问题,要深入思考去解决,这样解决一个个难题积累下来整理成文档,自然而然的就有了自己的个人体系,面试时候看一下就知道很多知识点。
如何将自己的技术水平很好的展现出来呢?
1.面试前看下面试单位的JD介绍,针对考察内容能否很快的在自己的脑海想到最优解答思路,如果没有想到就应该把问题列出来,找资料,整理最优解答案以及扩展知识点。
2.整理自己简历,要把你简历里的项目中最大的技术难点和亮点突出出来,并且有很好的解决方案,整理好思路,不至于面试过程中出现卡壳的现象。
3.在跟面试官进行技术沟通过程中要做到有理有据,不卑不亢,
4.在技术问题上坚持自己的客观和原则,根据共同认可的事实进行逻辑判断得出观点。

基础面试内容

抽象类是什么?与接口有什么区别?为什么要使用抽象类?

  • 抽象类是不允许被实例化的类,一个类只能使用一次继承关系,但是一个类可以实现多个接口
    抽象类和接口所反映出的设计理念不同:
    抽象类表示的是 “is - a”
    接口表示的是 “like - a”
    实现抽象类和接口的类必须实现其中的所有方法.抽象类可以有非抽象方法,接口中则不能有实现方法,但是在Java 8中允许接口中有静态默认方法
    接口中定义的变量默认是public static final型,且必须给出初值,所以实现类中不能重新定义,也不能改变这个值
    抽象类中定义的变量默认是friendly型,这个变量的值可以在子类中重新定义,也可以重新赋值
    子类中实现父类中的抽象方法时.可见性可以大于等于父类中的
    接口实现类类中的接口方法的可见性只能与接口中的相同,即为public
    使用抽象类是为了重用,减少编码量,降低耦合性

String,StringBuffer与StringBuilder的区别?

三者都可以可以储存和操作字符串。其中 String 是只读字符串,也就意味着 String 引用的字符串内容是不能被改变的。而 StringBuffer和StringBuilder 类表示的字符串对象可以直接进行修改。StringBuilder 是 Java 5 中引入的,它和 StringBuffer 的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized 修饰,因此它的效率也比 StringBuffer 要高。

  • 如果操作少量的数据用String
  • 单线程下操作大量的数据用StringBuilder
  • 多线程下操作大量的数据用StringBuffer

java中常用的集合类有哪些?

Map接口和Collection接口是所有集合框架的父接口:
Collection接口的子接口包括:Set接口和List接口
Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

ArrayList,LinkList的区别?插入和查找哪个更快?

  • 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
  • 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
  • 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
  • 内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
  • 线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
  • 综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
    补充:数据结构基础之双向链表
    双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

HashMap和HashTable的区别?

HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
TreeMap: 红黑树(自平衡的排序二叉树)

HashMap的原理,在Java 8中做了哪些改变?

  • Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树组成。
  • 根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)
    在这里插入图片描述
    在这里插入图片描述
    HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。上图中,每个绿色
    的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。
  • capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
  • loadFactor:负载因子,默认为 0.75。
  • threshold:扩容的阈值,等于 capacity * loadFactor

ConcurrentHashMap数据结构原理

  • ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每 个 Segment 是线程安全的,也就实现了全局的线程安全。

JAVA 线程实现和创建方式

  1. 继承 Thread 类
    Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过 Thread 类的 start()实例方法。start()方法是一个 native 方法,它将启动一个新线 程,并执行 run()方法。
public class MyThread extends Thread { 
  public void run() { 
   System.out.println("MyThread.run()");
  }
}
MyThread myThread = new MyThread(); 
myThread.start();   

2.实现 Runnable 接口。
如果自己的类已经 extends 另一个类,就无法直接 extends Thread,此时,可以实现一个Runnable 接口。

public class MyThread extends OtherClass implements Runnable {
  public void run() { 
   System.out.println("MyThread.run()");
  }
}

说说线程的生命周期

  • 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)就绪(Runnable)运行(Running)阻塞 (Blocked)死亡(Dead) 5 种状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独自 运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换
  • 当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值
  • 当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。
  • 如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。
  • 阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状 态。
  • 阻塞的情况分三种:
    等待阻塞(o.wait->等待对列):运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue)中。
    同步阻塞(lock->锁池) :运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。
    其他阻塞(sleep/join):运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,
    JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行(runnable)状态。
  • 线程会以下面三种方式结束,结束后就是死亡状态。
  • 正常结束
    run()或 call()方法执行完成,线程正常结束。
  • 异常结束
    线程抛出一个未捕获的 Exception 或 Error。
  • 调用 stop
    直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
    在这里插入图片描述

说说sleep 与 wait 区别有哪些

  1. 对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于 Object 类中的。
  2. sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然 保持者,当指定的时间到了又会自动恢复运行状态。
  3. 在调用 sleep()方法的过程中,线程不会释放对象锁。
  4. 而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此
    对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

java中的锁有哪些?

乐观锁
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数 据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新), 如果失败则要重复读-比较-写的操作。
java 中的乐观锁基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。

悲观锁
悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。 java 中的悲观锁就是 Synchronized,AQS 框架下的锁则是先尝试 cas 乐观锁去获取锁,获取不到, 才会转换为悲观锁,如 RetreenLock。

自旋锁
如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋), 等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
线程自旋是需要消耗 cup 的,说白了就是让 cup 在做无用功,如果一直获取不到锁,那线程 也不能一直占用 cup 自旋做无用功,所以需要设定一个自旋等待的最大时间。
如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁 的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。

Synchronized 同步锁
synchronized 它可以把任意一个非 NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重入锁。
Synchronized 作用范围

  1. 作用于方法时,锁住的是对象的实例(this);
  2. 当作用于静态方法时,锁住的是 Class 实例,又因为 Class 的相关数据存储在永久带PermGen
    (jdk1.8 则是 metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁, 会锁所有调用该方法的线程;
  3. synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。它有多个队列, 当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。
  • Synchronized 核心组件:
  1. Wait Set:哪些调用 wait 方法被阻塞的线程被放置在这里;
  2. Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;
  3. Entry List:Contention List 中那些有资格成为候选资源的线程被移动到 Entry List 中;
  4. OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为 OnDeck;
  5. Owner:当前已经获取到所资源的线程被称为 Owner;
  6. !Owner:当前释放锁的线程。

ReentrantLock锁
ReentantLock 继承接口 Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等 避免多线程死锁的方法。ReentrantLock 在构造函数中提供了
是否公平锁的初始化方式,默认为非公平锁。

ReentrantLock 与 synchronized两者区别?

两者的共同点:

  1. 都是用来协调多线程对共享对象、变量的访问
  2. 都是可重入锁,同一线程可以多次获得同一个锁
  3. 都保证了可见性和互斥性
  • 两者的不同点:
  1. ReentrantLock 显示的获得、释放锁,synchronized 隐式获得释放锁
  2. ReentrantLock 可响应中断、可轮回,synchronized 是不可以响应中断的,为处理锁的 不可用性提供了更高的灵活性
  3. ReentrantLock 是 API 级别的,synchronized 是 JVM 级别的
  4. ReentrantLock 可以实现公平锁
  5. ReentrantLock 通过 Condition 可以绑定多个条件
  6. 底层实现不一样, synchronized 是同步阻塞,使用的是悲观并发策略,lock 是同步非阻塞,采用的是乐观并发策略
  7. Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现。
  8. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象, 因此使用 Lock 时需要在 finally 块中释放锁。
  9. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时, 等待的线程会一直等待下去,不能够响应中断。
  10. 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
  11. Lock 可以提高多个线程进行读操作的效率,既就是实现读写锁等。

什么是 CAS?

CAS(Compare And Swap/Set)比较并交换,CAS 算法的过程是这样:它包含 3 个参数CAS(V,E,N)。V 表示要更新的变量(内存值),E 表示预期值(旧的),N 表示新值。当且仅当 V 值等于 E 值时,才会将 V 的值设为 N,如果 V 值和 E 值不同,则说明已经有其他线程做了更新,则当 前线程什么都不做。最后,CAS 返回当前 V 的真实值。
CAS 操作是抱着乐观的态度进行的(乐观锁),它总是认为自己可以成功完成操作。当多个线程同时 使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂 起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,
CAS 操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。

  • CAS 会导致“ABA 问题”?

CAS 算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且 two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操 作发现内存中仍然是 A,然后 one 操作成功。尽管线程 one 的 CAS 操作成功,但是不代表这个过 程就是没有问题的。
部分乐观锁的实现是通过版本号(version)的方式来解决 ABA 问题,乐观锁每次在执行数据的修 改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本 号执行+1 操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现 ABA 问 题,因为版本号只会增加不会减少。

volatile 关键字的作用?

如何在两个线程之间共享数据?

说一下Springbean 生命周期?

  • 实例化
  1. 实例化一个 Bean,也就是我们常说的 new。
  • IOC 依赖注入
  1. 按照 Spring 上下文对实例化的 Bean 进行配置,也就是 IOC 注入。
  • setBeanName 实现
  1. 如果这个 Bean 已经实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String)方法,此处传递的就是 Spring 配置文件中 Bean 的 id 值
  • BeanFactoryAware 实现
  1. 如果这个 Bean 已经实现了 BeanFactoryAware 接口,会调用它实现的 setBeanFactory,setBeanFactory(BeanFactory)传递的是 Spring 工厂自身(可以用这个方式来获取其它 Bean, 只需在 Spring 配置文件中配置一个普通的 Bean 就可以)。
  • ApplicationContextAware 实现
  1. 如果这个 Bean 已经实现了 ApplicationContextAware 接口,会调用setApplicationContext(ApplicationContext)方法,传入 Spring 上下文(同样这个方式也 可以实现步骤 4 的内容,但比 4 更好,因为 ApplicationContext 是 BeanFactory 的子接 口,有更多的实现方法)
  • postProcessBeforeInitialization 接口实现-初始化预处理
  1. 如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor 经常被用 作是 Bean 内容的更改,并且由于这个是在 Bean 初始化结束时调用那个的方法,也可以被应 用于内存或缓存技术。
  • init-method
  1. 如果 Bean 在 Spring 配置文件中配置了 init-method 属性会自动调用其配置的初始化方法。
  • postProcessAfterInitialization
  1. 如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用postProcessAfterInitialization(Object obj, String s)方法。

注:以上工作完成以后就可以应用这个 Bean 了,那这个 Bean 是一个 Singleton 的,所以一 般情况下我们调用同一个 id 的 Bean 会是在内容地址相同的实例,当然在 Spring 配置文件中 也可以配置非 Singleton。

  • Destroy 过期自动清理阶段
  1. 当 Bean 不再需要时,会经过清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调用那个其实现的 destroy()方法;
  • destroy-method 自配置清理
  1. 最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调用其配置的销毁方法。

说一下Spring AOP两种代理方式?

Spring 提供了两种方式来生成代理对象: JDKProxy 和 Cglib,具体使用哪种方式生成由AopProxyFactory 根据 AdvisedSupport 对象的配置来决定。默认的策略是如果目标类是接口, 则使用 JDK 动态代理技术,否则使用 Cglib 来生成代理。

  1. JDK 动态代理 主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。InvocationHandler 是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类 的代码,动态将横切逻辑和业务逻辑编制在一起。Proxy 利用InvocationHandler 动态创建 一个符合某一接口的实例,生成目标类的代理对象。
  2. CGLib 动态代理 :CGLib 全称为 Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展 Java 类与实现 Java 接口,CGLib 封装了 asm,可以再运行期动态生成新 的 class。和 JDK 动态代理相比较:JDK 创建代理有一个限制,就是只能为接口创建代理实例, 而对于没有通过接口定义业务方法的类,则可以通过 CGLib 创建动态代理。
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页