diff --git "a/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - JVM.md" "b/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - JVM.md" index da14b59..0a4d06b 100644 --- "a/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - JVM.md" +++ "b/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - JVM.md" @@ -428,11 +428,11 @@ Java内存模型(`JMM`,Java Memory Model)主要关注的是线程之间如 > 在JDK1.2版之后提供了WeakReference类来实现弱引用,弱引用主要在ThreadLocal中使用。弱引|用对象本身也可以使用引|用队列进行回收。 - - ***虚引用:*无法获取包含的对象。唯一用途是当对象被回收时,可以接收到对应的通知,知道对象被回收了。** + - ***虚引用:无法获取包含的对象。唯一用途是当对象被回收时,可以接收到对应的通知,知道对象被回收了。** > Java中使用PhantomReference实现了虚引用,使用虚引用实现了直接内存中为了及时知道直接内存对象不再使用,从而回收内存。 - - ***终结器引用:*对象回收时可以自救,不建议使用。(在对象需要被回收时,对象将会被放置在Finalizer类中的引用队列中,并在稍后由一条由FinalizerThread线程从队列中获取对象,然后执行对象的finalize方法(再将自身对象使用强引用关联上))** + - **终结器引用:对象回收时可以自救,不建议使用。(在对象需要被回收时,对象将会被放置在Finalizer类中的引用队列中,并在稍后由一条由FinalizerThread线程从队列中获取对象,然后执行对象的finalize方法(再将自身对象使用强引用关联上))** - **可达性分析法**: diff --git "a/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - Java\345\237\272\347\241\200.md" "b/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - Java\345\237\272\347\241\200.md" index 71a3846..cc57505 100644 --- "a/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - Java\345\237\272\347\241\200.md" +++ "b/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - Java\345\237\272\347\241\200.md" @@ -2118,31 +2118,31 @@ Timer 可以实现延时任务,也可以实现周期性任务。 因为使用线程池进行任务调度,所以不会因某个任务的异常终止而导致其他任务停止。并且它提供了更灵活的 API,可以更精细地控制任务的执行周期和策略。 ```java - public static void main(String[] args) { - ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); - - // 延迟3秒后执行任务 - executor.schedule( - () -> System.out.println("Task running... "), - 3, - TimeUnit.SECONDS); - - // 初始延迟1秒后开始执行任务,之后每2秒执行一次 - executor.scheduleAtFixedRate( - () -> System.out.println("Task executed at " + System.currentTimeMillis()), // Runnable - 1, // initialDelay - 2, // period - TimeUnit.SECONDS); - - // 模拟长时间运行,实际应用中应该有一个条件来决定何时关闭线程池 - try { - Thread.sleep(10000); // 让主线程等待10秒 - // 关闭线程池 - executor.shutdown(); - } catch (InterruptedException e) { - e.printStackTrace(); - } +public static void main(String[] args) { + ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); + + // 延迟3秒后执行任务 + executor.schedule( + () -> System.out.println("Task running... "), + 3, + TimeUnit.SECONDS); + + // 初始延迟1秒后开始执行任务,之后每2秒执行一次 + executor.scheduleAtFixedRate( + () -> System.out.println("Task executed at " + System.currentTimeMillis()), // Runnable + 1, // initialDelay + 2, // period + TimeUnit.SECONDS); + + // 模拟长时间运行,实际应用中应该有一个条件来决定何时关闭线程池 + try { + Thread.sleep(10000); // 让主线程等待10秒 + // 关闭线程池 + executor.shutdown(); + } catch (InterruptedException e) { + e.printStackTrace(); } +} ``` ## 例:超时关闭不付款的订单 @@ -2477,11 +2477,11 @@ Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种 - **`CyclicBarrier`**:可以让一组线程互相等待,直到到达某个公共屏障点 - **`Semaphore`**:信号量,可以控制对特定资源的访问线程数 - **`volatile`**:Java 中的关键字,确保变量的可见性,防止指令重排 -- **`AtomicInteger`**,可以用于实现线程安全的计数器或其他共享变量。 +- **`AtomicInteger`**:可以用于实现线程安全的计数器或其他共享变量。 补充 Object 中的方法说明: -- **Object 和 synchronized **——wait()、notify()、notifyAll():使线程进入等待状态,释放锁。唤醒单个等待线程。唤醒所有等待线程。 +- **Object 和 synchronized**——wait()、notify()、notifyAll():使线程进入等待状态,释放锁。唤醒单个等待线程。唤醒所有等待线程。 - **Lock 和 Condition**——await()、signal():使持有ReentranLock锁的线程等待。唤醒持有ReentranLock锁的线程。 - **BlockingQueue**——put()、take():将元素放入阻塞队列。从队列中获取取元素 @@ -2808,19 +2808,27 @@ workQueue - 当没有空闲核心线程时,新来任务会加入到此队列 - **`FixedThreadPool`**:固定线程数量的线程池,可控制线程最大并发数,超出的线程会在队列中等待,**允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM** - image-20240513164118730 + image-20240513164118730 + + - **`SingleThreadExecutor`**:单线程化的线程池,保证所有任务按照指定顺序执行,**允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM** - image-20240513164133123 + image-20240513164133123 + + - **`CachedThreadPool`**:可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程,**允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM** - image-20240513164601273 + image-20240513164601273 + + - **`ScheduledThreadPool`**:可以执行延迟任务的线程池,支持定时及周期性任务执行 - image-20240513164158611 + image-20240513164158611 + + - **`WorkStealingPool`**:基于任务窃取算法的线程池。线程池中的每个线程维护一个双端队列(deque),线程可以从自己的队列中取任务执行。如果线程的任务队列为空,它可以从其他线程的队列中"窃取"任务来执行,达到负载均衡的效果。适合大量小任务并行执行,特别是递归算法或大任务分解成小任务的场景。 @@ -2888,7 +2896,7 @@ public void execute(Runnable command) { ## 底层原理:线程池的动态调整是如何保证线程安全的? -**1. 使用 `volatile` 修饰 核心线程数 和 最大线程数 ** +**1. 使用 `volatile` 修饰 核心线程数 和 最大线程数** 核心线程数`corePoolSize` 和最大线程数 `maximumPoolSize` 都是用 `volatile` 修饰的,保证了当这些字段被修改时,其他线程能够看到最新的值,而且不会发生指令重排序,确保了多线程环境下的可见性和有序性。 diff --git "a/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - Spring.md" "b/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - Spring.md" index b26deaa..6224742 100644 --- "a/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - Spring.md" +++ "b/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - Spring.md" @@ -332,7 +332,7 @@ Bean不一定是线程安全的。 **循环依赖**:有多个类被Spring管理,它们在实例化时互相持有对方,最终形成闭环。 -img +img 示例代码: @@ -403,7 +403,7 @@ Spring为单例搞的三个 map,也就是三级依赖: 步骤 2 中如果查询发现 Bean 还未创建,就直接返回 null,返回 null 之后,说明这个 Bean 还未创建,这个时候会标记这个 Bean 正在创建中,然后再调用 `createBean` 来创建 Bean,而实际创建是调用方法 `doCreateBean`。doCreateBean 这个方法就会执行上面我们说的三步骤:实例化、属性注入初始化。在实例化 Bean 之后,**会往 三级缓存(singletonFactories)塞入一个工厂,而调用这个工厂的 `getObject` 方法,就能得到这个 Bean**。 -image-20240911195840657.png +image-20240911195840657.png # IOC diff --git "a/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - \345\210\206\345\270\203\345\274\217.md" "b/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - \345\210\206\345\270\203\345\274\217.md" index cd64a92..c9ba6f2 100644 --- "a/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - \345\210\206\345\270\203\345\274\217.md" +++ "b/docs/\345\274\200\345\217\221/My Java Guide/My Java Guide - \345\210\206\345\270\203\345\274\217.md" @@ -159,7 +159,7 @@ Raft算法将节点分为三种状态:**跟随者(Follower)、候选人( 说到这就可以停下了,然后等面试官发问,正常情况下他会选一个点进行深入探讨,这时候我们只能见招拆招了。 -img +img ## 细节:动态代理 / RPC的实现原理 @@ -177,13 +177,13 @@ RPC 会给接口生成一个代理类,我们调用这个接口实际调用的 动态代理中,最常见的技术就是 Spring AOP 了,涉及的有 JDK 动态代理和 cglib。 -img +img ## 细节:序列化 **序列化原因:**网络传输的数据是“扁平”的,最终需要转化成“扁平”的二进制数据在网络中传输。 -**序列化方案:**有很多序列化选择,一般需要**综合考虑通用性、性能、可读性和兼容性**。 +**序列化方案:**有很多序列化选择,一般需要综合考虑通用性、性能、可读性和兼容性。 **序列化方案对比:** @@ -208,11 +208,11 @@ RPC 会给接口生成一个代理类,我们调用这个接口实际调用的 例如Dubbo 协议: -img +img ## 细节:网络传输 -img +img 一般而言用的都是 **IO 多路复用**,因为大部分 RPC 调用场景都是高并发调用,IO 复用可以利用较少的线程 hold 住很多请求。 @@ -715,14 +715,14 @@ public class SlidingWindowLimiter { 为了确保分布式事务的ACID,有以下常见的分布式事务解决方案: -**1. 两阶段提交(Two-Phase Commit, 2PC)** **【XA 协议、Atomikos、Bitronix】** +**1. 两阶段提交(Two-Phase Commit, 2PC)【XA 协议、Atomikos、Bitronix】** 最传统的分布式事务协议之一。包括准备阶段和提交阶段,其中协调者与参与者进行交互以决定是否提交或回滚事务。 1. **准备阶段**:协调者询问所有参与者是否准备好提交事务。 2. **提交阶段**:如果所有参与者都同意,则协调者命令所有参与者提交;如果任何一个参与者不同意,则协调者命令所有参与者回滚。 -**2. 三阶段提交(Three-Phase Commit, 3PC) ** **【SAGA、TCC(Try-Confirm-Cancel)、最终一致性】** +**2. 三阶段提交(Three-Phase Commit, 3PC)【SAGA、TCC(Try-Confirm-Cancel)、最终一致性】** 3PC是在2PC的基础上增加了预表决阶段,以减少阻塞情况的发生。 @@ -730,7 +730,7 @@ public class SlidingWindowLimiter { 2. **准备阶段**:参与者回复预表决结果。 3. **提交阶段**:根据参与者回复的结果,协调者发送提交或回滚指令。 -**3. 单边提交(One-Sided Commit) ** **【AP系统、DDD架构】** +**3. 单边提交(One-Sided Commit)【AP系统、DDD架构】** 在这种方案中,参与者独立决定是否提交事务,而不需要等待协调者的指示。