1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-08-01 16:28:03 +08:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Guide
b6cdf6dd3a [docs update]添加几个Redis相关的开源工具 2024-04-15 17:38:58 +08:00
Guide
467bbef465 [docs update]完善问题:CallerRunsPolicy 拒绝策略有什么风险?如何解决? 2024-04-14 22:37:35 +08:00
Guide
f89c38e62b
Merge pull request #2365 from shark-ctrl/shark-chili
[docs update] 线程池拒绝策略面试题
2024-04-14 18:10:52 +08:00
Guide
4c642c3399
Merge pull request #2364 from ddouddle/main
[docs update]部分描述完善
2024-04-14 17:16:45 +08:00
shark-chili
de9e1e8c50 [docs update] 线程池拒绝策略面试题 2024-04-14 13:37:35 +08:00
ddouddle
645d387e92
[docs update]部分描述完善 2024-04-13 22:14:25 +08:00
Guide
4327b601fb [docs update]typo 2024-04-11 21:27:43 +08:00
7 changed files with 165 additions and 7 deletions

View File

@ -5,7 +5,7 @@ tag:
- 个人经历
---
> 这篇文章写 2021 年高考前夕。
> 这篇文章写 2021 年高考前夕。
聊到高考,无数人都似乎有很多话说。今天就假借高考的名义,简单来聊聊我的高中求学经历吧!
@ -19,7 +19,11 @@ tag:
最开始接触电脑是在我刚上五年级的时候,那时候家里没电脑,刚开始上网都是在黑网吧玩的。
在黑网吧上网的经历也是一波三折,经常会遇到警察来检查或者碰到大孩子骚扰。在黑网吧上网的一年多中,我一共两次碰到警察来检查,主要是看有没有未成年人(当时黑网吧里几乎全是未成年人),实际感觉像是要问黑网吧老板要点好处。碰到大孩子骚扰的次数就比较多,大孩子经常抢我电脑,还威胁我把身上所有的钱给他们。我当时一个人也比较怂,被打了几次之后,就尽量避开大孩子来玩的时间去黑网吧,身上也只带很少的钱。小时候的性格就比较独立,在外遇到事情我一般也不会给家里人说。
黑网吧大概就是下面这样式儿的,一个没有窗户的房间里放了很多台老式电脑,非常拥挤。
![黑网吧](https://oss.javaguide.cn/about-the-author/internet-addiction-teenager/heiwangba.png)
在黑网吧上网的经历也是一波三折,经常会遇到警察来检查或者碰到大孩子骚扰。在黑网吧上网的一年多中,我一共两次碰到警察来检查,主要是看有没有未成年人(当时黑网吧里几乎全是未成年人),实际感觉像是要问黑网吧老板要点好处。碰到大孩子骚扰的次数就比较多,大孩子经常抢我电脑,还威胁我把身上所有的钱给他们。我当时一个人也比较怂,被打了几次之后,就尽量避开大孩子来玩的时间去黑网吧,身上也只带很少的钱。小时候的性格就比较独立,在外遇到事情我一般也不会给家里人说(因为说了也没什么用,家人给我的安全感很少)。
我现在已经记不太清当时是被我哥还是我姐带进网吧的,好像是我姐。
@ -81,6 +85,8 @@ QQ 飞车这款戏当时还挺火的,很多 90 后的小伙伴应该比较熟
## 高中从小班掉到平行班
![出高考成绩后回高中母校拍摄](https://oss.javaguide.cn/about-the-author/internet-addiction-teenager/wodegaozhong.png)
由于参加了高中提前招生考试,我提前 4 个月就来到了高中,进入了小班,开始学习高中的课程。
上了高中的之后,我上课就偷偷看小说,神印王座、斗罗大陆、斗破苍穹很多小说都是当时看的。中午和晚上回家之后,就在家里玩几把 DNF。当时家里也买了电脑姥爷给买的是对自己顺利进入二中的奖励。到我卸载 DNF 的时候,已经练了 4 个满级的号,两个接近满级的号。
@ -103,6 +109,8 @@ QQ 飞车这款戏当时还挺火的,很多 90 后的小伙伴应该比较熟
于是,我便开始牟足劲学习,每天都沉迷学习无法自拔(豪不夸张),乐在其中。虽然晚自习上完回到家已经差不多 11 点了,但也并不感觉累,反而感觉很快乐,很充实。
![和其他两位同学讨论数学问题](../../../../../Library/Application Support/typora-user-images/taolunwenti.png)
**我的付出也很快得到了回报,我顺利返回了奥赛班。** 当时,理科平行班大概有 7 个,每次考试都是平行班之间会单独排一个名次,小班和奥赛班不和我们一起排名次。后面的话,自己基本每次都能在平行班得第一,并且很多时候都是领先第二名 30 来分。由于成绩还算亮眼,高三上学期快结束的时候,我就向年级主任申请去了奥赛班。
## 高考前的失眠

View File

@ -196,7 +196,7 @@ public class Student {
- 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
- 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
- 多态不能调用“只在子类存在但在父类不存在”的方法;
- 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
- 如果子类重写了父类的方法,真正执行的是子类重写的方法,如果子类没有重写父类的方法,执行的是父类的方法。
### 接口和抽象类有什么共同点和区别?

View File

@ -320,7 +320,7 @@ Thread[线程 2,5,main]waiting get resource1
### 如何检测死锁?
- 使用`jmap``jstack`等命令查看 JVM 线程栈和堆内存的情况。如果有死锁,`jstack` 的输出中通常会有 `Found one Java-level deadlock:`的字样,后面会跟着死锁相关的线程信息。另外,实际项目中还可以搭配使用`top``df``free`等命令查看操作系统的基本情况出现死锁可能会导致CPU、内存等资源消耗过高。
- 使用`jmap``jstack`等命令查看 JVM 线程栈和堆内存的情况。如果有死锁,`jstack` 的输出中通常会有 `Found one Java-level deadlock:`的字样,后面会跟着死锁相关的线程信息。另外,实际项目中还可以搭配使用`top``df``free`等命令查看操作系统的基本情况,出现死锁可能会导致 CPU、内存等资源消耗过高。
- 采用 VisualVM、JConsole 等工具进行排查。
这里以 JConsole 工具为例进行演示。

View File

@ -388,9 +388,9 @@ synchronized(this) {
### 构造方法可以用 synchronized 修饰么?
先说结论:**构造方法不能使用 synchronized 关键字修饰。**
构造方法不能使用 synchronized 关键字修饰。不过,可以在构造方法内部使用 synchronized 代码块。
构造方法本身就属于线程安全的,不存在同步的构造方法一说
另外,构造方法本身是线程安全的,但如果在构造方法中涉及到共享资源的操作,就需要采取适当的同步措施来保证整个构造过程的线程安全
### synchronized 底层原理了解吗?

View File

@ -342,6 +342,152 @@ public static class CallerRunsPolicy implements RejectedExecutionHandler {
}
```
### 如果不允许丢弃任务任务,应该选择哪个拒绝策略?
根据上面对线程池拒绝策略的介绍,相信大家很容易能够得出答案是:`CallerRunsPolicy`
这里我们再来结合`CallerRunsPolicy` 的源码来看看:
```java
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
//只要当前程序没有关闭就用执行execute方法的线程执行该任务
if (!e.isShutdown()) {
r.run();
}
}
}
```
从源码可以看出,只要当前程序不关闭就会使用执行`execute`方法的线程执行该任务。
### CallerRunsPolicy 拒绝策略有什么风险?如何解决?
我们上面也提到了:如果想要保证任何一个任务请求都要被执行的话,那选择 `CallerRunsPolicy` 拒绝策略更合适一些。
不过,如果走到`CallerRunsPolicy`的任务是个非常耗时的任务,且处理提交任务的线程是主线程,可能会导致主线程阻塞,影响程序的正常运行。
这里简单举一个例子,该线程池限定了最大线程数为 2还阻塞队列大小为 1(这意味着第 4 个任务就会走到拒绝策略)`ThreadUtil`为 Hutool 提供的工具类:
```java
Logger log = LoggerFactory.getLogger(ThreadPoolTest.class);
// 创建一个线程池核心线程数为1最大线程数为2
// 当线程数大于核心线程数时多余的空闲线程存活的最长时间为60秒
// 任务队列为容量为1的ArrayBlockingQueue饱和策略为CallerRunsPolicy。
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
2,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
new ThreadPoolExecutor.CallerRunsPolicy());
// 提交第一个任务,由核心线程执行
threadPoolExecutor.execute(() -> {
log.info("核心线程执行第一个任务");
ThreadUtil.sleep(1, TimeUnit.MINUTES);
});
// 提交第二个任务,由于核心线程被占用,任务将进入队列等待
threadPoolExecutor.execute(() -> {
log.info("非核心线程处理入队的第二个任务");
ThreadUtil.sleep(1, TimeUnit.MINUTES);
});
// 提交第三个任务,由于核心线程被占用且队列已满,创建非核心线程处理
threadPoolExecutor.execute(() -> {
log.info("非核心线程处理第三个任务");
ThreadUtil.sleep(1, TimeUnit.MINUTES);
});
// 提交第四个任务由于核心线程和非核心线程都被占用队列也满了根据CallerRunsPolicy策略任务将由提交任务的线程即主线程来执行
threadPoolExecutor.execute(() -> {
log.info("主线程处理第四个任务");
ThreadUtil.sleep(2, TimeUnit.MINUTES);
});
// 提交第五个任务,主线程被第四个任务卡住,该任务必须等到主线程执行完才能提交
threadPoolExecutor.execute(() -> {
log.info("核心线程执行第五个任务");
});
```
输出:
```ba
18:19:48.203 INFO [pool-1-thread-1] c.j.concurrent.ThreadPoolTest - 核心线程执行第一个任务
18:19:48.203 INFO [pool-1-thread-2] c.j.concurrent.ThreadPoolTest - 非核心线程处理第三个任务
18:19:48.203 INFO [main] c.j.concurrent.ThreadPoolTest - 主线程处理第四个任务
18:20:48.212 INFO [pool-1-thread-2] c.j.concurrent.ThreadPoolTest - 非核心线程处理入队的第二个任务
18:21:48.219 INFO [pool-1-thread-2] c.j.concurrent.ThreadPoolTest - 核心线程执行第五个任务
```
从输出结果可以看出,因为`CallerRunsPolicy`这个拒绝策略,导致耗时的任务用了主线程执行,导致线程池阻塞,进而导致后续任务无法及时执行,严重的情况下很可能导致 OOM。
我们从问题的本质入手,调用者采用`CallerRunsPolicy`是希望所有的任务都能够被执行,暂时无法处理的任务又被保存在阻塞队列`BlockingQueue`中。这样的话,在内存允许的情况下,我们可以增加阻塞队列`BlockingQueue`的大小并调整堆内存以容纳更多的任务,确保任务能够被准确执行。
为了充分利用 CPU我们还可以调整线程池的`maximumPoolSize` (最大线程数)参数,这样可以提高任务处理速度,避免累计在 `BlockingQueue`的任务过多导致内存用完。
![调整阻塞队列大小和最大线程数](https://oss.javaguide.cn/github/javaguide/java/concurrent/threadpool-reject-2-threadpool-reject-01.png)
如果服务器资源以达到可利用的极限,这就意味我们要在设计策略上改变线程池的调度了,我们都知道,导致主线程卡死的本质就是因为我们不希望任何一个任务被丢弃。换个思路,有没有办法既能保证任务不被丢弃且在服务器有余力时及时处理呢?
这里提供的一种**任务持久化**的思路,这里所谓的任务持久化,包括但不限于:
1. 设计一张任务表间任务存储到 MySQL 数据库中。
2. `Redis`缓存任务。
3. 将任务提交到消息队列中。
这里以方案一为例,简单介绍一下实现逻辑:
1. 实现`RejectedExecutionHandler`接口自定义拒绝策略,自定义拒绝策略负责将线程池暂时无法处理(此时阻塞队列已满)的任务入库(保存到 MySQL 中)。注意:线程池暂时无法处理的任务会先被放在阻塞队列中,阻塞队列满了才会触发拒绝策略。
2. 继承`BlockingQueue`实现一个混合式阻塞队列,该队列包含`JDK`自带的`ArrayBlockingQueue`。另外,该混合式阻塞队列需要修改取任务处理的逻辑,也就是重写`take()`方法,取任务时优先从数据库中读取最早的任务,数据库中无任务时再从 `ArrayBlockingQueue`中去取任务。
![将一部分任务保存到MySQL中](https://oss.javaguide.cn/github/javaguide/java/concurrent/threadpool-reject-2-threadpool-reject-02.png)
整个实现逻辑还是比较简单的,核心在于自定义拒绝策略和阻塞队列。如此一来,一旦我们的线程池中线程以达到满载时,我们就可以通过拒绝策略将最新任务持久化到 MySQL 数据库中,等到线程池有了有余力处理所有任务时,让其优先处理数据库中的任务以避免"饥饿"问题。
当然,对于这个问题,我们也可以参考其他主流框架的做法,以 Netty 为例,它的拒绝策略则是直接创建一个线程池以外的线程处理这些任务,为了保证任务的实时处理,这种做法可能需要良好的硬件设备且临时创建的线程无法做到准确的监控:
```java
private static final class NewThreadRunsPolicy implements RejectedExecutionHandler {
NewThreadRunsPolicy() {
super();
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
//创建一个临时线程处理任务
final Thread t = new Thread(r, "Temporary task executor");
t.start();
} catch (Throwable e) {
throw new RejectedExecutionException(
"Failed to start a new thread", e);
}
}
}
```
ActiveMQ 则是尝试在指定的时效内尽可能的争取将任务入队,以保证最大交付:
```java
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) {
try {
//限时阻塞等待,实现尽可能交付
executor.getQueue().offer(r, 60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RejectedExecutionException("Interrupted waiting for BrokerService.worker");
}
throw new RejectedExecutionException("Timed Out while attempting to enqueue Task.");
}
});
```
### 线程池常用的阻塞队列有哪些?
新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

View File

@ -42,6 +42,8 @@ icon: tool
- [Another Redis Desktop Manager](https://github.com/qishibo/AnotherRedisDesktopManager/blob/master/README.zh-CN.md):更快、更好、更稳定的 Redis 桌面(GUI)管理客户端,兼容 Windows、Mac、Linux。
- [Tiny RDM](https://github.com/tiny-craft/tiny-rdm):一个更现代化的 Redis 桌面(GUI)管理客户端,基于 Webview2兼容 Windows、Mac、Linux。
- [Redis Manager](https://github.com/ngbdf/redis-manager)Redis 一站式管理平台支持集群cluster、master-replica、sentinel的监控、安装除 sentinel、管理、告警以及基本的数据操作功能。
- [CacheCloud](https://github.com/sohutv/cachecloud):一个 Redis 云管理平台,支持 Redis 多种架构(Standalone、Sentinel、Cluster)高效管理、有效降低大规模 Redis 运维成本,提升资源管控能力和利用率。
- [RedisShake](https://github.com/tair-opensource/RedisShake):一个用于处理和迁移 Redis 数据的工具。
## Docker

View File

@ -7,13 +7,15 @@ tag:
加密算法是一种用数学方法对数据进行变换的技术,目的是保护数据的安全,防止被未经授权的人读取或修改。加密算法可以分为三大类:对称加密算法、非对称加密算法和哈希算法(也叫摘要算法)。
日常开发中常见的需要用到加密算法的场景:
日常开发中常见的需要用到加密算法的场景:
1. 保存在数据库中的密码需要加盐之后使用哈希算法(比如 BCrypt进行加密。
2. 保存在数据库中的银行卡号、身份号这类敏感数据需要使用对称加密算法(比如 AES保存。
3. 网络传输的敏感数据比如银行卡号、身份号需要用 HTTPS + 非对称加密算法(如 RSA来保证传输数据的安全性。
4. ……
ps: 严格上来说,哈希算法其实不属于加密算法,只是可以用到某些加密场景中(例如密码加密),两者可以看作是并列关系。加密算法通常指的是可以将明文转换为密文,并且能够通过某种方式(如密钥)再将密文还原为明文的算法。而哈希算法是一种单向过程,它将输入信息转换成一个固定长度的、看似随机的哈希值,但这个过程是不可逆的,也就是说,不能从哈希值还原出原始信息。
## 哈希算法
哈希算法也叫散列函数或摘要算法,它的作用是对任意长度的数据生成一个固定长度的唯一标识,也叫哈希值、散列值或消息摘要(后文统称为哈希值)。