Skip to content

Commit 34346b9

Browse files
committed
design-pattern
1 parent 3624e34 commit 34346b9

10 files changed

Lines changed: 227 additions & 117 deletions

File tree

docs/.vuepress/config.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ module.exports = {
3535
nav: [
3636
{ text: 'Java', link: '/java/' },
3737
{ text: '数据结构与算法', link: '/data-structure-algorithms/' },
38+
{ text: '设计模式', link: '/design-pattern/' },
3839
{ text: '数据存储与缓存', link: '/data-store/' },
3940
{ text: '直击面试', link: '/interview/' },
4041
],
4142
sidebar: {
4243
"/java/": genJavaSidebar(),
4344
"/data-structure-algorithms/": genDSASidebar(),
45+
"/design-pattern/": genDesignPatternSidebar(),
4446
//"/data-store/": genDataStoreSidebar(),
4547
"/interview/": genInterviewSidebar(),
4648
},
@@ -99,9 +101,11 @@ function genJavaSidebar() {
99101
['JUC/Concurrent-Container','Collection 大局观'],
100102
"JUC/AQS",
101103
'JUC/Reentrantlock',
104+
"JUC/ThreadLocal",
102105
"JUC/CountDownLatch、CyclicBarrier、Semaphore",
103106
['JUC/BlockingQueue','阻塞队列'],
104-
"JUC/Thread-Pool"
107+
"JUC/Thread-Pool",
108+
"JUC/Locks",
105109
]
106110
}
107111
];
@@ -126,6 +130,21 @@ function genDSASidebar() {
126130
];
127131
}
128132

