mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-07-28 12:22:17 +08:00
Compare commits
11 Commits
029713f39d
...
53ce8e9d14
Author | SHA1 | Date | |
---|---|---|---|
|
53ce8e9d14 | ||
|
650a4c8275 | ||
|
38d8effb65 | ||
|
a269e1ccbd | ||
|
ab46feebea | ||
|
751b2ba4d0 | ||
|
79c0653ada | ||
|
0ba4ff082f | ||
|
2ece98655d | ||
|
e8dd6107bd | ||
|
21ea70c0eb |
@ -367,7 +367,7 @@ MD5 可以用来生成一个 128 位的消息摘要,它是目前应用比较
|
||||
|
||||
**SM3**
|
||||
|
||||
国密算法**SM3**。加密强度和 SHA-256算法 相差不多。主要是受到了国家的支持。
|
||||
国密算法**SM3**。加密强度和 SHA-256 算法 相差不多。主要是受到了国家的支持。
|
||||
|
||||
**总结**:
|
||||
|
||||
|
@ -123,7 +123,7 @@ HyperLogLog 相关的命令非常少,最常用的也就 3 个。
|
||||
|
||||
### 应用场景
|
||||
|
||||
**数量量巨大(百万、千万级别以上)的计数场景**
|
||||
**数量巨大(百万、千万级别以上)的计数场景**
|
||||
|
||||
- 举例:热门网站每日/每周/每月访问 ip 数统计、热门帖子 uv 统计、
|
||||
- 相关命令:`PFADD`、`PFCOUNT` 。
|
||||
|
@ -729,7 +729,7 @@ Redis 的定期删除过程是随机的(周期性地随机从设置了过期
|
||||
|
||||
Redis 7.2 版本的执行时间阈值是 **25ms**,过期 key 比例设定值是 **10%**。
|
||||
|
||||
```java
|
||||
```c
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* Max % of CPU to use. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE 10 /* % of stale keys after which
|
||||
|
@ -99,7 +99,7 @@ public LinkedList(Collection<? extends E> c) {
|
||||
`add()` 方法有两个版本:
|
||||
|
||||
- `add(E e)`:用于在 `LinkedList` 的尾部插入元素,即将新元素作为链表的最后一个元素,时间复杂度为 O(1)。
|
||||
- `add(int index, E element)`:用于在指定位置插入元素。这种插入方式需要先移动到指定位置,再修改指定节点的指针完成插入/删除,因此需要移动平均 n/2 个元素,时间复杂度为 O(n)。
|
||||
- `add(int index, E element)`:用于在指定位置插入元素。这种插入方式需要先移动到指定位置,再修改指定节点的指针完成插入/删除,因此需要移动平均 n/4 个元素,时间复杂度为 O(n)。
|
||||
|
||||
```java
|
||||
// 在链表尾部插入元素
|
||||
|
@ -559,7 +559,7 @@ public class SynchronizedDemo2 {
|
||||
|
||||
`synchronized` 修饰的方法并没有 `monitorenter` 指令和 `monitorexit` 指令,取而代之的是 `ACC_SYNCHRONIZED` 标识,该标识指明了该方法是一个同步方法。
|
||||
|
||||
**不过两者的本质都是对对象监视器 monitor 的获取。**
|
||||
**不过,两者的本质都是对对象监视器 monitor 的获取。**
|
||||
|
||||
相关推荐:[Java 锁与线程的那些事 - 有赞技术团队](https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/) 。
|
||||
|
||||
@ -573,6 +573,30 @@ public class SynchronizedDemo2 {
|
||||
|
||||
`synchronized` 锁升级是一个比较复杂的过程,面试也很少问到,如果你想要详细了解的话,可以看看这篇文章:[浅析 synchronized 锁升级的原理与实现](https://www.cnblogs.com/star95/p/17542850.html)。
|
||||
|
||||
### synchronized 的偏向锁为什么被废弃了?
|
||||
|
||||
Open JDK 官方声明:[JEP 374: Deprecate and Disable Biased Locking](https://openjdk.org/jeps/374)
|
||||
|
||||
在 JDK15 中,偏向锁被默认关闭(仍然可以使用 `-XX:+UseBiasedLocking` 启用偏向锁),在 JDK18 中,偏向锁已经被彻底废弃(无法通过命令行打开)。
|
||||
|
||||
在官方声明中,主要原因有两个方面:
|
||||
|
||||
- **性能收益不明显:**
|
||||
|
||||
偏向锁是 HotSpot 虚拟机的一项优化技术,可以提升单线程对同步代码块的访问性能。
|
||||
|
||||
受益于偏向锁的应用程序通常使用了早期的 Java 集合 API,例如 HashTable、Vector,在这些集合类中通过 synchronized 来控制同步,这样在单线程频繁访问时,通过偏向锁会减少同步开销。
|
||||
|
||||
随着 JDK 的发展,出现了 ConcurrentHashMap 高性能的集合类,在集合类内部进行了许多性能优化,此时偏向锁带来的性能收益就不明显了。
|
||||
|
||||
偏向锁仅仅在单线程访问同步代码块的场景中可以获得性能收益。
|
||||
|
||||
如果存在多线程竞争,就需要 **撤销偏向锁** ,这个操作的性能开销是比较昂贵的。偏向锁的撤销需要等待进入到全局安全点(safe point),该状态下所有线程都是暂停的,此时去检查线程状态并进行偏向锁的撤销。
|
||||
|
||||
- **JVM 内部代码维护成本太高:**
|
||||
|
||||
偏向锁将许多复杂代码引入到同步子系统,并且对其他的 HotSpot 组件也具有侵入性。这种复杂性为理解代码、系统重构带来了困难,因此, OpenJDK 官方希望禁用、废弃并删除偏向锁。
|
||||
|
||||
### ⭐️synchronized 和 volatile 有什么区别?
|
||||
|
||||
`synchronized` 关键字和 `volatile` 关键字是两个互补的存在,而不是对立的存在!
|
||||
@ -659,9 +683,9 @@ public class SynchronizedDemo {
|
||||
|
||||
关于 **等待可中断** 的补充:
|
||||
|
||||
> `lockInterruptibly()` 会让获取锁的线程在阻塞等待的过程中可以响应中断,即当前线程在获取锁的时候,发现锁被其他线程持有,就会阻塞等待
|
||||
> `lockInterruptibly()` 会让获取锁的线程在阻塞等待的过程中可以响应中断,即当前线程在获取锁的时候,发现锁被其他线程持有,就会阻塞等待。
|
||||
>
|
||||
> 在阻塞等待的过程中,如果其他线程中断当前线程 「 `interrupt()` 」,就会抛出 `InterruptedException` 异常,可以捕获该异常,做一些处理操作
|
||||
> 在阻塞等待的过程中,如果其他线程中断当前线程 `interrupt()` ,就会抛出 `InterruptedException` 异常,可以捕获该异常,做一些处理操作。
|
||||
>
|
||||
> 为了更好理解这个方法,借用 Stack Overflow 上的一个案例,可以更好地理解 `lockInterruptibly()` 可以响应中断:
|
||||
>
|
||||
@ -730,17 +754,11 @@ public class SynchronizedDemo {
|
||||
|
||||
> **为什么需要 `tryLock(timeout)` 这个功能呢?**
|
||||
>
|
||||
> 假设这样一种场景:有一个加载缓存数据的任务在某个时间点多个线程同时要来执行,为了并发安全,通过锁来控制只有一个线程可以执行该任务。
|
||||
> `tryLock(timeout)` 方法尝试在指定的超时时间内获取锁。如果成功获取锁,则返回 `true`;如果在锁可用之前超时,则返回 `false`。此功能在以下几种场景中非常有用:
|
||||
>
|
||||
> 假设大量线程同时来执行该任务,由于需要穿行执行,因此大量线程都进入阻塞队列等待获取锁
|
||||
>
|
||||
> 当第一个线程拿到锁,执行完任务之后,此时后边的线程都不需要执行该任务了,但是由于没有这个超时功能,导致后边的线程还需要在队列中阻塞等待获取锁,再一个个进入同步代码块,发现任务已经执行过了,不需要自己再执行了,之后再退出释放锁,退出同步代码块。
|
||||
>
|
||||
> 因此就需要一个支持超时的功能,`tryLock(timeout)` 的作用就是 **将大量线程的串行操作转为并行操作** ,当大量线程等待时间已经超过了指定的超时时间,直接返回 false,表示获取锁失败,不需要大量的线程串行排队等待获取锁。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 这里 `tryLock(timeout)` 的情况只是举一个特殊的情况,其实是参考了分布式环境下,更新 Redis 缓存时会出现这种情况,但是在分布式环境下肯定不会使用 synchronized ,因此这里主要是举个例子说一下 tryLock(timeout) 的作用!
|
||||
> - **防止死锁:** 在复杂的锁场景中,`tryLock(timeout)` 可以通过允许线程在合理的时间内放弃并重试来帮助防止死锁。
|
||||
> - **提高响应速度:** 防止线程无限期阻塞。
|
||||
> - **处理时间敏感的操作:** 对于具有严格时间限制的操作,`tryLock(timeout)` 允许线程在无法及时获取锁时继续执行替代操作。
|
||||
|
||||
### 可中断锁和不可中断锁有什么区别?
|
||||
|
||||
|
@ -106,9 +106,14 @@ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
|
||||
|
||||
### ⭐️ThreadLocal 内存泄露问题是怎么导致的?
|
||||
|
||||
`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。
|
||||
`ThreadLocal` 内存泄漏的根本原因在于其内部实现机制。
|
||||
|
||||
这样一来,`ThreadLocalMap` 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。`ThreadLocalMap` 实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后最好手动调用`remove()`方法
|
||||
通过上面的内容我们已经知道:每个线程维护一个名为 `ThreadLocalMap` 的 map。 当你使用 `ThreadLocal` 存储值时,实际上是将值存储在当前线程的 `ThreadLocalMap` 中,其中 `ThreadLocal` 实例本身作为 key,而你要存储的值作为 value。
|
||||
|
||||
`ThreadLocalMap` 的 `key` 和 `value` 引用机制:
|
||||
|
||||
- **key 是弱引用**:`ThreadLocalMap` 中的 key 是 `ThreadLocal` 的弱引用 (`WeakReference<ThreadLocal<?>>`)。 这意味着,如果 `ThreadLocal` 实例不再被任何强引用指向,垃圾回收器会在下次 GC 时回收该实例,导致 `ThreadLocalMap` 中对应的 key 变为 `null`。
|
||||
- **value 是强引用**:`ThreadLocalMap` 中的 value 是强引用。 即使 key 被回收(变为 `null`),value 仍然存在于 `ThreadLocalMap` 中,被强引用,不会被回收。
|
||||
|
||||
```java
|
||||
static class Entry extends WeakReference<ThreadLocal<?>> {
|
||||
@ -122,11 +127,19 @@ static class Entry extends WeakReference<ThreadLocal<?>> {
|
||||
}
|
||||
```
|
||||
|
||||
**弱引用介绍:**
|
||||
当 `ThreadLocal` 实例失去强引用后,其对应的 value 仍然存在于 `ThreadLocalMap` 中,因为 `Entry` 对象强引用了它。如果线程持续存活(例如线程池中的线程),`ThreadLocalMap` 也会一直存在,导致 key 为 `null` 的 entry 无法被垃圾回收,机会造成内存泄漏。
|
||||
|
||||
> 如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
|
||||
>
|
||||
> 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
|
||||
也就是说,内存泄漏的发生需要同时满足两个条件:
|
||||
|
||||
1. `ThreadLocal` 实例不再被强引用;
|
||||
2. 线程持续存活,导致 `ThreadLocalMap` 长期存在。
|
||||
|
||||
虽然 `ThreadLocalMap` 在 `get()`, `set()` 和 `remove()` 操作时会尝试清理 key 为 null 的 entry,但这种清理机制是被动的,并不完全可靠。
|
||||
|
||||
**如何避免内存泄漏的发生?**
|
||||
|
||||
1. 在使用完 `ThreadLocal` 后,务必调用 `remove()` 方法。 这是最安全和最推荐的做法。 `remove()` 方法会从 `ThreadLocalMap` 中显式地移除对应的 entry,彻底解决内存泄漏的风险。 即使将 `ThreadLocal` 定义为 `static final`,也强烈建议在每次使用后调用 `remove()`。
|
||||
2. 在线程池等线程复用的场景下,使用 `try-finally` 块可以确保即使发生异常,`remove()` 方法也一定会被执行。
|
||||
|
||||
## 线程池
|
||||
|
||||
@ -286,7 +299,7 @@ public void allowCoreThreadTimeOut(boolean value) {
|
||||
如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,`ThreadPoolExecutor` 定义一些策略:
|
||||
|
||||
- `ThreadPoolExecutor.AbortPolicy`:抛出 `RejectedExecutionException`来拒绝新任务的处理。
|
||||
- `ThreadPoolExecutor.CallerRunsPolicy`:调用执行自己的线程运行任务,也就是直接在调用`execute`方法的线程中运行(`run`)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果你的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。
|
||||
- `ThreadPoolExecutor.CallerRunsPolicy`:调用执行者自己的线程运行任务,也就是直接在调用`execute`方法的线程中运行(`run`)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果你的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。
|
||||
- `ThreadPoolExecutor.DiscardPolicy`:不处理新任务,直接丢弃掉。
|
||||
- `ThreadPoolExecutor.DiscardOldestPolicy`:此策略将丢弃最早的未处理的任务请求。
|
||||
|
||||
|
@ -17,7 +17,7 @@ JWT 不是银弹,也有很多缺陷,很多时候并不是最优的选择。
|
||||
|
||||
### 无状态
|
||||
|
||||
JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 Session 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。
|
||||
JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 JWT 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。
|
||||
|
||||
不过,也正是由于 JWT 的无状态,也导致了它最大的缺点:**不可控!**
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user