Skip to content

Commit e9ff7c5

Browse files
committed
update
1 parent 07917cf commit e9ff7c5

3 files changed

Lines changed: 92 additions & 27 deletions

File tree

JavaKnowledge/MVC与MVP及MVVM.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ MVVM
6464

6565
MVVM是Model-View-ViewModel的简写。
6666

67+
![](https://raw.githubusercontent.com/CharonChui/Pictures/master/MVVM.png)
68+
69+
MVVM模式将Presener改名为View Model,基本上与MVP模式完全一致,同样是以VM为核心,但是不同于MVP,MVVM采用了数据双向绑定的方案,替代了繁琐复杂的DOM操作。该模型中,View与VM保持同步,View绑定到VM的属性上,如果VM数据发生变化,通过数据绑定的方式,View会自动更新视图;VM同样也暴露出Model中的数据。
70+
71+
看起来MVVM很好的解决了MVC和MVP的不足,但是由于数据和视图的双向绑定,导致出现问题时不太好定位来源,有可能数据问题导致,也有可能业务逻辑中对视图属性的修改导致。如果项目中打算用MVVM的话可以考虑使用官方的架构组件ViewModel、LiveData、DataBinding去实现MVVM
72+
73+
74+
6775
- 优点
6876
`MVVM`模式和`MVC`模式一样,主要目的是分离视图`View`和模型`Model`
6977
- 低耦合。

JavaKnowledge/算法的复杂度.md

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,16 @@
4141

4242
什么是时间复杂度,算法中某个函数有n次基本操作重复执行,用T(n)表示,现在有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。通俗一点讲,其实所谓的时间复杂度,就是找了一个同样曲线类型的函数f(n)来表示这个算法的在n不断变大时的趋势 。当输入量n逐渐加大时,时间复杂性的极限情形称为算法的“渐近时间复杂性”。
4343

44-
- 时间频度
44+
- 时间频度
4545

4646
一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为`T(n)`。(算法中的基本操作一般指算法中最深层循环内的语句)
4747

48-
- 时间复杂度
48+
- 时间复杂度
4949

5050
在刚才提到的时间频度中,`n`称为问题的规模,当`n`不断变化时,时间频度`T(n)`也会不断变化。但有时我们想知道它变化时呈现什么规律。为此,我们引入时间复杂度的概念。
5151

5252

53+
5354
在进行算法分析时,语句总的执行次数`T(n)`是关于问题规模`n`的函数,进而分析`T(n)``n`的变化情况并确定`T(n)`的数量级。
5455
算法的时间复杂度,也就是算法的时间量度,记作`T(n) = O(f(n))`。它表示随问题规模`n`的增大,算法执行时间的增长率和`f(n)`的增长率相同,
5556
称为算法的渐近时间复杂度,简称为时间复杂度。其中`f(n)`是规模`n`的某个函数。
@@ -139,26 +140,24 @@ O(2的`n`次方) 比如求具有`n`个元素集合的所有子集的算法
139140
空间复杂度
140141
---
141142

142-
算法的空间复杂度并不是计算实际占用的空间,而是计算整个算法的辅助空间单元的个数,与问题的规模没有关系。算法的空间复杂度`S(n)`定义为该算法所耗费空间的数量级。
143-
`S(n)=O(f(n))`若算法执行时所需要的辅助空间相对于输入数据量`n`而言是一个常数,则称这个算法的辅助空间为`O(1)`;
144-
递归算法的空间复杂度:递归深度`N*`每次递归所要的辅助空间, 如果每次递归所需的辅助空间是常数,则递归的空间复杂度是`O(N)`.
143+
算法的空间复杂度并不是计算实际占用的空间,而是计算整个算法的辅助空间单元的个数,与问题的规模没有关系。算法的空间复杂度`S(n)`定义为该算法所耗费空间的数量级。
144+
`S(n)=O(f(n))`若算法执行时所需要的辅助空间相对于输入数据量`n`而言是一个常数,则称这个算法的辅助空间为`O(1)`;
145+
递归算法的空间复杂度:递归深度`N*`每次递归所要的辅助空间, 如果每次递归所需的辅助空间是常数,则递归的空间复杂度是`O(N)`.
145146

146-
空间复杂度的分析方法:
147+
空间复杂度的分析方法:
147148

148149
- 一个算法的空间复杂度`S(n)`定义为该算法所耗费的存储空间,它也是问题规模`n`的函数。空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。
149150
- 一个算法在计算机存储器上所占用的存储空间,包括存储算法本身所占用的存储空间,算法的输入输出数据所占用的存储空间和算法在运行过程中临时占用的存储空间这三个方面。
150151
- 一个算法的空间复杂度只考虑在运行过程中为局部变量分配的存储空间的大小,它包括为参数表中形参变量分配的存储空间和为在函数体中定义的局部变量分配的存储空间两个部分。
151152

152-
  算法的空间复杂度一般也以数量级的形式给出。如当一个算法的空间复杂度为一个常量,即不随被处理数据量`n`的大小而改变时,可表示为`O(1)`
153-
  当一个算法的空间复杂度与以2为底的`n`的对数成正比时,可表示为`O(log2n)`
154-
  当一个算法的空间复杂度与`n`成线性比例关系时,可表示为`O(n)`。若形参为数组,则只需要为它分配一个存储由实参传送来的一个地址指针的空间,
155-
  即一个机器字长空间;若形参为引用方式,则也只需要为其分配存储一个地址的空间,用它来存储对应实参变量的地址,
156-
  以便由系统自动引用实参变量。
153+
  算法的空间复杂度一般也以数量级的形式给出。如当一个算法的空间复杂度为一个常量,即不随被处理数据量`n`的大小而改变时,可表示为`O(1)`
154+
  当一个算法的空间复杂度与以2为底的`n`的对数成正比时,可表示为`O(log2n)`
155+
  当一个算法的空间复杂度与`n`成线性比例关系时,可表示为`O(n)`。若形参为数组,则只需要为它分配一个存储由实参传送来的一个地址指针的空间,即一个机器字长空间;若形参为引用方式,则也只需要为其分配存储一个地址的空间,用它来存储对应实参变量的地址,以便由系统自动引用实参变量。
157156

158-
空间复杂度补充:
157+
空间复杂度补充:
159158

160-
一个程序的空间复杂度是指运行完一个程序所需内存的大小。利用程序的空间复杂度,可以对程序的运行所需要的内存多少有个预先估计。
161-
一个程序执行时除了需要存储空间和存储本身所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些为现实计算所需信息的辅助空间。
159+
一个程序的空间复杂度是指运行完一个程序所需内存的大小。利用程序的空间复杂度,可以对程序的运行所需要的内存多少有个预先估计。
160+
一个程序执行时除了需要存储空间和存储本身所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些为现实计算所需信息的辅助空间。
162161
程序执行时所需存储空间包括以下两部分:   
163162

164163
- 固定部分。这部分空间的大小与输入/输出的数据的个数多少、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间。

JavaKnowledge/线程池简介.md

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@
1919
- 任务接口(`Task`):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等
2020
- 任务队列(`TaskQueue`):用于存放没有处理的任务。提供一种缓冲机制。
2121

22-
2322
- 使用线程池的好处:
24-
- 减少在创建和销毁线程上所花的时间以及系统资源的开销
25-
- 如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。
26-
- 提交相应速度
23+
- 降低资源消耗。通过重复利用减少在创建和销毁线程上所花的时间以及系统资源的开销
24+
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,
25+
还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。
26+
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
2727

2828
- 工作流程
2929
线程池的任务就在于负责这些线程的创建,销毁和任务处理参数传递、唤醒和等待。
3030
- 创建若干线程,置入线程池
3131
- 任务达到时,从线程池取空闲线程
3232
- 取得了空闲线程,立即进行任务处理
33-
- 否则新建一个线程,并置入线程池,执行3
33+
- 否则新建一个线程,并置入线程池,并执行上一步
3434
- 如果创建失败或者线程池已满,根据设计策略选择返回错误或将任务置入处理队列,等待处理
3535
- 销毁线程池
3636

@@ -76,10 +76,10 @@ public class ThreadPoolExecutor extends AbstractExecutorService {
7676

7777
上面构造器中各参数的含义:
7878

79-
- `corePoolSize`:核心池的大小。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,
79+
- `corePoolSize`:用来表示线程池中的核心线程的数量,也可以称为可闲置的线程数量。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,
8080
除非调用了`prestartAllCoreThreads()`或者`prestartCoreThread()`方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建`corePoolSize`个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到`corePoolSize`后,就会把到达的任务放到缓存队列当中;
8181

82-
- `maximumPoolSize`:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
82+
- `maximumPoolSize`:来表示线程池中最多能够创建的线程数量。这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
8383
- `keepAliveTime`:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于`corePoolSize`时,`keepAliveTime`才会起作用,直到线程池中的线程数不大于`corePoolSize`,即当线程池中的线程数大于`corePoolSize`时,如果一个线程空闲的时间达到`keepAliveTime`,则会终止,直到线程池中的线程数不超过`corePoolSize`。但是如果调用了`allowCoreThreadTimeOut(boolean)`方法,在线程池中的线程数不大于`corePoolSize`时,`keepAliveTime`参数也会起作用,直到线程池中的线程数为0;
8484
- `unit`:参数`keepAliveTime`的时间单位,有7种取值,在`TimeUnit`类中有7种静态属性:
8585

@@ -100,7 +100,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService {
100100

101101
- `threadFactory`:线程工厂,主要用来创建线程
102102
- `handler`:表示当拒绝处理任务时的策略,有以下四种取值:
103-
103+
104104
- `ThreadPoolExecutor.AbortPolicy`:丢弃任务并抛出`RejectedExecutionException`异常
105105
- `ThreadPoolExecutor.DiscardPolicy`:也是丢弃任务,但是不抛出异常
106106
- `ThreadPoolExecutor.DiscardOldestPolicy`:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
@@ -111,15 +111,18 @@ public class ThreadPoolExecutor extends AbstractExecutorService {
111111
线程池状态
112112
---
113113

114+
![](https://raw.githubusercontent.com/CharonChui/Pictures/master/thread_poll_state.png)
115+
116+
117+
114118
`ThreadPoolExecutor`中定义了一个`volatile`变量,另外定义了几个`static final`变量表示线程池的各个状态:
115119

116-
- `volatile int runState;`
117-
- `static final int RUNNING = -1;`当创建线程池后,初始时,线程池处于`RUNNING`状态;
120+
- `volatile int runState;` 表示当前线程池的状态,它是一个`volatile`变量用来保证线程之间的可见性
121+
- `static final int RUNNING = -1;`此状态下,线程池可以接受新的任务,也可以处理阻塞队列中的任务。执行shutdown()方法可进入待关闭(SHUTDOWN)状态,执行shutdownNow()方法可进入停止(STOP)状态。
118122
- `static final int SHUTDOWN = 0;`如果调用了`shutdown()`方法,则线程池处于`SHUTDOWN`状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕
119123
- `static final int STOP = 1;`如果调用了`shutdownNow()`方法,则线程池处于`STOP`状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
120-
- `static final int TIDYING = 2;`该状态表示线程池对线程进行整理优化;
121-
- `static final int TERMINATED = 3;`当线程池处于`SHUTDOWN``STOP`状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为`TERMINATED`状态
122-
`runState`表示当前线程池的状态,它是一个`volatile`变量用来保证线程之间的可见性;
124+
- `static final int TIDYING = 2;`此状态下,所有任务都已经执行完毕,且没有工作线程。执行terminated()方法进入终止(TERMINATED)状态。该状态表示线程池对线程进行整理优化;
125+
- `static final int TERMINATED = 3;`当线程池处于`SHUTDOWN``STOP`状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为`TERMINATED`状态。此状态下,线程池完全终止,并完成了所有资源的释放。
123126

124127

125128
`Executors`
@@ -170,6 +173,61 @@ public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
170173
实际中,如果`Executors`提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置`ThreadPoolExecutor`的参数有点麻烦,要根据实际任务的类型和数量来进行配置。
171174

172175

176+
177+
## 线程池执行状态
178+
179+
![](https://raw.githubusercontent.com/CharonChui/Pictures/master/thread_poll_process.png)
180+
181+
整个过程可以拆分成以下几个部分:
182+
183+
- 提交任务
184+
185+
当向线程池提交一个新的任务时,线程池有三种处理情况,分别是:创建一个工作线程来执行该任务、将任务加入阻塞队列、拒绝该任务。
186+
187+
提交任务的过程也可以拆分成以下几个部分:
188+
189+
1. 当工作线程数小于核心线程数时,直接创建新的核心工作线程。
190+
2. 当工作线程数大于核心线程数时,就需要尝试将任务添加到阻塞队列中去。
191+
3. 如果能够加入成功,说明队列还没满,那么就需要做以下的二次校验来保证添加进去的任务能够成功被执行。
192+
4. 验证当前线程池中的运行状态,如果是非RUNNING状态,则需要将任务从阻塞队列中移除,然后拒绝该任务。
193+
5. 验证当前线程池中的工作线程的个数,如果是0,则需要主动添加一个空工作线程来执行刚刚添加到阻塞队列中的任务。
194+
6. 如果加入失败,说明队列已经满了,这时就需要创建新的临时工作线程来执行任务。
195+
7. 如果创建成功,则直接执行该任务。
196+
8. 如果创建失败,说明工作线程数已经等于最大线程数了,只能拒绝该任务了。
197+
198+
- 创建工作线程
199+
200+
创建工作线程需要做一系列的判断,需要确保当前线程池可以创建新的线程之后,才能创建。
201+
202+
首先,当线程池的状态是SHUTDOWN或者STOP时,不能创建新的线程。
203+
204+
其次,当线程工厂创建线程失败时,也不能创建新的线程。
205+
206+
第三,拿当前工作线程的数量与核心线程数、最大线程数进行比较,如果前者大于后者的话,也不允许创建。除此之外,线程池会尝试通过CAS来自增工作线程的个数,如果自增成功了,则会创建新的工作线程,即Worker对象。
207+
208+
然后加锁进行二次验证是否能够创建工作线程,如果最后创建成功,则会启动该工作线程。
209+
210+
- 启动工作线程
211+
212+
当工作线程创建成功后,也就是Worker对象已经创建好了,这时就需要启动该工作线程,让线程开始干活了,Worker对象中关联着一个Thread,所以要启动工作线程的话,只要通过worker.thread.start()来启动该线程即可。
213+
214+
启动完了之后,就会执行Worker对象的run方法,因为Worker实现了Runnable接口,所以本质上Worker也是一个线程。
215+
216+
通过线程start开启之后就会调用到Runnable的run方法,在Worker对象的run方法中,调用了runWorker(this)方法,也就是把当前对象传递给了runWorker()方法,让它来执行。
217+
218+
- 获取任务并执行
219+
220+
在runWorker方法被调用之后,就是执行具体的任务了,首先需要拿到一个可以执行的任务,而Worker对象中默认绑定了一个任务,如果该任务不为空的话,那么就是直接执行。
221+
222+
执行完了之后,就会去阻塞队列中获取任务来执行。
223+
224+
获取任务的过程则需要考虑当前工作线程的个数:
225+
226+
1. 如果工作线程数大于核心线程数,那么就需要通过poll(keepAliveTime, timeUnit)来获取,因为这时需要对闲置线程进行超时回收。
227+
2. 如果工作线程数小于等于核心线程数,那么就可以通过take()来获取了。因为这时所有的线程都是核心线程,不需要进行回收,前提是没有设置allowCoreThreadTimeOut(允许核心线程超时回收)为true。
228+
229+
230+
173231
向线程池提交任务
174232
---
175233

0 commit comments

Comments
 (0)