Quellcode durchsuchen

新增面经基础部分笔记
新增面经并发编程部分笔记

seamew vor 2 Jahren
Ursprung
Commit
506b6233fb

+ 2 - 0
面经/问答/JVM.md

@@ -113,3 +113,5 @@ jvm内存区域大体上可以分为线程私有和线程共有两大部分
 
 
 
+
+

BIN
面经/问答/assets/image-20230506170615271.png


BIN
面经/问答/assets/image-20230506170616683.png


+ 6 - 0
面经/问答/基础.md

@@ -0,0 +1,6 @@
+## 为什么重写 equals() 时必须重写 hashCode() 方法?
+
+根据Java规范的规定,如果两个对象相等,那么它们的哈希值必须相等。也就是说如果 `equals` 方法判断两个对象是相等的,那这两个对象的 `hashCode` 值也要相等。如果重写 `equals()` 时没有重写 `hashCode()` 方法的话就可能会导致 `equals` 方法判断是相等的两个对象,`hashCode` 值却不相等。
+
+例如hashset集合中插入的对象重写 equals()但是没有重写 hashCode() 就会导致插入重复的元素。
+

+ 104 - 0
面经/问答/并发编程.md

@@ -0,0 +1,104 @@
+## 为什么wait和notify必须放在synchronized中?
+
+在Java中,wait()和notify()是Object类的两个方法,它们用于实现线程间的协作。wait()使一个线程进入等待状态,直到另一个线程发出通知唤醒它。而notify()则用于唤醒正在等待的线程。
+
+wait()和notify()必须放在synchronized块中是因为这些方法依赖于对象的监视器锁(也称为互斥锁)。只有获得了对象的监视器锁(该锁即为调用wait方法的对象)的线程才能调用wait()和notify()方法。如果这些方法不在同步块中使用,就无法保证线程安全性。
+
+当一个线程调用wait()方法时,它会释放持有的锁并进入等待状态,等待其他线程调用notify()方法来唤醒它。如果wait()方法不在同步块中使用,那么该线程在释放锁之后可能已经被其他线程修改了。这样,即使其他线程调用了notify()方法唤醒了它,该线程也无法正确地处理唤醒事件。
+
+同样,notify()方法也必须在同步块中使用。如果一个线程在未获得锁的情况下调用notify()方法,那么它将无法通知任何等待的线程。因为它没有获取锁,所以它不能访问共享数据或执行必要的同步操作来确保正确的通知。
+
+因此,使用wait()和notify()方法时,必须在同步块中使用它们,以确保线程之间的安全性并避免出现竞态条件。
+
+## volatile 关键字
+
+在Java中,volatile关键字是用于保证多线程环境下变量的可见性和有序性。主要分为两大功能。
+
+1. 保证变量可见性:被volatile关键字声明代表变量是共享且不稳定的,每次使用它都到主存中进行读取,并且强制刷新到内存。
+2. 进制指令重排序:JVM 具有指令重排的特性,可以保证程序执行效率更高,但是volatile关键字会进制指令重排保证其有序性。
+
+## volatile 关键字底层原理
+
+`volatile`是通过编译器在生成字节码时,在指令序列中添加“**内存屏障**”来禁止指令重排序的。
+
+JMM层面的“**内存屏障**”:
+
+- **LoadLoad屏障**: 对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
+- **StoreStore屏障**:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
+- **LoadStore屏障**:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作执行前,保证Load1要读取的数据被读取完毕。
+- **StoreLoad屏障**: 对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。
+
+JVM的实现会在volatile读写前后均加上内存屏障,在一定程度上保证有序性。如下所示:
+
+> **LoadLoad**
+> **volatile 读操作**
+> **LoadStore**
+>
+> **StoreStore**
+> **volatile 写操作**
+> **StoreLoad**
+
+**禁止指令重排,汇编层面**
+
+>  **lock 前缀**:lock不是内存屏障,而是一种锁。执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU
+
+## synchronized 关键字
+
+`synchronized` 是 Java 中的一个关键字,翻译成中文是同步的意思,主要解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
+
+在JDK1.6之前synchronized关键字基于操作系统mutux互斥变量实现,该操作较为重量级,但是在JDK1.6之后进行的JVM层面的锁优化。效率大大提升,优化后的锁主要分为一下四类
+
+1. 无锁状态
+2. 偏向锁状态
+3. 轻量级锁状态
+4. 重量级锁状态
+
+锁可以升级,但不能降级。即:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁是单向的。首先程序判断该对象是否有锁,如果没有锁则将无锁状态转换为偏向锁状态,这里通过CAS操作进行加锁,如果加锁成功之后,就不会再有解锁等操作了。假如有两个线程来竞争该锁话,那么偏向锁就失效了,进而升级成轻量级锁了。
+
+轻量级之所以是轻量级锁,是因为它仅仅使用 CAS 进行操作来获取锁。如果获取成功那么会直接获取锁,如果失败,当前线程便尝试使用自旋来获取锁。当竞争线程的自旋次数达到界限值(`threshold`),轻量级锁将会膨胀为重量级锁。
+
+重量级锁(`heavy weight lock`),是使用操作系统互斥量(`mutex`)来实现的传统锁。 当所有对锁的优化都失效时,将退回到重量级锁。它与轻量级锁不同竞争的线程不再通过自旋来竞争线程, 而是直接进入堵塞状态,此时不消耗CPU,然后等拥有锁的线程释放锁后,唤醒堵塞的线程, 然后线程再次竞争锁。但是注意,当锁膨胀(`inflate`)为重量锁时,就不能再退回到轻量级锁。
+
+## synchronized 和 volatile 有什么区别?
+
+* `volatile` 关键字是线程同步的轻量级实现,所以性能肯定比`synchronized`关键字要好 。
+
+* `volatile` 关键字只能用于变量而 `synchronized` 关键字可以修饰方法以及代码块 。
+
+* `volatile` 关键字能保证数据的可见性,但不能保证数据的原子性。`synchronized` 关键字两者都能保证。
+
+* `volatile`关键字主要用于解决变量在多个线程之间的可见性,而 `synchronized` 关键字解决的是多个线程之间访问资源的同步性。
+
+# ReentrantLock锁
+
+`ReentrantLock` 实现了 `Lock` 接口,是一个可重入且独占式的锁,他的底层是基于CAS+AQS+LockSupport实现的
+
+* CAS
+
+CAS是Compare and Swap的缩写,即比较并交换。它是一种无锁算法,在多线程编程中用于实现同步操作。简单来说,CAS操作包括三个操作数:内存位置V、预期值A和新值B。当且仅当内存位置V的值等于预期值A时,才将该位置的值更新为新值B。
+
+* AQS
+
+AQS抽象队列同步器。它是Java并发包中锁和同步工具的核心实现上面的所说的LOCK锁就是基于AQS实现的
+
+AQS提供了一种通用的框架,用于实现线程间的协作和同步操作。它的核心思想是使用一个先进先出的等待队列来管理线程状态,同时支持独占模式和共享模式两种同步方式。
+
+![image-20230506170616683](assets/image-20230506170616683.png)
+
+* LockSupport
+
+基于C语言底层实现,它可以阻塞线程以等待许可证,或者取消线程的阻塞状态,而不需要使用传统的synchronized关键字或Object.wait()/notify()方法。
+
+## synchronized 和 ReentrantLock 有什么区别?
+
+* synchronized 依赖于 JVM 而 `ReentrantLock` 依赖于 API
+* `ReentrantLock`可以指定是公平锁还是非公平锁。而`synchronized`只能是非公平锁。
+* `synchronized`是以代码块形式实现的,因此只能对整个代码块进行同步操作,无法在代码块内部实现一些细粒度的控制。而`ReentrantLock`可以通过`Condition`对象实现线程间的协作和控制。
+
+## LockSupport和synchronized的区别
+
+* LockSupport不需要获取锁对象,因此避免了可能出现的死锁问题。
+
+* LockSupport可以响应中断,而Object.wait()/notify()方法无法响应中断。
+
+* LockSupport可以先执行unpark()方法,然后再执行park()方法,而Object.notify()方法必须在对应的wait()方法之前执行。