Java基础八股文
Java基础八股文
接口和抽象类有什么区别?
接口 是用来定义行为规范的,适用于多个类需要实现相同的功能
抽象类 本质还是类,用来定义类的模板,适用于需要提供通用功能的同时有需要部分功能由子类实现
具体区别如下:
接口用interface定义,方法默认public abstract,字段是public static final,没有构造器,不能实例化。
抽象类用abstract定义,可以包含抽象方法(没有方法体)和普通方法,可以有构造器,但不能直接实例化
一个类可以实现多个接口,但只能继承一个抽象类,(多继承会造成菱形继承问题)
JDK 动态代理和 CGLIB 动态代理有什么区别?
| 特性 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 实现方式 | 基于 Java 的反射机制,使用 java.lang.reflect.Proxy 类。 |
基于字节码生成技术,依赖 net.sf.cglib 包。 |
| 代理目标 | 只能代理接口(必须有接口)。 | 可以代理类(不需要接口)。 |
| 原理 | 动态生成代理类,代理类实现目标对象的接口并重写方法。 | 动态生成目标类的子类,并覆盖其非 final 方法。 |
| 字节码生成工具 | 使用 JDK 自带的 Proxy 类。 |
使用 ASM 框架操作字节码。 |
你使用过 Java 的反射机制吗?如何应用反射?
Java 反射 是 Java 提供的一种机制,允许程序在运行时动态地检查和操作类、方法、字段和构造函数。通过反射,可以在运行时获取类的详细信息(比如类名、方法、字段等),并且可以动态调用方法或访问字段。
反射的作用:
在运行时动态获取类的完整信息(包括名称、构造器、字段和方法等)。
在运行时创建对象或调用方法,而无需明确指定类。
开发框架和工具(例如 Spring、MyBatis 等,都利用反射实现动态功能)。
操作不可直接访问的私有成员或方法(通过修改访问权限)。
反射的用法
1 | //获取class对象 |
说说 Java 中 HashMap 的原理?
HashMap 是一种基于哈希表的集合,允许以键值对形式存储数据,并支持快速查找和操作。
在jdk1.7之前,HashMap采用数组+链表 结构存储数据,但如果哈希冲突过多,会导致链表变长,降低查询效率。
在jdk1.8之后,HashMap采用数组+链表+红黑树 结构存储数据,当链表长度大于8(默认),链表就会转化为红黑树,提高查询效率。
HashMap在存储数据时,使用hashCode()方法计算哈希值,并通过indexFor方法(jdk1.7及之后版本移出来这个方法,采用(n-1)&hash)确定元素在数组中的存储位置。如果位置一样,就采用链表法解决哈希冲突。
HashMap默认存储容量为16,负载因子0.75,2倍扩容。当元素数量超过16*0.75=12个时,进行扩容,但扩容比较耗时,会影响性能。
HashMap常用方法:put(k key,v value),get(Object key),remove(Object key)
Java 中有哪些集合类?请简单介绍
java中的集合类主要有两大类:Collection和Map
Collection(存储对象)中包含Set、List、Queue
Set: HashSet(哈希表,无序,不重复)、LinkedHashSet(链表和哈希表,维护插入顺序,不重复)、TreeSet(红黑树,元素有序,不重复)
List: ArrayList(动态数组,查询快,插入删除慢)、LinkedList(双向链表,插入删除快,查询慢),Vector(线程安全的动态数组,但开销大)
Queue: PriorityQueue(优先级堆)、LinkedList(可作为队列,支持FIFO操作)
Map(键值对)中包含HashMap(哈希表,无序,键不重复)、LinkedHashMap(链表和哈希表,维护插入顺序,键不重复)、TreeMap(红黑树,有序,键不重复)、Hashtable(线程安全,键或值不为null)、ConcurrentHashMap(线程安全,高并发,键或值不为null)
Java 中 HashMap 的扩容机制是怎样的?
HashMap默认存储容量为16,负载因子0.75,2倍扩容。
- HashMap底层维护了Node类型的数组table,默认为null
- 当创建对象时,将加载因子(loadfactor)初始化为0.75
- 当添加k-v时,通过key的哈希值得到下table 的索引,然后判断该索引处是否有元素,如果没有元素直接添加,如果该索引处有元素,则继续判断该元素的key是否和准备加入的key值一样,如果相等,则直接替换val,如果不相等需要判断是树结构还是链表结构,做出 相应的处理,如果添加时发现容量不够,则需要扩容
- 第一次添加时,扩容table容量为16,临界值(threshold)为12(16*0.75)
- 以后再次扩容,则需要扩容table容量为原来的两倍,临界值为原来的两倍,即24,以此类推
- 在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACIYU(默认64),就会树化(红黑树)
你了解 Java 线程池的原理吗?
Java线程池是一种池化技术,用于预先创建管理一组线程,避免频繁创建和销毁线程的开销,提高性能和响应速度。
线程池的核心参数
- 核心线程数:线程池中保持活动状态的最小线程数。即使线程池空闲,这些线程也不会被销毁,除非设置了
allowCoreThreadTimeOut。 - 最大线程数:线程池中允许的最大线程数,包括核心线程和非核心线程。
- 空闲存活时间:非核心线程的最大空闲时间,超过这个时间非核心线程会被销毁。如果设置了
allowCoreThreadTimeOut,核心线程也会遵守这个时间。 - 时间单位:用于指定
keepAliveTime的时间单位。 - 工作队列: 存放等待执行的任务的阻塞队列。
- 线程工厂:用于创建新线程。
- 拒绝策略:当任务无法被执行时的处理策略,如直接丢弃任务、抛出异常等。
工作原理
- 默认情况下,线程池不会提前创建线程,任务提交后才会创建线程(除非调用
prestartAllCoreThreads来提前创建核心线程)。 - 如果线程池中线程数小于核心线程数,线程池会优先创建核心线程来处理任务。
- 如果核心线程数已满,新任务会进入工作队列等待。
- 如果工作队列已满且线程池中的线程数小于最大线程数,线程池会创建非核心线程处理任务。
- 如果线程池已达到最大线程数且工作队列已满,新的任务会根据指定的拒绝策略进行处理。
- 当线程空闲时间超过
keepAliveTime后,非核心线程会被回收。如果设置了allowCoreThreadTimeOut,核心线程也会被回收。
Java创建线程池
1 |
|
你使用过哪些 Java 并发工具类?
java并发工具类:ConcurrentHashMap、Atomiclnteger、Semaphore、CyclicBarrier、CountDownLatch、BlockingQueue
ConcurrentHashMap:线程安全,适用于高并发环境
Atomiclnteger :提供了一个支持原子操作的 int 类型变量,基于CAS实现,适合高并发
Semaphore :用于限制同时访问资源的线程数量
CyclicBarrier: 让一组线程在到达某个屏障点后继续执行,适合多线程分阶段执行任务
CountDownLatch :一个线程等待其他线程完成任务后再继续执行。倒计数器,线程通过 countDown() 减少计数,计数为 0 时释放等待线程。
BlockingQueue: 线程安全的队列,支持阻塞操作,适合生产者-消费者模式。
什么是 Java 的 CAS(Compare-And-Swap)操作?
Java中的CAS是一种实现多线程并发操作的无锁机制,由硬件原子指令实现。
它是通过比较内存中的某个变量是否为预期值,如果是,则更新为新值,否则不修改。
先比较(Compare),如果相等,则交换(Swap)更新为新值,不相等就失败重试
CAS操作会引起ABA问题,什么是ABA问题?
就是一个变量值从A变为B,再从B变为A,CAS会认为没有变化,导致错误
解决办法:引入版本号或时间戳,每次更新的同时更新版本号
说说 AQS 吧?
AQS(AbstractQueuedSynchronizer,抽象队列同步器)是 Java 并发工具包中的一个核心框架,用于构建锁、信号量以及其他同步器工具的基础。
AQS就是起到一个抽象、封装的作用,将一些排队、入队、加锁、中断等方法提供出来,便于其他JUC锁的使用。
AQS 通过一个先进先出(FIFO)等待队列和一个 state 状态变量来管理线程的同步状态和排队机制,提供了一个通用的框架来实现同步器。
AQS 的两种工作模式
独占模式:只有一个线程可以访问共享资源。如 ReentrantLock 的实现。
核心方法:tryAcquire(int arg):尝试获取资源;tryRelease(int arg):尝试释放资源。
共享模式:多个线程可以同时访问共享资源。如 Semaphore 和 CountDownLatch 的实现。
核心方法:tryAcquireShared(int arg):尝试以共享模式获取资源;tryReleaseShared(int arg):尝试以共享模式释放资源。
AQS 的典型实现
ReentrantLock:基于 AQS 的独占模式实现。公平锁和非公平锁的实现不同:
- 公平锁:线程按照进入队列的顺序获取锁;
- 非公平锁:直接抢占锁。
Semaphore:基于 AQS 的共享模式实现,用于限制同时访问资源的线程数量。
CountDownLatch:使用 AQS 的共享模式实现倒计时功能。
CyclicBarrier:基于 AQS 实现线程的同步点。
Synchronized 和 ReentrantLock 有什么区别?
Synchronized 是Java内置的关键字,是一种隐式锁,可以实现基本的同步机制,不支持超时,非公平,不可中断,不支持多条件。
ReentrantLock 是JUC类库提供的,由JDK1.5引入,支持设置超时时间,可以避免死锁,比较灵活,支持公平锁,可以中断,支持多条件判断。
ReentrantLock 需要手动解锁,而Synchronized 不需要,都是可重入锁
一般情况用Synchronized 即可,比较简单,而ReentrantLock比较灵活,支持的功能比较多,复杂情况下用ReentrantLock
可重入锁 是指一种允许同一个线程在持有锁的情况下再次获取该锁的锁机制。它解决了线程因重复获取自身已持有的锁而导致的死锁问题。
Java 中 volatile 关键字的作用是什么?
Java中Volatile 的作用是保证变量的可见性和禁止指令重排优化
可见性:是指当一个线程修改了变量的值,新值会立马刷新到主内存中,其他线程在读取该变量时立马就可以获取到最新的值,避免了线程由于缓存一致性的问题导致的读取旧值的现象。
禁止指令重排优化:由于编译器和CPU为了优化性能,会对指令顺序进行重新排序,导致实际执行顺序和代码顺序不同,使用了Volatile就可以禁止指令重排,保证代码执行的顺序性。


