请简述银行家算法
银行家算法通俗来讲其实就是把请求资源需要的资源量和操作系统剩余的进行比较,如果操作系统剩余的大于请求的量那么就可以给他分配资源,否则一点不分配。
请简述死锁产生的条件
互斥条件、请求且保持、循环等待条件、不可剥夺
解决死锁的几种方式
避免四个条件同时发生就可以了: 破坏不可剥夺,设置优先级,允许优先级高的抢占资源 破坏循环等待条件,保证多个进程的执行顺序相同
简述synchronized锁的膨胀
当一个被synchronized修饰的对象第一次被线程访问时,JVM会将该线程的线程ID写入到对象头中,并将对象头的状态修改成偏向锁,这时候如果有线程再次访问,首先它会判断该线程的线程ID和对象头内存储的ID是否相同,一样就可以得到锁,因为偏向锁时一种竞争出现才会释放锁的机制,如果不一样他会判断持有锁的线程是否还存活,不在了新线程获得锁,如果在锁会升级成轻量级锁,在轻量级锁中,JVM会在线程的栈帧中创建一个存储锁记录的空间,并将对象头中的Makeword复制到其中,利用cas尝试用指向线程锁记录的指针替换对象头中的MakeWord,成功获得锁,失败利用自选重新尝试获取,解锁时会使用cas操作将锁记录内的makeword替换回对象头,成果说明没有竞争,失败升级成重量级锁,在重量级锁,如果已经被线程池有,其他线程会进入阻塞队列,等待持有锁的线程释放锁。
什么是cas
比较然后交换 它主要有三个参数 一个预期值 一个新值 还有一个原数据的地址
进行cas操作时他会从原地址取出数据与预期值进行比较,如果相同就将值修改成新值,说明操作成功,不一样就失败。
cas 的问题如何解决
ABA问题 就是两个线程都对 一个数据A=1进行操作线程1将他的值改成别的有改回1,这个时候虽然线程2也能对他操作,但是没有记录 这个数据的变化过程。
可以采用版本号 值中添加版本号,修改更新版本号
jmm 简单理解
线程访问数据都是先看本地内存有没有本地内存没有再取主内存中更新
volatile
保证变量的可见性
被valatile修饰的变量发生写操作时 会发送一个lock为前缀的指令,会将修改的值刷回主内存,同时其他缓存中的值会失效,需要从主内存中重新拉取。
借用内存屏障禁止指令重排
单例模式 线程安全的两种代码 懒汉&恶汉
package java基础.Test719; public class Singleton { private static volatile Singleton instance; private Singleton(){ } public static Singleton getInstance(){ if(instance==null){ synchronized (Singleton.class){ if(instance==null){ instance = new Singleton(); } } } return instance; } } class Singletions{ private static volatile Singletions instance = new Singletions(); private Singletions(){ } public static Singletions getInstance(){ return instance; } }
线程池的7个参数分别是什么作用
核心线程数
最大线程数
工作队列
拒绝策略
最长空闲时间
创建线程工厂
任务执行策略
请实现交替打印abc
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class AlternatePrint { private static final ReentrantLock lock = new ReentrantLock(); private static final Condition conditionA = lock.newCondition(); private static final Condition conditionB = lock.newCondition(); private static final Condition conditionC = lock.newCondition(); private static int state = 0; // 0 - A, 1 - B, 2 - C public static void main(String[] args) { Thread threadA = new Thread(() -> print("A", 0, conditionA, conditionB)); Thread threadB = new Thread(() -> print("B", 1, conditionB, conditionC)); Thread threadC = new Thread(() -> print("C", 2, conditionC, conditionA)); threadA.start(); threadB.start(); threadC.start(); } private static void print(String letter, int threadState, Condition currentCondition, Condition nextCondition) { for (int i = 0; i < 10; i++) { // 打印10次 lock.lock(); try { while (state != threadState) { currentCondition.await(); // 等待当前状态 } System.out.print(letter); // 打印字母 state = (state + 1) % 3; // 根据状态更新下一个要打印的字母 nextCondition.signal(); // 唤醒下一个线程 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlock(); } } } }
AQS原理是什么
AQS(AbstractQueuedSynchronizer)是Java中的一个核心同步器框架,位于 java.util.concurrent
包下。它为实现锁和其他同步器提供了一个基础架构,能够处理线程的排队和同步等问题。AQS的主要原理和机制包括以下几个方面:
1. 同步状态(State)
AQS维护一个同步状态变量(state
),用于表示锁的状态或其他同步器的状态。这个状态变量通常是一个整数,通过它来控制锁的获取和释放等操作。
2. 队列(FIFO队列)
AQS使用一个先进先出的(FIFO)等待队列来管理线程的排队。这个队列由一个链表实现,线程在获取不到资源时会被封装成节点(Node
)加入到队列中,线程会在队列中等待直到获得资源。
3. 获取和释放同步状态
AQS提供了两种主要的方法来处理同步状态:
-
tryAcquire
和tryRelease
:这些方法用于尝试获取和释放同步状态的具体实现。用户可以通过继承AQS并重写这些方法来实现自定义的同步器,如锁、信号量等。 -
acquire
和release
:这些方法是公共API,依赖于tryAcquire
和tryRelease
方法,通过它们可以安全地获取和释放同步状态。如果获取失败,线程会被加入等待队列中。
4. 等待队列节点(Node)
AQS中的每个等待线程都被封装成一个 Node
对象,这些节点被组成一个双向链表。每个节点记录了线程的状态和前驱、后继节点的引用。节点的状态可以是:
-
SIGNAL
:表示线程需要被唤醒。 -
CANCELLED
:表示线程取消等待。 -
CONDITION
:表示线程在条件队列中等待。
5. 独占模式和共享模式
AQS支持两种不同的同步模式:
-
独占模式:一次只有一个线程可以获取同步状态。常见的实现有互斥锁(
ReentrantLock
)。 -
共享模式:多个线程可以共享同步状态。常见的实现有读写锁(
ReadWriteLock
)和信号量(Semaphore
)。
6. 条件队列(Condition)
AQS还支持条件队列,用于实现更复杂的线程协调机制。线程可以在条件队列中等待,直到满足某个条件后被唤醒。这种机制在 ReentrantLock
和 Condition
中得到应用。
7. 自旋和阻塞
AQS的实现利用自旋和阻塞相结合的策略来优化性能。线程在尝试获取锁时,会先进行自旋(即忙等待),如果自旋失败,才会进入阻塞状态。
总结
AQS是一个灵活且高效的同步工具,它为创建各种同步器(如锁、信号量、读写锁等)提供了基础框架。通过管理同步状态、线程队列、节点状态等,它能够实现复杂的同步控制需求。通过使用AQS,开发者可以避免重复造轮子,提高代码的可靠性和效率。
模版设计模式
1. 单例模式(Singleton Pattern)
确保一个类只有一个实例,并提供一个全局访问点。
2. 工厂模式(Factory Pattern)
定义一个创建对象的接口,但由子类决定实例化哪个类。工厂方法使得类的实例化延迟到子类。
3. 抽象工厂模式(Abstract Factory Pattern)
提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
4. 模板方法模式(Template Method Pattern)
定义一个操作的算法框架,并将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下重新定义算法的某些步骤。
5. 观察者模式(Observer Pattern)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
6. 适配器模式(Adapter Pattern)
将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
7. 策略模式(Strategy Pattern)
定义一系列算法,把它们一个个封装起来,并且使它们可以互换。本模式使得算法可独立于使用它的客户而变化。
8. 装饰者模式(Decorator Pattern)
动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式相比生成子类更加灵活。
9. 代理模式(Proxy Pattern)
为其他对象提供一种代理以控制对这个对象的访问。
10. 迭代器模式(Iterator Pattern)
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
11. 组合模式(Composite Pattern)
将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
这些模式都是软件设计中常见的工具,帮助开发者解决特定的设计问题,提高代码的可维护性和可扩展性。
jvm常见的垃圾回收算法
标记-清除
标记-复制
标记-整理
为对象分配内存的方式
指针碰撞
空闲列表
假设Java堆中内存是绝对规整的,所有被使用过的内存都被放在一 边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那 个指针向空闲空间方向挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump The Pointer)。但如果Java堆中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,那 就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分 配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称 为“空闲列表”(Free List)
除如何划分可用空间之外,还有另外一个需要考虑的问题:对象创建在虚拟机中是非常频繁的行 为,即使仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象 A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。解决这个问题 有两种可选方案:一种是对分配内存空间的动作进行同步处理——实际上虚拟机是采用CAS配上失败 重试的方式保证更新操作的原子性;另外一种是把内存分配的动作按照线程划分在不同的空间之中进 行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完 了,分配新的缓存区时才需要同步锁定。
强软弱虚引用的区别
强引用 ,即使出现OOM也不会进行垃圾回收
软引用,在即将发生OOM之前的垃圾回收不将他清除掉
弱引用 只能存活到下次GC之前
虚引用 只是垃圾回收的时候日志会提醒一下和没有没区别
讲讲cms和g1收集器的原理 流程 优缺点
原理
CMS(Concurrent Mark-Sweep)是一种以获取最短回收停顿时间为目标的垃圾收集器。它采用“标记-清除”算法。
流程
-
初始标记(Initial Marking): 标记GC Roots可以直接关联到的对象。这个阶段需要“stop the world”。
-
并发标记(Concurrent Marking): 从GC Roots开始对对象图进行遍历的标记过程,这个阶段是并发的。
-
重新标记(Remark): 修正并发标记期间因用户程序继续运作而导致的那一部分标记记录变动的对象。这个阶段需要“stop the world”。
-
并发清除(Concurrent Sweeping): 回收标记阶段确定死亡的对象空间,这个阶段是并发的。
优点
-
并发收集,停顿时间短,适合低停顿要求的应用。
缺点
-
对CPU资源敏感,可能会降低应用程序的性能。
-
无法处理浮动垃圾(concurrent mode failure)。
-
产生碎片,需要STW整理。
G1 收集器
原理
G1(Garbage First)是一种面向服务端应用的垃圾收集器,以并行和并发的方式执行。它将堆划分为多个区域(Region),并使用“标记-整理”算法。
流程
-
初始标记(Initial Marking): 标记GC Roots可以直接关联到的对象。这个阶段需要“stop the world”。
-
并发标记(Concurrent Marking): 从GC Roots开始对对象图进行遍历的标记过程,这个阶段是并发的。
-
最终标记(Final Marking): 完成标记过程。这个阶段需要“stop the world”。
-
筛选回收(Live Data Counting and Cleanup): 筛选出回收价值最大的区域进行回收,包括对这些区域的压缩和整理。
优点
-
并发和并行,停顿时间可预测。
-
区域化管理内存,减少碎片。
缺点
-
相比CMS,更复杂,可能需要更多的调优。
分配担保
定义
分配担保机制是指在发生Minor GC之前,虚拟机检测老年代可用空间是否大于新生代所有对象总空间,如果是则直接进行Minor GC,否则触发Full GC。
触发情景
-
新生代空间不足且老年代可用空间小于新生代对象总空间时。
什么是分配担保,在什么情景下触发
分配担保机制就是在发生GC前虚拟机检测老年代可用空间是否大于新生代所有对象的空间,如果是则直接进行GC,不是就触发Full GC
触发场景:新生代空间不足且老年代可用空间小于新生代对象总空间时
简单说说双亲委派和破坏双亲委派,各有什么优缺点
双亲委派模型
原理
类加载机制中,先由父类加载器尝试加载类,只有在父类加载器无法加载时,才由当前加载器尝试加载。
优点
-
避免类的重复加载,确保Java核心库的安全。
缺点
-
对于一些特定的需求(如模块隔离),可能需要打破双亲委派模型。
破坏双亲委派
场景
-
自定义类加载器,加载特定类时跳过父类加载器。
优点
-
灵活性高,可以根据需求加载特定版本的类。
缺点
-
可能导致类的重复加载和安全问题。
简单说说类加载
类加载
流程
-
加载(Loading): 查找并加载类的二进制数据。
-
链接(Linking):
-
验证(Verification): 检查类文件格式和字节码的正确性。
-
准备(Preparation): 为类的静态变量分配内存,并将其初始化为默认值。
-
解析(Resolution): 将常量池中的符号引用转换为直接引用。
-
-
初始化(Initialization): 初始化类的静态变量和静态代码块。
回答要点:
-
双亲委派模型简介:
-
简要解释双亲委派模型:当一个类加载器加载一个类时,会先委派给它的父类加载器,一直到引导类加载器。如果父类加载器无法加载该类,才由当前类加载器尝试加载。引导类加载器负责加载 Java 核心类库,包括
java.lang.Object
类。
-
-
问题背景:
-
表明在正常情况下,自定义的
java.lang.Object
类是无法被加载的,因为引导类加载器会加载 JDK 自带的java.lang.Object
类。
-
-
解决方案:
-
通过编写自定义类加载器,并重写
loadClass
和findClass
方法,可以绕过双亲委派模型,加载自定义的java.lang.Object
类。
-
-
详细步骤:
-
编写自定义类加载器: 提到需要编写一个自定义类加载器,重写
findClass
方法,不调用super.loadClass
,从而绕过双亲委派模型。 -
路径设置: 确保自定义的
java.lang.Object
类文件路径正确。 -
加载类数据: 在
loadClass
方法中,首先检查是否已经加载该类,如果没有加载且是自定义类,则直接调用findClass
方法加载类数据。对于其他类,则按双亲委派模型进行加载。
-
-
总结和注意事项:
-
强调这种方法仅用于学习和实验,在实际生产环境中不推荐使用,因为可能带来兼容性和安全性问题。
-
示例回答:
"在 Java 中,类加载机制采用双亲委派模型,这意味着当一个类加载器尝试加载一个类时,会先委派给它的父类加载器,一直到引导类加载器。如果父类加载器无法加载该类,才由当前类加载器尝试加载。引导类加载器负责加载 Java 核心类库,包括 java.lang.Object
类。因此,在正常情况下,自定义的 java.lang.Object
类是无法被加载的。
为了加载自定义的 java.lang.Object
类,我们可以绕过双亲委派模型,编写一个自定义类加载器,重写 findClass
方法,不调用 super.loadClass
。这样,我们就可以绕过双亲委派模型,直接加载自定义的类。
具体来说,有以下步骤:
-
编写一个自定义类加载器,重写
findClass
方法,不调用super.loadClass
方法。 -
在
loadClass
方法中,首先检查是否已经加载该类。如果没有加载且是自定义类,则直接调用findClass
方法加载类数据。对于其他类,则按双亲委派模型进行加载。 -
设置类路径,确保自定义的
java.lang.Object
类文件路径正确。 -
在
findClass
方法中,从文件系统或其他来源加载类的字节码数据,并使用defineClass
方法将字节码转换为 Java 类。
需要注意的是,这种方法仅用于学习和实验,因为它可能违反类加载机制的设计原则,并带来兼容性和安全性问题。在实际生产环境中,我们不推荐这样做。
有过jvm调优的经历吗,阐述一下原因和如何处理的
回答示例: “在高峰时段,我们注意到系统响应时间明显变慢,同时服务器出现内存不足的问题。通过监控工具(如 Prometheus 和 Grafana)和日志分析,我们发现频繁的 Full GC 是导致这些问题的主要原因。此外,我们还通过 JVisualVM 工具监控到内存泄漏的迹象。”
JVM 调优的过程和方法
回答示例: “我们进行了以下几方面的调优工作:
1. 垃圾回收器(GC)调优
“通过 GC 日志分析,我们决定切换到 G1 GC,这种 GC 更适合我们这种大内存且需要低停顿时间的应用。 具体的 JVM 参数调整包括:
-
-XX:+UseG1GC
-
-Xms4g
和-Xmx8g
来调整堆大小 -
-XX:MaxGCPauseMillis=200
来设置最大 GC 暂停时间。”
2. 内存调优
“使用 Eclipse MAT 工具分析后,我们找到了内存泄漏的源头,并修复了 MyBatis Plus 相关的代码,避免频繁创建和销毁大对象。我们还调整了堆内存大小,确保在高峰时段系统依然能稳定运行。”
3. 线程调优
“发现应用在高并发情况下线程管理不善,导致 CPU 使用率过高。因为他是一个外卖平台项目吗,像下单,分配订单其实还是输入I/O密集型 ,我们通过调整增加了一些线程 一般来说设置成那个(cpu个数为N)设置成2*N就可以
4. 启动参数调优
“为了进一步优化应用的启动时间,我们使用了 GraalVM 提供的 AoT(前端编译)技术,这显著减少了 JVM 的启动时间。”
基本数据类型,几个字节,数据范围
byte 1 short 2 int 4 long 8 float 4 double 8 char 2 boolean 2
接口和抽象类的区别
抽象类抽象到极致就是接口
抽象类可以非抽象方法 接口没有
抽象类更多是为了代码复用 更像是一个模板
接口更像是一种规范
java 单继承多实现
反射的认识
动态的获取、操控类信息以及他的变量对象
代理的认识
就好比海外代购 我帮你买东西,我还可以在这个基础上加一些东西 比如加钱
设计模式的七大准则
单一职责原则;
开放封闭原则;
里氏替换原则;
依赖倒置原则;
接口隔离原则