133+
function genDesignPatternSidebar() {
134+
return [
135+
['Design-Pattern-Overview', '设计模式前传'],
136+
['Singleton-Pattern', '单例模式'],
137+
['Factory-Pattern', '工厂模式'],
138+
['Prototype-Pattern', '原型模式'],
139+
['Decorator-Pattern', '装饰模式'],
140+
['Proxy-Pattern', '代理模式'],
141+
['Adapter-Pattern', '适配器模式'],
142+
['Chain-of-Responsibility-Pattern', '责任链模式'],
143+
['Observer-Pattern', '观察者模式'],
144+
['Facade-Pattern', '外观模式']
145+
];
146+
}
147+
129148
function genDataStoreSidebar(){
130149
return [
131150
{

docs/design-pattern/Facade-Pattern.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# 外观模式
2+
13
之前介绍过装饰者模式和适配器模式,我们知道适配器模式是如何将一个类的接口转换成另一个符合客户期望的接口的。但 Java 中要实现这一点,必须将一个不兼容接口的对象包装起来,变成兼容的对象。
24

35
- 装饰模式:不改变接口,但加入责任
@@ -6,8 +8,6 @@
68

79

810

9-
# 外观模式
10-
1111
## 问题
1212

1313
假设你必须在代码中使用某个复杂的库或框架中的众多对象。 正常情况下,你需要负责所有对象的初始化工作、 管理其依赖关系并按正确的顺序执行方法等。

docs/design-pattern/Observer-Pattern.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
> 文章收录在 GitHub [JavaKeeper](https://github.com/Jstarfish/JavaKeeper) ,N线互联网开发必备技能兵器谱
1+
# 观察者模式
22

33
在软件系统中经常会有这样的需求:如果一个对象的状态发生改变,某些与它相关的对象也要随之做出相应的变化。
44

File renamed without changes.

docs/design-pattern/Singleton-Pattern.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
# 单例模式——独一无二的对象
2+
13
> 面试官:带笔了吧,那写两种单例模式的实现方法吧
24
>
35
> 沙沙沙刷刷刷~~~ 写好了
46
>
57
> 面试官:你这个是怎么保证线程安全的,那你知道,volatile 关键字? 类加载器?锁机制????
68
> 点赞+收藏 就学会系列,文章收录在 GitHub [JavaEgg](https://github.com/Jstarfish/JavaEgg) ,N线互联网开发必备技能兵器谱
79
8-
## 单例模式——独一无二的对象
10+
911

1012
![](https://i01piccdn.sogoucdn.com/d4a728c10d74ab67)
1113

docs/interview/JUC-FAQ.md

Lines changed: 95 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -870,9 +870,6 @@ public interface Callable<V> {
870870
首先创建一个 `Runnable` 接口的实现类(当然也可以是 `Callable` 接口,我们上面也说了两者的区别。)
871871

872872
```
873-
MyRunnable.java
874-
import java.util.Date;
875-
876873
/**
877874
* 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。
878875
* @author shuang.kou
@@ -910,11 +907,6 @@ public class MyRunnable implements Runnable {
910907
编写测试程序,我们这里以阿里巴巴推荐的使用 `ThreadPoolExecutor` 构造函数自定义参数的方式来创建线程池。
911908

912909
```
913-
ThreadPoolExecutorDemo.java
914-
import java.util.concurrent.ArrayBlockingQueue;
915-
import java.util.concurrent.ThreadPoolExecutor;
916-
import java.util.concurrent.TimeUnit;
917-
918910
public class ThreadPoolExecutorDemo {
919911

920912
private static final int CORE_POOL_SIZE = 5;
@@ -984,7 +976,7 @@ pool-1-thread-1 End. Time = Tue Nov 12 20:59:54 CST 2019
984976

985977
### 线程池原理分析
986978

987-
承接 4.6,我们通过代码输出结果可以看出:**线程池每次会同时执行 5 个任务,这 5 个任务执行完之后,剩余的 5 个任务才会被执行。** 大家可以先通过上面讲解的内容,分析一下到底是咋回事?(自己独立思考一会)
979+
承接上节,我们通过代码输出结果可以看出:**线程池每次会同时执行 5 个任务,这 5 个任务执行完之后,剩余的 5 个任务才会被执行。** 大家可以先通过上面讲解的内容,分析一下到底是咋回事?(自己独立思考一会)
988980

989981
现在,我们就分析上面的输出内容来简单分析一下线程池原理。
990982

@@ -1037,41 +1029,114 @@ pool-1-thread-1 End. Time = Tue Nov 12 20:59:54 CST 2019
10371029

10381030
[![图解线程池实现原理](https://camo.githubusercontent.com/cf627f637b4c678cd77b815fbea8789dd3158b0c/68747470733a2f2f6d792d626c6f672d746f2d7573652e6f73732d636e2d6265696a696e672e616c6979756e63732e636f6d2f323031392d372f2545352539422542452545382541372541332545372542412542462545372541382538422545362542312541302545352541452539452545372538452542302545352538452539462545372539302538362e706e67)](https://camo.githubusercontent.com/cf627f637b4c678cd77b815fbea8789dd3158b0c/68747470733a2f2f6d792d626c6f672d746f2d7573652e6f73732d636e2d6265696a696e672e616c6979756e63732e636f6d2f323031392d372f2545352539422542452545382541372541332545372542412542462545372541382538422545362542312541302545352541452539452545372538452542302545352538452539462545372539302538362e706e67)
10391031

1040-
现在,让我们在回到 4.6 节我们写的 Demo, 现在应该是不是很容易就可以搞懂它的原理了呢?
1032+
现在,让我们在回到我们写的 Demo, 现在应该是不是很容易就可以搞懂它的原理了呢?
10411033

10421034
没搞懂的话,也没关系,可以看看我的分析:
10431035

10441036
> 我们在代码中模拟了 10 个任务,我们配置的核心线程数为 5 、等待队列容量为 100 ,所以每次只可能存在 5 个任务同时执行,剩下的 5 个任务会被放到等待队列中去。当前的 5 个任务之行完成后,才会之行剩下的 5 个任务。
10451037

1046-
####
10471038

10481039

1040+
### 当提交新任务时,异常如何处理?
1041+
1042+
1. 在任务代码try/catch捕获异常
1043+
1044+
2. 通过Future对象的get方法接收抛出的异常,再处理
1045+
1046+
3. 为工作者线程设置UncaughtExceptionHandler,在uncaughtException方法中处理异常
1047+
1048+
```java
1049+
ExecutorService threadPool = Executors.newFixedThreadPool(1, r -> {
1050+
Thread t = new Thread(r);
1051+
t.setUncaughtExceptionHandler(
1052+
(t1, e) -> {
1053+
System.out.println(t1.getName() + "线程抛出的异常"+e);
1054+
});
1055+
return t;
1056+
});
1057+
threadPool.execute(()->{
1058+
Object object = null;
1059+
System.out.print("result## " + object.toString());
1060+
});
1061+
```
1062+
1063+
4. 重写ThreadPoolExecutor的afterExecute方法,处理传递的异常引用
1064+
1065+
```java
1066+
class ExtendedExecutor extends ThreadPoolExecutor {
1067+
// 这可是jdk文档里面给的例子。。
1068+
protected void afterExecute(Runnable r, Throwable t) {
1069+
super.afterExecute(r, t);
1070+
if (t == null && r instanceof Future<?>) {
1071+
try {
1072+
Object result = ((Future<?>) r).get();
1073+
} catch (CancellationException ce) {
1074+
t = ce;
1075+
} catch (ExecutionException ee) {
1076+
t = ee.getCause();
1077+
} catch (InterruptedException ie) {
1078+
Thread.currentThread().interrupt(); // ignore/reset
1079+
}
1080+
}
1081+
if (t != null)
1082+
System.out.println(t);
1083+
}
1084+
}}
1085+
1086+
```
1087+
1088+
1089+
1090+
### 线程池都有哪几种工作队列?
1091+
1092+
- ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列
1093+
- LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列
1094+
- PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列
1095+
- DelayQueue:一个使用优先级队列实现的无界阻塞队列
1096+
- SynchronousQueue:一个不存储元素的阻塞队列
1097+
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列(实现了继承于 BlockingQueueTransferQueue
1098+
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
1099+
1100+
1101+
1102+
### 合理配置线程池你是如何考虑的?
1103+
1104+
首先要考虑到 CPU 核心数,那么在 Java 中如何获取核心线程数?
1105+
1106+
可以使用 `Runtime.getRuntime().availableProcessor()` 方法来获取(可能不准确,作为参考)
1107+
1108+
在确认了核心数后,再去判断是 CPU 密集型任务还是 IO 密集型任务:
1109+
1110+
- **CPU 密集型任务**CPU密集型也叫计算密集型,这种类型大部分状况下,CPU使用时间远高于I/O耗时。有许多计算要处理、许多逻辑判断,几乎没有I/O操作的任务就属于 CPU 密集型。
1111+
1112+
CPU 密集任务只有在真正的多核 CPU 上才可能得到加速(通过多线程)
1113+
1114+
而在单核 CPU 上,无论开几个模拟的多线程该任务都不可能得到加速,因为 CPU 总的运算能力就那些。
1115+
1116+
如果是 CPU 密集型任务,频繁切换上下线程是不明智的,此时应该设置一个较小的线程数
1117+
1118+
一般公式:**CPU 核数 + 1 个线程的线程池**
1119+
1120+
为什么 +1 呢?
1121+
1122+
Java并发编程实战》一书中给出的原因是:**即使当计算(CPU)密集型的线程偶尔由于页缺失故障或者其他原因而暂停时,这个“额外”的线程也能确保 CPU 的时钟周期不会被浪费。**
1123+
1124+
- **IO 密集型任务**:与之相反,IO 密集型则是系统运行时,大部分时间都在进行 I/O 操作,CPU 占用率不高。比如像 MySQL 数据库、文件的读写、网络通信等任务,这类任务**不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间**
1125+
1126+
在单线程上运行 IO 密集型的任务会导致浪费大量的 CPU 运算能力浪费在等待。
1127+
1128+
所以在 IO 密集型任务中使用多线程可以大大的加速程序运行,即使在单核 CPU 上,这种加速主要就是利用了被浪费调的阻塞时间。所以在 IO 密集型任务中使用多线程可以大大的加速程序运行,即使在单核 CPU 上,这种加速主要就是利用了被浪费掉的阻塞时间。
10491129

1050-
- ConcurrentHashMapHashMap
1051-
-
1052-
- 线程池原理,拒绝策略,核心线程数
1053-
- 线程之间的交互方式有哪些?有没有线程交互的封装类 (join)?
1054-
- 死锁怎么避免?
1055-
- concurrentHashMap分段锁的细节
1056-
- 并发包里了解哪些
1057-
- synchronizedMap知道吗,和concurrentHashMap分别用于什么场景
1058-
- 描述一下java线程池
1059-
- 常用的队列,阻塞队列
1060-
- 如何获取多线程调用结果
1061-
-
1062-
- synchronized内部实现,偏向锁,轻量锁,重量锁
1130+
IO 密集型时,大部分线程都阻塞,故需要多配置线程数:
10631131

1064-
- 为什么需要自旋?
1132+
参考公式: CPU 核数/1- 阻塞系数) 阻塞系数在 0.8~0.9 之间
10651133

1066-
- sleep( ) 和 wait( n)、wait( ) 的区别:
1134+
比如 8CPU8/1 -0.9= 80个线程数
10671135

1068-
**sleep 方法:**Thread 类的静态方法,当前线程将睡眠 n 毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进行可运行状态,等待 CPU 的到来。睡眠不释放锁(如果有的话);
10691136

1070-
**wait 方法:**Object 的方法,必须与 synchronized 关键字一起使用,线程进入阻塞状态,当 notify 或者 notifyall 被调用后,会解除阻塞。但是,只有重新占用互斥锁之后才会进入可运行状态。睡眠时,释放互斥锁。
1137+
这个其实没有一个特别适用的公式,肯定适合自己的业务,美团给出了个**动态更新**的逻辑,可以看看
10711138

1072-
synchronizedLock的区别
10731139

1074-
sleep方法和yield方法的区别
10751140

10761141

10771142

docs/java/JUC/AQS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ AQS 是 `AbstractQueuedSynchronizer` 的简称,翻译成中文就是 `抽象
4242

4343
为什么要聚合一个同步器的子类呢,这其实就是一个典型的模板方法模式的优点:
4444

45-
- 我们使用的锁事面向使用者的,它定义了使用者与锁交互的接口,隐藏了实现的细节,我们就像范式那样直接使用就可以了,很简单
45+
- 我们使用的锁是面向使用者的,它定义了使用者与锁交互的接口,隐藏了实现的细节,我们就像范式那样直接使用就可以了,很简单
4646
- 而同步器面向的是锁的实现,比如 Doug Lea 大神,或者我们业务自定义的同步器,它简化了锁的实现方式,屏蔽了同步状态管理,线程排队,等待/唤醒等底层操作
4747

4848
这可以让我们使用起来更加方便,因为我们绝大多数都是在使用锁,实现锁之后,其核心就是要使用方便。
File renamed without changes.

0 commit comments

Comments
 (0)