• Java面试题


    第一天:

    1.重载和重写的区别

    什么是方法重写

    ​ 在Java和其他一些高级面向对象的编程语言中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。

    方法重写和方法重载的区别:

    ​重载(overload):发生在同一个类中 , 方法名相同,参数列表不同,不考虑返回值。同名的方法如果有不同的参数列表(类型不同、个数 不同、顺序不同)则视为重载。

    重写(overrides):发生在父子类中,子类继承父类的方法,方法名相同,参数也相同,但是实现方法体不同。

    重写的注意事项 

    • 必须保证父子类中重写的方法,方法名相同,参数列表相同。
    • 子类方法的返回值必须小于或等于父类方法的返回值范围。
    • 子类方法的权限必须要大于等于父类方法的权限(不包括 private)
    • 子类重写方法异常声明必须是父类的异常或子级异常
    • 私有的不能被继承

    2.String 和 StringBuffer、 StringBuilder 的区别是什么?

    可变性

    String 类中使用 final 关键字修饰字符数组来保存字符串,所以 String 对象是不可变的。 而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是 使用字符数组保存字符串char[]value 但是没有用 final 关键字修饰, 所以这两种对象都是可变的。

    线程安全性

    String 中的对象是不可变的,也就可以理解为常量,线程安全。

    StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是 线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程 安全的。

    性能

    每次对 String 类型进行改变的时候,都会生成一个新的 String 对象, 然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引 用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获 得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险

    3.== 与 equals 的区别

    对于基本类型和引用类型 == 的作用效果是不同的,基本类型:比较的是值 是否相同; 引用类型:比较的是引用是否相同;

    equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较

    4.说说自己是怎么使用 synchronized 关键字,在项目 中用到了吗?

    修饰实例方法

    作用于当前对象实例加锁,进入同步代码前要获得当前对象 实例的锁

    修饰静态方法

    作用于当前类对象加锁,进入同步代码前要获得当前类对象 的锁

    修饰代码块

    指定加锁对象,对给定对象加锁,进入同步代码库前要获得给 定对象的锁。

    双重校验锁实现对象单例(线程安全)

    使用场景

    1. public class Singleton {
    2. private volatile static Singleton uniqueInstance;
    3. private Singleton() {
    4. }
    5. public static Singleton getUniqueInstance() {
    6. //先判断对象是否已经实例过,没有实例化过才进入加锁代码
    7. if (uniqueInstance == null) {
    8. //类对象加锁
    9. synchronized (Singleton.class) {
    10. if (uniqueInstance == null) {
    11. uniqueInstance = new Singleton();
    12. }
    13. }
    14. }
    15. return uniqueInstance;
    16. }
    17. }

    5.抽象类和接口的区别是什么?

     实现:

    抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。

    构造函数:

    抽象类可以有构造函数;接口不能有。

    实现数量:

    类可以实现很多个接口;但只能继承一个抽象类【java只支持单 继承】。

    访问修饰符:

    接口中的方法默认使用 public 修饰;抽象类中的抽象方法 可以使用Public和Protected修饰,如果抽象方法修饰符为Private,则 报错:The abstract method 方法名 in type Test can only set a visibility modifier, one of public or protected。 接口中除了static、final变量,不能有其他变量,而抽象类中则不一定

    设计层面:

    抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象, 是一种行为的规范。


    第二天:

    1.Collection 和 Collections 有什么区别? 

    Collection 是一个集合接口,它提供了对集合对象进行基本操作的通用接口 方法,所有集合都是它的 子类,比如 List、Set 等。

    Collections 是一个包装类,包含了很多静态方法,不能被实例化, 就像一个 工具类,比如提供的排序方法: Collections. sort(list)

    2 .List、Set、Map 之间的区别是什么?

    3.HashMap 和 Hashtable 有什么区别? 

    存储:

    HashMap 运行 key 和 value 为 null,而 Hashtable 不允许。

    线程安全:

    Hashtable 是线程安全的,而 HashMap 是非线程安全的。

    推荐使用:

    在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使 用 , 推 荐 在 单 线 程 环 境下 使 用 HashMap 替 代 , 如 果 需 要 多 线 程使 用 则 用 ConcurrentHashMap 替代。

     4. 说一下 HashMap 的实现原理?

    HashMap 基于 Hash 算法实现的,我们通过 put(key,value)存储,get(key)来获取。 当传入 key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,我们称之为 hash 冲突,HashMap 的 做法是用链表和红 黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链 表否则使用红黑树。

    5.说一下 HashSet 的实现原理? 

    HashSet 是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相 关方法来完成, HashSet 不允许重复的值。


    第三天:

    1.ArrayList和LinkedList的区 别

     ArrayList:

    基于动态数组,连续内存存储,适合下标访问(随机访问),扩 容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组 的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复 制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至 超过linkedList (需要创建大量的node对象)

    LinkedList:

    基于链表,可以存储在分散的内存中,适合做数据插入及删除 操作,不适合查询:需要逐一遍历,遍历LinkedList必须使用iterator, 不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需 要对list重新进行遍历,性能消耗极大。

    2.ConcurrentHashMap原 理,jdk7和jdk8版本的区别

    https://blog.csdn.net/qq_38525526/article/details/99624132https://blog.csdn.net/qq_38525526/article/details/99624132

    jdk7:

    数据结构:

    ReentrantLock+Segment+HashEntry,一个Segment中包 含了一个HashEntry数组,每个HashEntry又是一个链表结构。

    元素查询:

    二次hash,第一次hash定位到Segment,第二次hash定位元 素所在链表的头部

    锁:

    Segment分段锁,Segment继承了ReentrantLock,锁定操作的 Segment,其他Segment不受影响,并发度为Segment的个数,可以通过 构造函数指定,数组扩容不影响其他Segment get方法无须加锁, volatile保证 

    jdk8:

    数据结构:

    synchronized+CAS+Node+红黑树,Node的val和next都用 volatile修饰,保证可见性

    查找,替换,赋值操作都使用CAS

    锁:

    锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更 高,扩容时,阻塞所有读写操作,并发扩容

    读操作无锁:Node的val和next使用volatile修饰,读写线程对该变量 互相可见

    数组使用volatile修饰,保证扩容时被读线程感知。

    3.哪些集合类是线程安全的?

     Vector、Hashtable、Stack 都是线程安全的,而像 HashMap 则是非 线程安全 的,不过在JDK1.5之后随着 Java.util.concurrent 并发包 的出现,它们也有 了 自 己 对 应 的 线 程 安 全 类 , 比 如 HashMap 对 应 的 线 程 安 全 类 就 是 ConcurrentHashMap.

    4.创建线程有哪几种方式

    创建线程有三种方式:

    继承 Thread 重写 run 方法;

    实现 Runnable 接口;

    实现 Callable 接口。 

    5.说一下 runnable 和 callable 有什么区别?

    runnable 没有返回值,callable 可以拿到有返回值,callable 可以 看作是 runnable 的补充。 


    第四天:

    1.线程有哪些状态?

     当线程对象对创建后,即进入了新建状态(NEW);

    当调用线程对象的start()方法(t.start();)线程即进入就绪状态(Runnable);

    CPU获取到时间片,进入到运行状态(Running);

    当线程调用 wait()或者sleep()时,进入阻塞状态(Blocked),

    当休眠时间结束后,或者调用notify或 notifyAll时会重新进入就绪状态(Runnable),再重新获取时间片,进入运行状态

    线程执行完了或 者因异常退出了run()方法,该线程结束生命周期,进入终结状态(Dead)

    2.sleep() 和wait() 有什么区别?

    类的不同:

    sleep() 来自 Thread,wait() 来自 Object。

    释放锁:

    sleep() 不释放锁;wait() 释放锁。

    用 法 不 同

    sleep() 时 间 到 会 自 动 恢 复 ; wait() 可 以 使 用 notify()/notifyAll()直接唤醒。 

    3.notify()和 notifyAll()有什么区别?

    notifyAll()会唤醒所有的线程,notify()之后唤醒一个线程。notifyAll() 调用后, 会将全部线 程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则 留在锁池等待锁被释放后 再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个 线程由虚拟机控制。 

    4.线程的 run() 和 start() 有什么区别?

    start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复 调用,而 start() 只能调用一次。 

     5.说一说几种常见的线程池及适用场景?

    FixedThreadPool:(固定线程池)

    可重用固定线程数的线程池。(适用于负载比较重的服务器)

    SingleThreadExecutor:(单一线程池)

    只会创建一个线程执行任务。(适用于需要保证顺序执行各个任务;并且在 任意时间点,没有多线程活动的场景。)

    CachedThreadPool:(缓存线程池)

    是一个会根据需要调整线程数量的线程池。(大小无界,适用于执行很多的短期异 步任务的小程序,或负载较轻的服务器)

    ScheduledThreadPool:(延迟线程池)

    继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或 者定期执行任务。使用DelayQueue作为任务队列。


    第五天:

    1.线程池中 submit() 和 execute() 方法有什么区别?

    execute():

    只能执行 Runnable 类型的任务。

    submit():

    可以执行 Runnable 和 Callable 类型的任务。 Callable 类型的任务可以获取执行的返回值,而 Runnable 执行无返 回值。

    2.在 Java 程序中怎么保证多线程 的运行安全?

    方法一:

    使用安全类,比如 Java. util. concurrent 下的类。

    方法二:

    使用自动锁 synchronized。

    方法三:

    使用手动锁 Lock 

    3.什么是死锁?

    当线程 A 持有独占锁 a,并尝试去获取独占锁 b 的同时,线程 B 持有独 占锁 b,并 尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互 相持有对方需要的锁,而发生 的阻塞现象,我们称为死锁。 

    4.怎么防止死锁?

    尽量使用 tryLock(long timeout, TimeUnit unit)的方法 (ReentrantLock、 ReentrantReadWriteLock),设置超时时间,超 时可以退出防止死锁。

    尽量使用 Java. util. concurrent 并发类代替自己手写锁。

    尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。

    尽量减少同步的代码块

    5.ThreadLocal 是什么?有哪些 使用场景? 

    ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个 线程都可以独 立地改变自己的副本,而不会影响其它线程所对应的副本。

    ThreadLocal 的经典使用场景是数据库连接和 session 管理等。 


    第六天:

    1.synchronized 和 Lock 有什 么区别?

    synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码 块加锁。

    synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自 动释放 锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不 当没有 unLock() 去释放锁就会造成死锁。

    通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办 到。

    2.什么是 Java 序列化?什么情况 下需要序列化?

    Java 序列化是为了保存各种对象在内存中的状态,并且可以把保存的对象 状态再读出来。

    以下情况需要使用 Java 序列化: 

    想把的内存中的对象状态保存到一个文件中或者数据库中时候;

    想用套接字在网络上传送对象的时候;

    想通过 RMI(远程方法调用)传输对象的时候。

    3.动态代理是什么?有哪些应用? 

    动态代理是运行时动态生成代理类。

    应用:

    动态代理的应用有 spring aop、rpc, Java 注解对象获取等。

    4.怎么实现动态代理?

    JDK 原生动态代理和 cglib 动态代理。

    JDK 原生动态代理是基于接口实 现的

    cglib 是基于继承当前类的子类实现的。 

    5.为什么要使用克隆?

    克隆的对象可能包含一些已经修改过的属性,而 new 出来的对象的属性都 还是初始化 时候的值,所以当需要一个新的对象来保存当前对象的“状态”就 靠克隆方法了。


    第六天:

    1.Redis 是什么?都有哪些使用 场景?

    Redis 是一个使用 C 语言开发的高速缓存数据库。

    Redis 使用场景: 

    记录帖子点赞数、点击数、评论数;

    缓存近期热点数据;

    记录用户会话信息.

    2. Redis 有哪些功能?

    • 数据缓存功能

    • 分布式锁功能

    • 支持数据持久化

    • 解决分布式会话

    3.什么是缓存穿透?怎么解决?

    缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中时需要从数据 库查询,查 不到数据则不写入缓存,这将导致这个不存在的数据每次请求都 要到数据库去查询,造成缓 存穿透 

    解决方法:

    1. 最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大 的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免 了对底层存储系统的查询压力。

    2.另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查 询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个 空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

    4.什么是缓存击穿?如何解决?

    缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这 时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数 据,引起数据库压力瞬间增大,造成过大压力。 

    解决方法:

    1.设置永久不过期。【这种只适合】

    2.使用互斥锁(mutex key)业界比较常用的做法,是使用mutex。简单 地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的 SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功 时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方 法。

    代码如下:

    1. public String get(key) {
    2. String value = redis.get(key);
    3. if (value == null) { //代表缓存值过期
    4. //设置3min的超时,防止del操作失败的时候,下次缓存过
    5. 期一直不能load db
    6. if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {
    7. //代表设置成功
    8. value = db.get(key);
    9. redis.set(key, value,
    10. expire_secs);
    11. redis.del(key_mutex);
    12. } else { //这个时候代表同时候的其他线程已经
    13. load db并回设到缓存了,这时候重试获取缓存值即可
    14. sleep(50);
    15. get(key); //重试
    16. }
    17. } else {
    18. return value;
    19. }
    20. }

    5. 什么是缓存雪崩?如何解决?

     缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据 库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发 查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数 据库。

    解决方案: 

     1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

    2.如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据 库中。

    3.设置热点数据永远不过期。

    每日一更新,祝大家找到自己心仪的工作!!!!!!

  • 相关阅读:
    Zig标准库:最全数据结构深度解析(1)
    系统韧性研究(2)|系统韧性如何关联其他质量属性?
    【Linux】《Linux命令行与shell脚本编程大全 (第4版) 》笔记-Chapter24-编写简单的脚本实用工具
    【DevOps】Git 图文详解(七):标签管理
    MySQL第八讲·如何进行数学计算、字符串处理和条件判断?
    【DevOps核心理念基础】2. 敏捷开发与DevOps关系
    一位年薪35W的测试被开除,回怼的一番话,令人沉思
    金仓数据库KingbaseES客户端编程接口指南-JDBC(9. JDBC 读写分离)
    博客园页面风格+代码块美化+分类+深色模式+Mac代码
    学c还行,学Python很累,还有其他语言适合我吗?
  • 原文地址:https://blog.csdn.net/Ysuhang/article/details/126116347