2026最强Java面试八股文(精简、纯手打)

张开发
2026/6/17 11:47:30 15 分钟阅读
2026最强Java面试八股文(精简、纯手打)
一、基础篇1.接口和抽象类的区别相似点1接口和抽象类都不能被实例化2实现接口或继承抽象类的普通子类都必须实现这些抽象方法不同点1抽象类可以包含普通方法和代码块接口里只能包含抽象方法静态方法和默认方法2抽象类可以有构造方法而接口没有3抽象类中的成员变量可以是各种类型的接口的成员变量只能是public static final类型的并且必须赋值2.重载和重写的区别重载发生在同一个类中方法名相同、参数列表、返回类型、权限修饰符可以不同重写发生在子类中方法名相、参数列表、返回类型都相同权限修饰符要大于父类方法声明异常范围要小于父类方法但是final 和private修饰的方法不可重写3.和equals的区别比较基本类型比较的是值比较引用类型比较的是内存地址equlas是Object类的方法本质上与一样但是有些类重写了equals方法比如String的equals被重写后比较的是字符值另外重写了equlas后也必须重写hashcode()方法4.异常处理机制1使用try、catch、finaly捕获异常finaly中的代码一定会执行捕获异常后程序会继续执行2使用throws声明该方法可能会抛出的异常类型出现异常后程序终止5.HashMap原理1.HashMap在Jdk1.8以后是基于数组链表红黑树来实现的特点是key不能重复可以为null线程不安全2.HashMap的扩容机制HashMap的默认容量为16默认的负载因子为0.75当HashMap中元素个数超过容量乘以负载因子的个数时就创建一个大小为前一次两倍的新数组再将原来数组中的数据复制到新数组中。当数组长度到达64且链表长度大于8时链表转为红黑树3.HashMap存取原理1计算key的hash值然后进行二次hash根据二次hash结果找到对应的索引位置2如果这个位置有值先进性equals比较若结果为true则取代该元素若结果为false就使用高低位平移法将节点插入链表JDK8以前使用头插法但是头插法在并发扩容时可能会造成环形链表或数据丢失而高低位平移发会发生数据覆盖的情况6.想要线程安全的HashMap怎么办1使用ConcurrentHashMap2使用HashTable3Collections.synchronizedHashMap()方法篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、场景题、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho7.ConcurrentHashMap原如何保证的线程安全JDK1.7:使用分段锁将一个Map分为了16个段每个段都是一个小的hashmap每次操作只对其中一个段加锁JDK1.8:采用CAS Synchronized保证线程安全每次插入数据时判断在当前数组下标是否是第一次插入是就通过CAS方式插入然后判断f.hash是否-1是的话就说明其他线程正在进行扩容当前线程也会参与扩容删除方法用了synchronized修饰保证并发下移除元素安全8.HashTable与HashMap的区别1HashTable的每个方法都用synchronized修饰因此是线程安全的但同时读写效率很低2HashTable的Key不允许为null3HashTable只对key进行一次hashHashMap进行了两次Hash4HashTable底层使用的数组加链表9.ArrayList和LinkedList的区别ArratList的底层使用动态数组默认容量为10当元素数量到达容量时生成一个新的数组大小为前一次的1.5倍然后将原来的数组copy过来因为数组在内存中是连续的地址所以ArrayList查找数据更快由于扩容机制添加数据效率更低LinkedList的底层使用链表在内存中是离散的没有扩容机制LinkedList在查找数据时需要从头遍历所以查找慢但是添加数据效率更高10.如何保证ArrayList的线程安全1使用collentions.synchronizedList方法为ArrayList加锁2使用VectorVector底层与Arraylist相同但是每个方法都由synchronized修饰速度很慢3使用juc下的CopyOnWriterArrayList该类实现了读操作不加锁写操作时为list创建一个副本期间其它线程读取的都是原本list写操作都在副本中进行写入完成后再将指针指向副本。11.String、StringBuffer、StringBuilder的区别String 由 char[] 数组构成使用了 final 修饰对 String 进行改变时每次都会新生成一个 String 对象然后把指针指向新的引用对象。StringBuffer可变并且线程安全StringBuiler可变但线程不安全。操作少量字符数据用 String单线程操作大量数据用 StringBuilder多线程操作大量数据用 StringBuffer。12.hashCode和equalshashCode()和equals()都是Obkect类的方法hashCode()默认是通过地址来计算hash码但是可能被重写过用内容来计算hash码equals()默认通过地址判断两个对象是否相等但是可能被重写用内容来比较两个对象所以两个对象相等他们的hashCode和equals一定相等但是hashCode相等的两个对象未必相等如果重写equals()必须重写hashCode()比如在HashMap中key如果是String类型String如果只重写了equals而没有重写hashcode的话则两个equals()比较为true的key因为hashcode不同导致两个key没有出现在一个索引上就会出现map中存在两个相同的key13.面向对象和面向过程的区别面向对象有封装、继承、多态性的特性所以相比面向过程易维护、易复用、易扩展但是因为类调用时要实例化所以开销大性能比面向过程低4.深拷贝和浅拷贝浅拷贝:浅拷贝只复制某个对象的引用而不复制对象本身新旧对象还是共享同一块内存深拷贝:深拷贝会创造一个一摸一样的对象新对象和原对象不共享内存修改新对象不会改变原对对象。15.多态的作用多态的实现要有继承、重写父类引用指向子类对象。它的好处是可以消除类型之间的耦合关系增加类的可扩充性和灵活性。16.什么是反射反射是通过获取类的class对象然后动态的获取到这个类的内部结构动态的去操作类的属性和方法。应用场景有要操作权限不够的类属性和方法时、实现自定义注解时、动态加载第三方jar包时、按需加载类节省编译和初始化时间获取class对象的方法有class.forName(类路径)类.class()对象的getClass17.Java创建对象得五种方式?(1)new关键字 (2)Class.newInstance (3)Constructor.newInstance(4)Clone方法 (5)反序列化篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、场景题、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho二.Java多线程篇1.进程和线程的区别进程间如何通信进程系统运行的基本单位进程在运行过程中都是相互独立但是线程之间运行可以相互影响。线程独立运行的最小单位一个进程包含多个线程且它们共享同一进程内的系统资源进程间通过管道、 共享内存、信号量机制、消息队列通信2. 什么是线程上下文切换当一个线程被剥夺cpu使用权时切换到另外一个线程执行3.什么是死锁死锁指多个线程在执行过程中因争夺资源造成的一种相互等待的僵局4.死锁的必要条件互斥条件同一资源同时只能由一个线程读取不可抢占条件不能强行剥夺线程占有的资源请求和保持条件请求其他资源的同时对自己手中的资源保持不放循环等待条件在相互等待资源的过程中形成一个闭环想要预防死锁只需要破坏其中一个条件即可比如使用定时锁、尽量让线程用相同的加锁顺序还可以用银行家算法可以预防死锁5.Synchrpnized和lock的区别1synchronized是关键字lock是一个类2 synchronized在发生异常时会自动释放锁lock需要手动释放锁3synchronized是可重入锁、非公平锁、不可中断 锁lock的ReentrantLock是可重入锁可中断锁可以是公平锁也可以是非公平锁4synchronized是JVM层次通过监视器实现的Lock是通过AQS实现的6.什么是AQS锁?AQS是一个抽象类可以用来构造锁和同步类如ReentrantLockSemaphoreCountDownLatchCyclicBarrier。AQS的原理是AQS内部有三个核心组件一个是state代表加锁状态初始值为0一个是获取到锁的线程还有一个阻塞队列。当有线程想获取锁时会以CAS的形式将state变为1CAS成功后便将加锁线程设为自己。当其他线程来竞争锁时会判断state是不是0不是0再判断加锁线程是不是自己不是的话就把自己放入阻塞队列。这个阻塞队列是用双向链表实现的可重入锁的原理就是每次加锁时判断一下加锁线程是不是自己是的话state1释放锁的时候就将state-1。当state减到0的时候就去唤醒阻塞队列的第一个线程。7.为什么AQS使用的双向链表因为有一些线程可能发生中断 而发生中断时候就需要在同步阻塞队列中删除掉这个时候用双向链表方便删除掉中间的节点8.有哪些常见的AQS锁AQS分为独占锁和共享锁ReentrantLock独占锁可重入可中断可以是公平锁也可以是非公平锁非公平锁就是会通过两次CAS去抢占锁公平锁会按队列顺序排队Semaphore信号量:设定一个信号量当调用acquire()时判断是否还有信号有就获取一个信号量没有就阻塞等待其他线程释放信号量当调用release()时释放一个信号量唤醒阻塞线程。应用场景允许多个线程访问某个临界资源时如上下车买卖票CountDownLatch倒计数器:给计数器设置一个初始值当调用CountDown()时计数器减一当调用await() 时判断计数器是否归0不为0就阻塞直到计数器为0。应用场景启动一个服务时主线程需要等待多个组件加载完毕之后再继续执行CyclicBarrier循环栅栏:给计数器设置一个目标值,当调用await() 时会计数1并判断计数器是否达到目标值未达到就阻塞直到计数器达到目标值应用场景多线程计算数据最后合并计算结果的应用场景9.sleep()和wait()的区别(1)wait()是Object的方法sleep()是Thread类的方法(2)wait()会释放锁sleep()不会释放锁(3)wait()要在同步方法或者同步代码块中执行sleep()没有限制(4)wait()要调用notify()或notifyall()唤醒,sleep()自动唤醒10.yield()和join()区别yield()调用后线程进入就绪状态A线程中调用B线程的join() ,则B执行完前A进入阻塞状态11.线程池七大参数核心线程数线程池中的基本线程数量最大线程数当阻塞队列满了之后逐一启动最大线程的存活时间当阻塞队列的任务执行完后最大线长的回收时间最大线程的存活时间单位阻塞队列当核心线程满后后面来的任务都进入阻塞队列线程工厂用于生产线程任务拒绝策略阻塞队列满后拒绝任务有四种策略1抛异常2丢弃任务不抛异常3打回任务4尝试与最老的线程竞争12.Java内存模型JMMJava内存模型 屏蔽了各种硬件和操作系统的内存访问差异实现让Java程序在各平台下都能达到一致的内存访问效果它定义了JVM如何将程序中的变量在主存中读取具体定义为所有变量都存在主存中主存是线程共享区域每个线程都有自己独有的工作内存线程想要操作变量必须从主从中copy变量到自己的工作区每个线程的工作内存是相互隔离的由于主存与工作内存之间有读写延迟且读写不是原子性操作所以会有线程安全问题13.保证并发安全的三大特性原子性一次或多次操作在执行期间不被其他线程影响可见性当一个线程在工作内存修改了变量其他线程能立刻知道有序性JVM对指令的优化会让指令执行顺序改变有序性是禁止指令重排14.volatile保证变量的可见性和有序性不保证原子性。使用了 volatile 修饰变量后在变量修改后会立即同步到主存中每次用这个变量前会从主存刷新。单例模式双重校验锁变量为什么使用 volatile 修饰 禁止 JVM 指令重排序new Object()分为三个步骤为实例对象分配内存用构造器初始化成员变量将实例对象引用指向分配的内存实例对象在分配内存后实才不为null。如果分配内存后还未初始化就先将实例对象指向了内存那么此时最外层的if会判断实例对象已经不等于null就直接将实例对象返回。而此时初始化还没有完成。15.线程使用方式(1)继承 Tread 类(2)实现 Runnable 接口(3)实现 Callable 接口带有返回值(4)线程池创建线程16.ThreadLocal原理原理是为每个线程创建变量副本不同线程之间不可见保证线程安全。每个线程内部都维护了一个Mapkey为threadLocal实例value为要保存的副本。但是使用ThreadLocal会存在内存泄露问题因为key为弱引用而value为强引用每次gc时key都会回收而value不会被回收。所以为了解决内存泄漏问题可以在每次使用完后删除value或者使用static修饰ThreadLocal可以随时获取value17.什么是CAS锁CAS锁可以保证原子性思想是更新内存时会判断内存值是否被别人修改过如果没有就直接更新。如果被修改就重新获取值直到更新完成为止。这样的缺点是1只能支持一个变量的原子操作不能保证整个代码块的原子操作2CAS频繁失败导致CPU开销大3ABS问题:线程1和线程2同时去修改一个变量将值从A改为B但线程1突然阻塞此时线程2将A改为B,然后线程3又将B改成A,此时线程1将A又改为B,这个过程线程2是不知道的这就是ABA问题可以通过版本号或时间戳解决18.Synchronized锁原理和优化Synchronize是通过对象头的markwordk来表明监视器的监视器本质是依赖操作系统的互斥锁实现的。操作系统实现线程切换要从用户态切换为核心态成本很高此时这种锁叫重量级锁在JDK1.6以后引入了偏向锁、轻量级锁、重量级锁偏向锁当一段代码没有别的线程访问此时线程去访问会直接获取偏向锁轻量级锁当锁是偏向锁时有另外一个线程来访问会升级为轻量级锁。线程会通过CAS方式获取锁不会阻塞提高性能重量级锁轻量级锁自旋一段时间后线程还没有获取到锁会升级为重量级锁重量级锁时来竞争锁的所有线程都会阻塞性能降低注意锁只能升级不能降级19.如何根据 CPU 核心数设计线程池线程数量IO 密集型线程中十分消耗Io的线程数*2CPU密集型 cpu线程数量20.AtomicInteger的使用场景AtomicInteger是一个提供原子操作的Integer类使用CASvolatile实来现线程安全的数值操作。因为volatile禁止了jvm的排序优化,所以它不适合在并发量小的时候使用只适合在一些高并发程序中使用

更多文章