From 6fefae90360d23ca313ad811897a8f9d7c21d151 Mon Sep 17 00:00:00 2001
From: IvanLu1024 <501275190@qq.com>
Date: 2019年6月28日 09:39:45 +0800
Subject: [PATCH 01/17] =?UTF-8?q?=E3=80=90=E7=B3=BB=E7=BB=9F=E8=AE=BE?=
=?UTF-8?q?=E8=AE=A1=E3=80=91=E6=9B=B4=E6=96=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 1 +
...03345円274円217円344円272円213円345円212円241円.md" | 165 ++++++++++++++++--
docs/SystemDesign/03CAP.md | 42 -----
docs/SystemDesign/04BASE.md | 32 ----
...210206円345円270円203円345円274円217円Session.md" | 6 +-
...6346円236円204円-347円233円256円345円275円225円.md" | 4 +-
6 files changed, 158 insertions(+), 92 deletions(-)
delete mode 100644 docs/SystemDesign/03CAP.md
delete mode 100644 docs/SystemDesign/04BASE.md
rename "docs/SystemDesign/08351円233円206円347円276円244円344円270円213円347円232円204円 Session 347円256円241円347円220円206円.md" => "docs/SystemDesign/08345円210円206円345円270円203円345円274円217円Session.md" (94%)
diff --git a/.gitignore b/.gitignore
index 7be1202..0fd834d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
.idea/
*.xml
+.idea/vcs.xml
diff --git "a/docs/SystemDesign/02345円210円206円345円270円203円345円274円217円344円272円213円345円212円241円.md" "b/docs/SystemDesign/02345円210円206円345円270円203円345円274円217円344円272円213円345円212円241円.md"
index 92d2deb..3d3d432 100644
--- "a/docs/SystemDesign/02345円210円206円345円270円203円345円274円217円344円272円213円345円212円241円.md"
+++ "b/docs/SystemDesign/02345円210円206円345円270円203円345円274円217円344円272円213円345円212円241円.md"
@@ -1,28 +1,86 @@
* [二、分布式事务](#二分布式事务)
- * [本地消息表](#本地消息表)
+ * [CAP](#CAP)
+ * [BASE](#BASE)
* [2PC](#2pc)
-
+ * [TCC](#TCC)
+ * [本地消息表](#本地消息表)
+ * [MQ事务消息](#MQ事务消息)
+ * [Sagas事务模型](#Sagas事务模型)
+ *
# 二、分布式事务
-指事务的操作位于不同的节点上,需要保证事务的 ACID 特性。
+指事务的操作位于**不同的节点**上,需要保证事务的 ACID 特性。例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
-例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
+这个时候单个数据库的ACID已经不能适应这种情况了,而在这种ACID的集群环境下,再想保证集群的ACID几乎是很难达到,或者即使能达到那么效率和性能会大幅下降,最为关键的是再很难扩展新的分区了,这个时候如果再追求集群的ACID会导致我们的系统变得很差,这时我们就需要引入一个新的理论原则来适应这种集群的情况,就是 CAP 原则或者叫CAP定理,那么CAP定理指的是什么呢?
-## 本地消息表
+## CAP
-本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。
+分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容忍性(P:Partition Tolerance),最多只能同时满足其中两项。
-1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
-2. 之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
-3. 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
+
-
+### 一致性
+
+一致性指的是多个数据副本是否能保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。
+
+对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。
+
+### 可用性
+
+可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99% 的时间是可用的。
+
+在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
+
+### 分区容忍性
+
+网络分区指分布式系统中的节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信。
+
+在分区容忍性条件下,分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障。
+
+### 权衡
+
+在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此,CAP 理论实际上是要在**可用性(P)和一致性(C)之间做权衡**。
+
+可用性和一致性往往是冲突的,很难使它们同时满足。在多个节点之间进行数据同步时,
+
+- 为了保证一致性(CP),不能访问未同步完成的节点,也就失去了部分可用性,zookeeper其实就是追求的强一致;
+- 为了保证可用性(AP),放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的BASE也是根据AP来扩展。
+
+
+
+## BASE
+
+在分布式系统中,我们往往追求的是**可用性**,它的重要程序比一致性要高,那么如何实现高可用性呢? 前人已经给我们提出来了另外一个理论,就是BASE理论,它是用来对CAP定理进行进一步扩充的。
+
+BASE 是基本可用(Basically Available)、软状态(Soft State)和最终一致性(Eventually Consistent)三个短语的缩写。
+
+BASE 理论是对 CAP 中一致性和可用性权衡的结果,它的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
+
+
+
+### 基本可用
+
+指分布式系统在出现故障的时候,保证核心可用,允许损失部分可用性。
+
+例如,电商在做促销时,为了保证购物系统的稳定性,部分消费者可能会被引导到一个降级的页面。
+
+### 软状态
+
+指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性,即允许系统不同节点的数据副本之间进行同步的过程存在时延。
+
+### 最终一致性
+
+最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能达到一致的状态。
+
+ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE 要求最终一致性,通过牺牲强一致性来达到可用性,通常运用在大型分布式系统中。
+
+在实际的分布式场景中,不同业务单元和组件对一致性的要求是不同的,因此 ACID 和 BASE 往往会结合在一起使用。
## 2PC
-两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。
+两阶段提交(Two-phase Commit,2PC),通过引入**协调者**(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。
### 1. 运行过程
@@ -56,4 +114,87 @@
#### 2.4 太过保守
-任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
\ No newline at end of file
+任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
+
+## TCC
+
+TCC 即补偿事务,其实就是采用的**补偿机制**,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:
+
+- Try 阶段主要是对业务系统做检测及资源预留;
+- Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功;
+- Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
+
+举个例子,假入 Bob 要向 Smith 转账,思路大概是:
+我们有一个本地方法,里面依次调用
+
+1. 首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。
+2. 在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
+3. 如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
+
+**优点:** 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
+
+**缺点:** 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。
+
+## 本地消息表
+
+此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。
+
+本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。
+
+1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
+2. 之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
+3. 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
+4. 如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
+
+
+
+这种方案遵循BASE理论,采用的是最终一致性,笔者认为是这几种方案里面比较适合实际业务场景的,即不会出现像2PC那样复杂的实现(当调用链很长的时候,2PC的可用性是非常低的),也不会像TCC那样可能出现确认或者回滚不了的情况。
+
+**优点:** 一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET中 有现成的解决方案。
+
+**缺点:** 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
+
+## MQ事务消息
+
+有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。
+
+以阿里的 RocketMQ 中间件为例,其思路大致为:
+
+第一阶段Prepared消息,会拿到消息的地址。
+第二阶段执行本地事务。
+
+第三阶段通过第一阶段拿到的地址去访问消息,消息接受者就能使用这个消息。
+
+
+
+
+
+也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就**保证了消息发送与本地事务同时成功或同时失败**。
+
+
+
+
+
+**优点:** 实现了最终一致性,不需要依赖本地数据库事务。
+
+**缺点:** 实现难度大,主流MQ不支持,RocketMQ事务消息部分代码也未开源。
+
+## Sagas 事务模型
+
+Saga事务模型又叫做长时间运行的事务(Long-running-transaction), 它是由普林斯顿大学的H.Garcia-Molina等人提出,它描述的是另外一种在没有两阶段提交的的情况下解决分布式系统中复杂的业务事务问题。你可以在[这里](https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf)看到 Sagas 相关论文。
+
+我们这里说的是一种基于 Sagas 机制的工作流事务模型,这个模型的相关理论目前来说还是比较新的,以至于百度上几乎没有什么相关资料。
+
+该模型其核心思想就是拆分分布式系统中的长事务为多个短事务,或者叫多个本地事务,然后由 Sagas 工作流引擎负责协调,如果整个流程正常结束,那么就算是业务成功完成,如果在这过程中实现失败,那么Sagas工作流引擎就会以相反的顺序调用补偿操作,重新进行业务回滚。
+
+比如我们一次关于购买旅游套餐业务操作涉及到三个操作,他们分别是预定车辆,预定宾馆,预定机票,他们分别属于三个不同的远程接口。可能从我们程序的角度来说他们不属于一个事务,但是从业务角度来说是属于同一个事务的。当发生失败时,会依次进行取消的补偿操作。
+
+
+
+
+
+# 参考资料
+
+- [再有人问你分布式事务,把这篇扔给他]()
+- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html)
+
diff --git a/docs/SystemDesign/03CAP.md b/docs/SystemDesign/03CAP.md
deleted file mode 100644
index 67ce192..0000000
--- a/docs/SystemDesign/03CAP.md
+++ /dev/null
@@ -1,42 +0,0 @@
-
-* [三、CAP](#三cap)
- * [一致性](#一致性)
- * [可用性](#可用性)
- * [分区容忍性](#分区容忍性)
- * [权衡](#权衡)
-
-
-# 三、CAP
-
-分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容忍性(P:Partition Tolerance),最多只能同时满足其中两项。
-
-
-
-## 一致性
-
-一致性指的是多个数据副本是否能保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。
-
-对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。
-
-## 可用性
-
-可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99% 的时间是可用的。
-
-在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
-
-## 分区容忍性
-
-网络分区指分布式系统中的节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信。
-
-在分区容忍性条件下,分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障。
-
-## 权衡
-
-在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此,CAP 理论实际上是要在可用性和一致性之间做权衡。
-
-可用性和一致性往往是冲突的,很难使它们同时满足。在多个节点之间进行数据同步时,
-
-- 为了保证一致性(CP),不能访问未同步完成的节点,也就失去了部分可用性;
-- 为了保证可用性(AP),允许读取所有节点的数据,但是数据可能不一致。
-
-
\ No newline at end of file
diff --git a/docs/SystemDesign/04BASE.md b/docs/SystemDesign/04BASE.md
deleted file mode 100644
index efe1252..0000000
--- a/docs/SystemDesign/04BASE.md
+++ /dev/null
@@ -1,32 +0,0 @@
-
-* [四、BASE](#四base)
- * [基本可用](#基本可用)
- * [软状态](#软状态)
- * [最终一致性](#最终一致性)
-
-
-# 四、BASE
-
-BASE 是基本可用(Basically Available)、软状态(Soft State)和最终一致性(Eventually Consistent)三个短语的缩写。
-
-BASE 理论是对 CAP 中一致性和可用性权衡的结果,它的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
-
-
-
-## 基本可用
-
-指分布式系统在出现故障的时候,保证核心可用,允许损失部分可用性。
-
-例如,电商在做促销时,为了保证购物系统的稳定性,部分消费者可能会被引导到一个降级的页面。
-
-## 软状态
-
-指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性,即允许系统不同节点的数据副本之间进行同步的过程存在时延。
-
-## 最终一致性
-
-最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能达到一致的状态。
-
-ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE 要求最终一致性,通过牺牲强一致性来达到可用性,通常运用在大型分布式系统中。
-
-在实际的分布式场景中,不同业务单元和组件对一致性的要求是不同的,因此 ACID 和 BASE 往往会结合在一起使用。
\ No newline at end of file
diff --git "a/docs/SystemDesign/08351円233円206円347円276円244円344円270円213円347円232円204円 Session 347円256円241円347円220円206円.md" "b/docs/SystemDesign/08345円210円206円345円270円203円345円274円217円Session.md"
similarity index 94%
rename from "docs/SystemDesign/08351円233円206円347円276円244円344円270円213円347円232円204円 Session 347円256円241円347円220円206円.md"
rename to "docs/SystemDesign/08345円210円206円345円270円203円345円274円217円Session.md"
index e093384..992a491 100644
--- "a/docs/SystemDesign/08351円233円206円347円276円244円344円270円213円347円232円204円 Session 347円256円241円347円220円206円.md"
+++ "b/docs/SystemDesign/08345円210円206円345円270円203円345円274円217円Session.md"
@@ -9,7 +9,7 @@
一个用户的 Session 信息如果存储在一个服务器上,那么当负载均衡器把用户的下一个请求转发到另一个服务器,由于服务器没有用户的 Session 信息,那么该用户就需要重新进行登录等操作。
-## Sticky Session
+## 粘性 Seesion(Sticky Session)
需要配置负载均衡器,使得一个用户的所有请求都路由到同一个服务器,这样就可以把用户的 Session 存放在该服务器中。
@@ -19,7 +19,7 @@
-## Session Replication
+## Session 广播(Session Replication)
在服务器之间进行 Session 同步操作,每个服务器都有所有用户的 Session 信息,因此用户可以向任何一个服务器进行请求。
@@ -30,7 +30,7 @@
-## Session Server
+## 缓存集中式管理(Session Server)
使用一个单独的服务器存储 Session 数据,可以使用传统的 MySQL,也使用 Redis 或者 Memcached 这种内存型数据库。
diff --git "a/docs/347円263円273円347円273円237円346円236円266円346円236円204円-347円233円256円345円275円225円.md" "b/docs/347円263円273円347円273円237円346円236円266円346円236円204円-347円233円256円345円275円225円.md"
index cd5e6ac..47f740f 100644
--- "a/docs/347円263円273円347円273円237円346円236円266円346円236円204円-347円233円256円345円275円225円.md"
+++ "b/docs/347円263円273円347円273円237円346円236円266円346円236円204円-347円233円256円345円275円225円.md"
@@ -8,15 +8,13 @@
- [分布式锁](./SystemDesign/01分布式锁.md)
- [分布式事务](./SystemDesign/02分布式事务.md)
-- [CAP](./SystemDesign/03CAP.md)
-- [BASE](./SystemDesign/04BASE.md)
- [Paxos](./SystemDesign/05Paxos.md)
- [Raft](./SystemDesign/06Raft.md)
### 集群
- [负载均衡](./SystemDesign/07负载均衡.md)
-- [集群下的 Session 管理](./SystemDesign/08集群下的%%Session%%管理.md)
+- [分布式 Session ](./SystemDesign/08分布式Session.md)
### 攻击技术
From e85b6d7f1b5c6ebbefd375a2ff3084a3f7af8f76 Mon Sep 17 00:00:00 2001
From: IvanLu1024 <501275190@qq.com>
Date: 2019年6月28日 09:40:02 +0800
Subject: [PATCH 02/17] Update Zookeeper.md
---
docs/BigData/Zookeeper.md | 23 +++++++++++++----------
1 file changed, 13 insertions(+), 10 deletions(-)
diff --git a/docs/BigData/Zookeeper.md b/docs/BigData/Zookeeper.md
index 618b7bc..b05f72c 100644
--- a/docs/BigData/Zookeeper.md
+++ b/docs/BigData/Zookeeper.md
@@ -201,10 +201,15 @@ ZAB 协议两种基本的模式:崩溃恢复和消息广播。
# Zookeeper 的应用场景
-- 统一配置
-- 统一命名管理
-- 分布式锁
-- 集群管理
+## 分布式协调/通知
+
+ZooKeeper中持有的Watcher注册与异步通知机制,能够很好地实现分布式环境下不同机器,甚至是不同系统之间的协调与通知,从而实现对数据变更的实时处理。通常的做法是不同的客户端都对ZooKeeper上同一个数据节点进行Watcher注册,监听数据节点的变化(包括数据节点本身及其子节点),如果数据节点发送变化,那么所有订阅者都能够收到相应的Watcher通知,并做出相应处理。
+
+## 分布式锁
+
+举个栗子。对某一个数据连续发出两个修改操作,两台机器同时收到了请求,但是只能一台机器先执行完另外一个机器再执行。那么此时就可以使用 zookeeper 分布式锁,一个机器接收到了请求之后先获取 zookeeper 上的一把分布式锁,就是可以去创建一个 znode,接着执行操作;然后另外一个机器也**尝试去创建**那个 znode,结果发现自己创建不了,因为被别人创建了,那只能等着,等第一个机器执行完了自己再执行。
+
+> 实现原理:
## 数据发布/订阅
@@ -214,15 +219,13 @@ ZAB 协议两种基本的模式:崩溃恢复和消息广播。
由于系统的配置信息通常具有数据量较小、数据内容在运行时会动态变化、集群中各机器共享等特性,这样特别适合使用ZooKeeper来进行统一配置管理。
-## 统一命名服务
-
-ZooKeeper提供了一套分布式全局唯一ID的分配机制,所谓ID,就是一个能够唯一标识某个对象的标识符。
+## 元数据/配置信息管理
-## 分布式协调/通知
-
-ZooKeeper中持有的Watcher注册与异步通知机制,能够很好地实现分布式环境下不同机器,甚至是不同系统之间的协调与通知,从而实现对数据变更的实时处理。通常的做法是不同的客户端都对ZooKeeper上同一个数据节点进行Watcher注册,监听数据节点的变化(包括数据节点本身及其子节点),如果数据节点发送变化,那么所有订阅者都能够收到相应的Watcher通知,并做出相应处理。
+zookeeper 可以用作很多系统的配置信息的管理,比如 kafka、storm 等等很多分布式系统都会选用 zookeeper 来做一些元数据、配置信息的管理,包括 dubbo 注册中心不也支持 zookeeper 么?
+## HA高可用
+这个应该是很常见的,比如 hadoop、hdfs、yarn 等很多大数据系统,都选择基于 zookeeper 来开发 HA 高可用机制,就是一个**重要进程一般会做主备**两个,主进程挂了立马通过 zookeeper 感知到切换到备用进程。
> 补充资料:
From 801d06791bf49667e3f3097b80d04874fab838a7 Mon Sep 17 00:00:00 2001
From: IvanLu1024 <501275190@qq.com>
Date: 2019年6月28日 09:40:09 +0800
Subject: [PATCH 03/17] Update Redis.md
---
docs/DataBase/Redis.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/docs/DataBase/Redis.md b/docs/DataBase/Redis.md
index 02c9f54..7d4eefe 100644
--- a/docs/DataBase/Redis.md
+++ b/docs/DataBase/Redis.md
@@ -339,7 +339,9 @@ List 是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息
在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。
-可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。
+- 单节点:可以使用 Redis 自带的 SETNX 或SET命令实现分布式锁。
+
+- 多节点:[RedLock模型](https://www.jianshu.com/p/fba7dd6dcef5),具有安全性、避免死锁和容错性的特性。
### 其它
From 37a5886ad42d8dd6f9c7beaa82cc6f8cd2addea4 Mon Sep 17 00:00:00 2001
From: DuHouAn <18351926682@163.com>
Date: 2019年6月28日 13:32:58 +0800
Subject: [PATCH 04/17] Java-Notes
---
...03347円273円223円346円236円204円345円236円213円.md" | 38 +-
.../00Spring346円246円202円350円277円260円.md" | 93 ++-
docs/Spring/01SpringIOC.md | 669 +++++++++++++--
docs/Spring/Spring AOP.md | 776 ++++++++++++++++++
...37345円221円275円345円221円250円346円234円237円.md" | 545 ++++++++++++
...76350円256円241円346円250円241円345円274円217円.md" | 29 +
docs/Spring/SpringIOC.md | 463 +++++++++++
7 files changed, 2495 insertions(+), 118 deletions(-)
create mode 100644 docs/Spring/Spring AOP.md
create mode 100644 "docs/Spring/Spring 344円270円255円 Bean 347円232円204円347円224円237円345円221円275円345円221円250円346円234円237円.md"
create mode 100644 "docs/Spring/Spring 344円270円255円346円266円211円345円217円212円345円210円260円347円232円204円350円256円276円350円256円241円346円250円241円345円274円217円.md"
create mode 100644 docs/Spring/SpringIOC.md
diff --git "a/docs/OO/03347円273円223円346円236円204円345円236円213円.md" "b/docs/OO/03347円273円223円346円236円204円345円236円213円.md"
index 58daedd..cbf2d0d 100644
--- "a/docs/OO/03347円273円223円346円236円204円345円236円213円.md"
+++ "b/docs/OO/03347円273円223円346円236円204円345円236円213円.md"
@@ -30,26 +30,20 @@ Adapter:适配器,把Adaptee适配成为Client需要的Target。
### Implementation1
-美国的电饭煲是在电压为110V下工作,而中国的电饭煲在电压220V下工作。
-
-要求将在中国使用的电饭煲适配成能在美国使用,从而使得电饭煲能够跨国销售!
+美国的电饭煲是在电压为 110V 下工作,而中国的电饭煲在电压 220V 下工作。要求将在美国使用的电饭煲适配成能在中国使用。
```java
/**
- * Adaptee:
- * 已经存在的接口,通常能满足客户端的功能要求,
+ * Adaptee:已经存在的接口,通常能满足客户端的功能要求。
* 但是接口与客户端要求的特定领域接口不一致,需要被适配。
*/
public interface CHINA220 {
//220 V电压接口
- public void connect();
+ void connect();
}
```
```java
-/**
- * Adaptee的具体实现类
- */
public class CHINA220Impl implements CHINA220{
@Override
public void connect() {
@@ -64,7 +58,7 @@ public class CHINA220Impl implements CHINA220{
*/
public interface USA110 {
//110 V电压接口
- public void connect();
+ void connect();
}
```
@@ -92,10 +86,10 @@ public class PowerAdapter implements USA110{
/**
* 该电器在110V下工作
*/
-public class USAElectricCooker {
+public class USACooker {
private USA110 usa110;
- public USAElectricCooker(USA110 usa110) {
+ public USACooker(USA110 usa110) {
this.usa110 = usa110;
}
@@ -107,20 +101,24 @@ public class USAElectricCooker {
```
```java
-/**
- * 想要110V,但是只有220V,就用220V "装" 110V
- */
public class Client {
public static void main(String[] args) {
- CHINA220 china220=new CHINA220Impl();
- PowerAdapter usa110=new PowerAdapter(china220);
- USAElectricCooker cooker=new USAElectricCooker(usa110);
- cooker.cook();
+ // 110 V 电压下直接 cook
+ USA110 usa110 = new USA110Impl();
+ USACooker usaCooker = new USACooker(usa110);
+ usaCooker.cook();
+
+ // 220 V 电压下需要适配成 110V,才可 cook
+ CHINA220 china220 = new CHINA220Impl();
+ PowerAdapter adapter = new PowerAdapter(china220);
+ USACooker usaCooker2 = new USACooker(adapter);
+ usaCooker2.cook();
}
}
```
-输出结果:
```html
+110V 接通电源,开始工作...
+开始煮饭...
220V 接通电源,开始工作...
开始煮饭...
```
diff --git "a/docs/Spring/00Spring346円246円202円350円277円260円.md" "b/docs/Spring/00Spring346円246円202円350円277円260円.md"
index d3c4201..8ec5548 100644
--- "a/docs/Spring/00Spring346円246円202円350円277円260円.md"
+++ "b/docs/Spring/00Spring346円246円202円350円277円260円.md"
@@ -1,55 +1,72 @@
-
-* [Spring概述](#Spring概述)
- * [Spring框架](#Spring框架)
- * [Spring的核心](#Spring的核心)
- * [Spring优点](#Spring优点)
- * [Spring入门程序](#Spring入门程序)
-
-# Spring概述
+# 一、Spring 概述
-## Spring框架
-Spring是**分层**的JavaSE/EE full-stack(**一站式**) 轻量级开源框架
+## Spring 框架
+Spring 是**分层**的 JavaSE/EE **一站式** 轻量级开源框架。
-> 分层:
+- **分层**
-SUN提供的EE的三层结构:web层、业务层、数据访问层(持久层,集成层)
+ SUN 提供的 EE 的三层结构:Web 层、业务层、数据访问层。
-> 一站式:
+- **一站式**
-Spring框架有对三层的每层解决方案:
+ Spring 框架对三层结构的每层都有解决方案:
-- web层:Spring MVC
-- 持久层:JDBC Template
-- 业务层:Spring的Bean管理
+ * Web 层:[SpringMVC](https://duhouan.github.io/Java-Notes/#/./Spring/06SpringMVC)
+ * 业务层:SpringBean 管理
+ * 业务层:JDBCTemplate
+Spring 框架的几个模块:
-## Spring的核心
-> IOC
+
+组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
-控制反转(Inverse of Control):将对象的创建权,交由Spring完成。
+- **核心容器(Spring Core)**:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 `BeanFactory`,它是工厂模式的实现。`BeanFactory` 使用*控制反转* (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
+- **Spring 上下文**:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
+- **Spring AOP**:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
+- **Spring DAO**:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
+- **Spring ORM**:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
+- **Spring Web 模块**:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
+- **Spring MVC 框架**:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
-> AOP
+Spring 框架优点:
-面向切面编程(Aspect Oriented Programming):**面向对象的功能延伸**。不是替换面向对象,是用来解决OO中一些问题.
+- **方便解耦,简化开发**
+ Spring 就是一个大工厂,可以将所有对象创建和依赖关系维护,交给 Spring 管理
-## Spring优点
-- **方便解耦,简化开发**; Spring就是一个大工厂,可以将所有对象创建和依赖关系维护,交给Spring管理
+- **AOP 编程的支持**
-- **AOP编程的支持**; Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能
+ Spring 提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能
-- **声明式事务的支持**:只需要通过配置就可以完成对事务的管理,而无需手动编程
+- **声明式事务的支持**
-- **方便程序的测试**:Spring对Junit4支持,可以通过注解方便的测试Spring程序
+ 只需要通过配置就可以完成对事务的管理,而无需手动编程
-- **方便集成各种优秀框架**:Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架
-(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持
+- **方便程序的测试**
-- **降低JavaEE API的使用难度**:Spring 对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),
-都提供了封装,使这些API应用难度大大降低
+ Spring 对 Junit4 支持,可以通过注解方便的测试 Spring 程序
+
+- **方便集成各种优秀框架**
+
+ Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持
+
+- **降低 JavaEE API 的使用难度**
+
+ Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低
+
+## Spring 核心
+
+- **IOC**
+
+ 控制反转(Inversion of Control,IoC):将对象的创建,交由 Spring 完成。
+
+- **AOP**
+
+ 面向切面变成(Aspect Oriented Programming,AOP):是面向对象思想的延伸,不是替换面向对象思想,是用来解决使用面向对象中出现的一些问题的。
+
+## Spring 入门程序
+- 创建 Spring 的配置文件
-## Spring入门程序
-### 1.创建Spring的配置文件
在src下创建一个applicationContext.xml。
引入XML的约束: 找到xsd-config.html.引入beans约束:
@@ -64,13 +81,14 @@ http://www.springframework.org/schema/beans http://www.springframework.org/schem
```
-### 2.在配置文件中配置类
+- 在配置文件中配置类
```html
```
-### 3.测试
+- 测试
+
```java
public interface UserService {
void say();
@@ -129,8 +147,3 @@ public class SpringTest {
}
}
```
-
-### IOC(控制反转)和DI(依赖注入)区别
-IOC(控制反转):对象的创建权,由Spring管理。
-
-DI(依赖注入):在Spring创建对象的过程中,把**对象依赖的属性**注入到类中。
\ No newline at end of file
diff --git a/docs/Spring/01SpringIOC.md b/docs/Spring/01SpringIOC.md
index b489fcc..50afe18 100644
--- a/docs/Spring/01SpringIOC.md
+++ b/docs/Spring/01SpringIOC.md
@@ -1,14 +1,612 @@
-
-* [SpringIOC](#SpringIOC)
- * [IOC装配Bean](#IOC装配Bean)
- * [IOC使用注解方式装配Bean](#IOC使用注解方式装配Bean)
- * [SpringIOC原理](#SpringIOC原理)
- * [SpringIOC源码分析](#SpringIOC源码分析)
-
+# 二、Spring IOC
+
+## IoC 与 DI
+
+### IoC 概念
+
+控制反转(Iversion of Control,IoC)是一种**设计思想**。**将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理**。涉及到 2 个方面:
+
+- **控制**
+
+ 控制对象的创建及销毁(生命周期)。
+
+- **反转**
+
+ 将对象的控制权交给 IoC 容器。
+
+所有的类都会在 Spring 容器中注册,告诉 Spring 你是个什么东西,你需要什么东西,然后 Spring 会在系统运行到适当的时候,把你需要的东西主动给你
+
+**所有类的创建、销毁都由 Spring 来控制,也就是说控制对象生命周期的不是引用它的对象,而是 Spring**。对于某个具体对象而言,以前是它控制其他对象,现在所有对象都被 Spring 控制。
+
+### DI 概念
+
+依赖注入(Dependency Injection,DI) 。依赖注入举例:设计行李箱。
+
+
+
+相应的代码如下:
+
+
+
+size 固定值,改进后的代码:
+
+
+
+使用 DI 方式进行改进:
+
+
+
+改进后相应的代码如下:
+
+
+
+不难理解,依赖注入就是**将底层类作为参数传递给上层类,实现上层对下层的控制**,**依赖注入实现控制反转**。
+
+### IoC 和 DI 关系
+
+IoC 是在系统运行中,**动态的向某个对象提供它所需要的其他对象**,通过 DI 来实现。
+
+- IoC 和 DI 的关系如下图:
+
+
+
+- 依赖倒置原则、IoC、DI 和 IoC 容器的关系:
+
+
+
+## IoC 容器
+
+IoC 容器功能:
+
+- 管理 Bean 的生命周期
+- 控制 Bean 依赖注入
+
+使用 IoC 容器的好处:
+
+- 避免在各处使用 new 来创建类,并且可以做到统一维护
+
+- 创建实例的时候不需要了解其中的细节
+
+ 依赖注入方式获取实例:
+
+
+
+ 使用 IoC 容器获取获取实例(创建实例的时候不需要了解其中的细节):
+
+
+
+
+
+## XML 配置文件解析
+
+### BeanDefinition
+
+BeanDefinition 是一个接口。在 Spring 中存在 3 种实现:
+
+- RootBeanDefinition
+- ChildBeanDefinition
+- GenericBeanDefinition
+
+三种实现都继承了 AbstractBeanDefinition。**AbstractBeanDefinition 是对上述的共同的类信息进行抽象**。
+
+```java
+public abstract class AbstractBeanDefinition extends
+ BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable {
+ //...
+}
+```
+
+**BeanDefinition 是配置文件中 ``元素标签在容器中的内部表现形式**。
+
+在配置文件中,可以定义父`` 和子``,父`` 用 RootBeanDefinition 表示,而子``用ChildBeanDefinition 表示,而没有父`` 的 `` 就使用 RootBeanDefinition 表示。
+
+### BeanDefinitionRegistry
+
+Spring 通过 BeanDefinition 将配置文件中的``配置信息转换为容器的内部表示,并且将这些 BeanDefinition 注册 **BeanDefinitionRegistry** 中。
+
+Spring 容器的 BeanDefinitionRegistry 就是配置信息的 内存数据库,主要是以 map 的形式保存数据,后续操作直接从 BeanDefinitionRegistry 中读取配置信息。
+
+### Resource
+
+Spring 采用 Resource 来对各种资源进行统一抽象。
+
+Resource 是一个接口,定义了资源的基本操作,包括是否存在、是否可读、是否已经打开等等。
+
+Resource 继承了 InputStreamSource。
+
+```java
+public interface Resource extends InputStreamSource {
+ //...
+}
+```
+
+InputStreamSource 封装任何可以返回 InputStream 的类。
+
+```java
+/*
+* InputStreamSource 封装任何可以返回 InputStream 的类。
+*/
+public interface InputStreamSource {
+ /*
+ * 用于返回一个新的 InputStream 对象
+ */
+ InputStream getInputStream() throws IOException;
+}
+```
+
+对于不同来源的资源文件都有相应的 Resource 实现:
+
+| 资源来源 | Resource 实现类 |
+| :--------------: | :-----------------: |
+| 文件 | FileSystemResource |
+| classpath 资源 | ClassPathResource |
+| URL 资源 | UrlResource |
+| InputStream 资源 | InputStreamResource |
+| Byte 数组 | ByteArrayResource |
+
+### 解析过程
+
+分为 2 大步骤:
+
+**1、配置文件的封装**
+
+Resource 接口**抽象了所有 Spring 内部使用到的底层资源**:File、URL、ClassPath 等。
+
+**2、加载 Bean**
+
+- 封装资源文件。XMLBeanDefinitionReader 对参数 Resource 使用 EncodeResource 类进行封装。
+- 获取输入流。从 Resource 中获取对应的 InputStream 并构造 InputSource。
+- 通过构造的 InputSource 实例和 Resource 实例继续调用函数 `doLoadBeanDefinition`。
+ * 获取 XML 文件的实体解析器和验证模式(常见的验证模式有 DTD 和 XSD 两种)
+ * 加载 XML 文件,并得到对应的 Document
+ * 根据返回的 Document 解析并注册 BeanDefinition
+
+## Spring 的 IoC 容器
+
+
+
+IoC 容器其实就是一个大工厂,它用来管理我们所有的对象以及依赖关系:
+
+- 根据 Bean 配置信息在容器内部创建 Bean 定义注册表
+- 根据注册表加载,实例化 Bean,**建立 Bean 与 Bean 之间的依赖关系**
+- 将 Bean 实例放入 Spring IoC 容器中,等待应用程序调用
+
+### Spring IoC 支持的功能
+
+- 依赖注入
+- 依赖检查
+- 自动装配属性
+- 可指定初始化方法和销毁方法
+
+### Spring 的 2 种 IoC 容器
+
+- **BeanFactory**
+ - IoC 容器要实现的最基础的接口
+ - 采用**延迟初始化策略**(容器初始化完成后并不会创建 Bean 对象,只有当收到初始化请求时才进行初始化)
+ - 由于是延迟初始化策略,因此启动速度较快,占用资源较少
+
+- **ApplicationConext**
+ - 在 BeanFactory 基础上,增加了更为高级的特性:事件发布、国际化等。
+ - 在容器启动时,完成所有 Bean 的创建
+ - 启动时间较长,占用资源较多
+
+### IoC 容器的初始化过程
+
+Spring IoC 容器的初始化过程分为 3 个阶段:Resource 定位、BeanDefinition 的载入和向 IoC 容器注册 BeanDefinition。Spring 将这 3 个阶段分离,并使用不同的模块来完成,这样可以让用户更加灵活的对这 3 个阶段进行扩展。
+
+
+
+1、**Resource 定位**
+
+Resource 定位指的是 BeanDefinition 的资源定位,它由 ResourceLoader 通过统一的 Resource 接口来完成,Resource 为各种形式的 BeanDefinition 的使用都提供了统一的接口。
+
+2、**BeanDefinition 的载入**
+
+BeanDefinition 实际上就是 POJO 对象在 IoC 容器中的抽象,通过 BeanDefiniton,IoC 容器可以方便对 POJO 对象进行管理。
+
+3、 **向 IoC 容器注册 BeanDefinition**
+
+向 IoC 容器注册 BeanDefinition 是通过调用 BeanDefinitionRegistry 接口来的实现来完成的,这个注册过程是把载入的 BeanDefinition 向 IoC 容器进行注册。实际上 IoC 容器内部维护这一个 HashMap,而这个注册过程其实就是将 BeanDefinition 添加至该 HashMap 中。
+
+> 注意:BeanFactory 和 FactoryBean 的区别
+
+BeanFactory 是 IoC 最基本的容器,负责生产和管理 Bean,为其他具体的 IoC 容器提供了最基本的规范。
+
+FactoryBean 是一个 Bean,是一个接口,当 IoC 容器中的 Bean 实现了 FactoryBean 后,
+
+通过 getBean(String beanName) 获取到的 Bean 对象并不是 FactoryBean 的实现类对象,而是这个实现类中的 getObject() 方法返回的对象。
+
+要想获取 FactoryBean 的实现类对象,就是在 beanName 前面加上 "&"。
+
+## Spring Bean 加载流程
+
+AbstractBeanFactory 中 `getBean`最终调用 `doGetBean`方法。
+
+```java
+protected T doGetBean(String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
+ String beanName = this.transformedBeanName(name);
+ Object sharedInstance = this.getSingleton(beanName);
+ Object bean;
+ if (sharedInstance != null && args == null) {
+ if (this.logger.isTraceEnabled()) {
+ if (this.isSingletonCurrentlyInCreation(beanName)) {
+ this.logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
+ } else {
+ this.logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
+ }
+ }
+
+ bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
+ } else {
+ if (this.isPrototypeCurrentlyInCreation(beanName)) {
+ throw new BeanCurrentlyInCreationException(beanName);
+ }
+
+ BeanFactory parentBeanFactory = this.getParentBeanFactory();
+ if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
+ String nameToLookup = this.originalBeanName(name);
+ if (parentBeanFactory instanceof AbstractBeanFactory) {
+ return ((AbstractBeanFactory)parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
+ }
+
+ if (args != null) {
+ return parentBeanFactory.getBean(nameToLookup, args);
+ }
+
+ if (requiredType != null) {
+ return parentBeanFactory.getBean(nameToLookup, requiredType);
+ }
+
+ return parentBeanFactory.getBean(nameToLookup);
+ }
+
+ if (!typeCheckOnly) {
+ this.markBeanAsCreated(beanName);
+ }
+
+ try {
+ RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
+ this.checkMergedBeanDefinition(mbd, beanName, args);
+ String[] dependsOn = mbd.getDependsOn();
+ String[] var11;
+ if (dependsOn != null) {
+ var11 = dependsOn;
+ int var12 = dependsOn.length;
+
+ for(int var13 = 0; var13 < var12; ++var13) { + String dep = var11[var13]; + if (this.isDependent(beanName, dep)) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); + } + + this.registerDependentBean(dep, beanName); + + try { + this.getBean(dep); + } catch (NoSuchBeanDefinitionException var24) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", var24); + } + } + } + + if (mbd.isSingleton()) { + sharedInstance = this.getSingleton(beanName, () -> {
+ try {
+ return this.createBean(beanName, mbd, args);
+ } catch (BeansException var5) {
+ this.destroySingleton(beanName);
+ throw var5;
+ }
+ });
+ bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
+ } else if (mbd.isPrototype()) {
+ var11 = null;
+
+ Object prototypeInstance;
+ try {
+ this.beforePrototypeCreation(beanName);
+ prototypeInstance = this.createBean(beanName, mbd, args);
+ } finally {
+ this.afterPrototypeCreation(beanName);
+ }
+
+ bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
+ } else {
+ String scopeName = mbd.getScope();
+ Scope scope = (Scope)this.scopes.get(scopeName);
+ if (scope == null) {
+ throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
+ }
+
+ try {
+ Object scopedInstance = scope.get(beanName, () -> {
+ this.beforePrototypeCreation(beanName);
+
+ Object var4;
+ try {
+ var4 = this.createBean(beanName, mbd, args);
+ } finally {
+ this.afterPrototypeCreation(beanName);
+ }
+
+ return var4;
+ });
+ bean = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
+ } catch (IllegalStateException var23) {
+ throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", var23);
+ }
+ }
+ } catch (BeansException var26) {
+ this.cleanupAfterBeanCreationFailure(beanName);
+ throw var26;
+ }
+ }
+
+ if (requiredType != null && !requiredType.isInstance(bean)) {
+ try {
+ T convertedBean = this.getTypeConverter().convertIfNecessary(bean, requiredType);
+ if (convertedBean == null) {
+ throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
+ } else {
+ return convertedBean;
+ }
+ } catch (TypeMismatchException var25) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", var25);
+ }
+
+ throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
+ }
+ } else {
+ return bean;
+ }
+}
+```
+
+doGetBean 大致流程如下:
+
+**1、获取参数 name 对应的真正 beanName**
+
+**2、尝试从缓存中加载单例**
+
+单例在 Spring 的同一个容器中只会被创建一次,后续再获取 Bean,就直接从**单例缓存**中获取。
+
+这里只是尝试加载,首先尝试从**缓存**中加载,如果加载不成功则再次尝试从 **singletonFactories** 中加载。
+
+在创建依赖的时候为了避免循环依赖,在 Spring 中创建 Bean 的原则:**不等 bean 创建完成就会将创建 Bean 的 ObjectFactory 提早曝光加入到缓存中,一旦下一个 Bean 创建时需要依赖上一个 Bean 则直接使用 ObjectFactory**。
+
+**3、Bean 的实例化**
+
+如果从缓存中得到了 Bean 的原始状态,则需要对 Bean 进行实例化。这里有必要强调一下,缓存中记录的是原始的 Bean 状态,并一定是我们最终想要的 Bean。
+
+**4、原型模式的依赖检查**
+
+只有在**单例情况下**才会尝试解决循环依赖。
+
+如果存在 A 中有 B 的属性,B 中有 A 的属性,那么当依赖注入的时 候,就会产生当 A 还未创建完的时候因为对于 B 的创建再次返回创建 A ,造成循环依赖,即 isPrototypeCurrentlyInCreation(String beanName) 判断 true。
+
+**5、检测 parentBeanFactory**
+
+如果缓存没有数据的话,直接转到 parentBeanFactory 去加载,条件是:
+
+```java
+parentBeanFactory != null && !this.containsBeanDefinition(beanName)
+```
+
+containsBeanDefinition 方法是检测如果当前 XML 中没有配置对应的 beanName,只能到 parentBeanfactory 中尝试一下。
+
+**6、将 GenericBeanDefinition 对象转换为 RootBeanDefiniion 对象**
+
+从 XML 配置文件中读取 Bean 信息会放到 GenericBeanDefinition 中, 但是所有的 Bean 后续处理都是针对 RootBeanDefinition 的。
+
+这里做一下转换,转换的同时如果父类 Bean 不为空的话,则会进一步合并父类的属性。
+
+**7、初始化依赖的 Bean**
+
+Spring 在初始化某一个 Bean 的时候首先初始化这个 Bean 的依赖。
+
+**8、依据当前 bean 的作用域对 bean 进行实例化**
+
+Spring 中可以指定各种 scope,默认是 singleton 。
+
+还有其他的配置比如 prototype、request 等,Spring 会根据不同的配置进行不同的初始化策略。
+
+**9、类型转换**
+
+将返回的 Bean 转化为 requiredType 所指定的类型。
+
+**10、返回 Bean**
+
+## Spring 解决循环依赖
+
+循环依赖就是循环引用,就是两个或者多个 Bean 之间相互持有对方,比如 CircleA 引用 CircleB,CircleB 引用 CircleC,CircleC 引用 CircleA,则它们最终反映为一个环。
+
+### 构造器循环依赖
+
+表示通过构造器注入构成的循环依赖。此依赖是无法解决的,只能抛出 BeanConcurrentlyCreationException 异常表示循环依赖。
+
+### setter 循环依赖
+
+表示通过 setter 注入构成的循环依赖。 解决该依赖问题是通过 Spring 容器提前暴露刚完成构造器注入但未完成其他步骤(如 setter 注入) 的 Bean 来完成的,而且只能解决单例作用域的 Bean 的循环依赖。
+
+对于 `scope="prototype"`的 Bean,Spring 容器无法完成依赖注入,因为 "prototype" 作用域 Bean,Spring 容器不进行缓存,因此无法提前暴露一个创建中的 Bean。对于 "singleton" 作用域 Bean,可以通过setAllowCircleReferences(false) 来禁用循环引用。
+
+对于单例对象来说,在 Spring 整个容器的生命周期内,有且只有一个对象,很容易想到这个对象应该存在于**缓存**中,Spring 在解决循环依赖问题的过程中就使用了"三级缓存":
+
+- **singletonObjects **
+
+ 用于保存 beanName 和创建 Bean 实例之间的关系,缓存的是 **Bean 实例**。
+
+- **singletonFactories**
+
+ 用于保存 beanName 和创建 Bean 的工厂之间的关系,缓存的是为解决循环依赖而准备的 **ObjectFactory**。
+
+- **earlySingletonObjects**
+
+ 用于保存 beanName 和创建 Bean 实例之间的关系,与 singletonObjects 的不同之处在于,当一个单例 Bean 被放到这里面后,当 Bean 还在创建过程中,就可以通过 getBean 方法获得,其目的是检查循环引用。这边缓存的也是实例,只是这边的是为解决循环依赖而提早暴露出来的实例,其实就是 ObjectFactory。
+
+首先尝试从 singletonObjects 中获取实例,如果获取不到,再从 earlySingletonObjects 中获取,如果还获取不到,再尝试从 singletonFactories 中获取 beanName 对应的 ObjectFactory,然后调用 getObject 来创建 Bean,并放到 earlySingletonObjects 中去,并且从 singletonFactories 中 remove 掉这个 ObjectFactory。
+
+> 注意:singleObjects 和 earlySingletonObjects
+
+- 两者都是以 beanName 为key,Bean 实例为 value 进行存储
+
+- 两者得出区别在于 singletonObjects 存储的是实例化的完成的 Bean 实例,而 earlySingletonObjects 存储的是正在实例化中的 Bean,所以两个集合的内容是互斥的。
+
+
+
+# Spring 框架中涉及到的设计模式
+
+- [工厂模式](https://duhouan.github.io/Java-Notes/#/./OO/01创建型?id=_2-简单工厂(simple-factory))
+
+ BeanFactory 用来创建各种不同的 Bean。
+
+- [单例模式](https://duhouan.github.io/Java-Notes/#/./OO/01创建型?id=_1-单例(singleton))
+
+ 在 Spring 配置文件中定义的 Bean 默认为单利模式。
+
+- [模板方法模式](https://duhouan.github.io/Java-Notes/#/./OO/02行为型?id=_10-模板方法(template-method))
+
+ 用来解决代码重复的问题。比如 JdbcTempolate。
+
+- [代理模式](https://duhouan.github.io/Java-Notes/#/./OO/03结构型?id=_7-代理(proxy))
+
+ AOP、事务都大量运用了代理模式。
+
+- [原型模式](https://duhouan.github.io/Java-Notes/#/./OO/01创建型?id=_6-原型模式(prototype))
+
+ 特点在于通过"复制"一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的"原型",这个原型是可定制的。
+
+- [责任链模式](https://duhouan.github.io/Java-Notes/#/./OO/02行为型?id=_1-责任链(chain-of-responsibility))
+
+ 在 SpringMVC 中,会经常使用一些拦截器(HandlerInterceptor),当存在多个拦截器的时候,所有的拦截器就构成了一条拦截器链。
+
+- [观察者模式](https://duhouan.github.io/Java-Notes/#/./OO/02行为型?id=_7-观察者(observer))
+
+ Spring 中提供了一种监听机制,即 ApplicationListenber,可以实现 Spring 容器内的事件监听。
+
+
+
+
+
+# ////////////////////
+
+
+
+## Spring IoC
+
+### Spring IoC 支持的功能
+
+-
+
+### Spring IoC 的核心接口
+
+Spring 中核心的接口或者类:
+
+- **BeanDefinition**
+
+ **用来描述 Bean 的定义**。将 xml 文件或者注解中的 Bean 定义解析成 BeanDefintion。
+
+- **BeanDefiniitonRegistry**
+
+ **提供向 IoC 容器注册 BeanDefinition 对象的方法。**以` `键值对形式存储到 beanDefinitionMap 中,同时将 beanName 存入到 beanDefinionNames 中,方便后续 Bean 的实例化。
+
+Spring IoC 的核心接口:
+
+- **BeanFactory**
+- **ApplicationContext**
+
+### Spring IoC 容器的原理
+
+#### BeanFactory
+
+BeanFactory 中:
+
+- 提供 IoC 控制机制
+- 包含 Bean 的各种定义,便于实例化 Bean
+- 建立 Bean 之间的依赖关系
+- Bean 生命周期的控制
+
+BeanFactory 类继承体系:
+
+
+
+BeanFactory 的生命周期:
+
+
+
+#### ApplicationContext
+
+ApplicationContext 类继承体系:
+
+
+
+ApplicationContext 的生命周期:
+
+
+
+初始化的过程都是比较长,我们可以**分类**来对其进行解析:
+
+- **Bean 自身的方法**
+
+ 如调用 Bean 的构造函数实例化 Bean,调用 Setter 设置 Bean 的属性值以及通过 ``的 init-method 和 destroy-method 所指定的方法;
+
+- **Bean 级生命周期接口方法**
+
+ 如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,这些接口方法由 Bean 类直接实现;
+
+- **容器级生命周期接口方法**
+
+ 在上图中带"★" 的步骤是由 InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为" **后处理器**" 。 **后处理器接口一般不由 Bean 本身实现,它们独立于 Bean,实现类以容器附加装置的形式注册到 Spring 容器中并通过接口反射为 Spring 容器预先识别**。当 Spring 容器创建任何 Bean 的时候,这些后处理器都会发生作用,所以这些后处理器的影响是全局性的。当然,用户可以通过合理地编写后处理器,让其仅对感兴趣 Bean 进行加工处理。
+
+#### ApplicationContext 和 BeanFactory
+
+- ApplicationContext 会利用 Java 反射机制**自动识别出配置文件中定义的 BeanPostProcessor、 InstantiationAwareBeanPostProcesso 和 BeanFactoryPostProcessor 后置器**,自动将它们注册到应用上下文中。
+
+ BeanFactory 需要在代码中通过**手工调用`addBeanPostProcessor()`方法进行注册**。
+
+- ApplicationContext 在**初始化**应用上下文的时候**就实例化所有单实例的 Bean**。
+
+ BeanFactory 在初始化容器的时候并未实例化 Bean,**直到**第一次访问某个 Bean 时**才**实例化目标 Bean。
+
+#### Bean 的初始化过程
+
+
+
+简要总结:
+
+- BeanDefinitionReader **读取 Resource 所指向的配置文件资源**,然后解析配置文件。配置文件中每一个`` 解析成一个 **BeanDefinition 对象**,并**保存**到 BeanDefinitionRegistry 中;
+
+- 容器扫描 BeanDefinitionRegistry 中的 BeanDefinition;
+
+ 调用 InstantiationStrategy **进行 Bean 实例化的工作**;
+
+ 使用 **BeanWrapper 完成 Bean 属性的设置**工作;
+
+- 单例 Bean 缓存池:Spring 在 DefaultSingletonBeanRegistry 类中提供了一个用于缓存单实例 Bean 的**缓存器**,它是一个用 HashMap 实现的缓存器,单实例的 Bean **以 beanName 为键保存在这个 HashMap** 中。
+
+
+
+> 实践:通过注解方式实现 Bean 装载到 IOC 容器中
+
+```java
+
+```
+
+
+
+
+
+
+
+
+
+# =====================
+
-# SpringIOC
## IOC装配Bean
+
### Spring框架Bean实例化的方式
提供了三种方式实例化Bean:
@@ -497,55 +1095,10 @@ IoC的一个重点是在系统运行中,**动态的向某个对象提供它所
注射到A当中,这样就完成了对各个对象之间关系的控制。
A需要依赖 Connection才能正常运行,而这个Connection是由Spring注入到A中的,依赖注入的名字就这么来的。
-
-
-
-
-
-
-那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),
-它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,
-**Spring就是通过[反射]()来实现注入的**。
-
-## IOC容器的优势
-
-- 避免在各处使用new来创建类,这个容器可以自动对代码进行初始化,并且可以做到统一维护。
-- 创建实例的时候不需要了解其中的细节,这个容器相当于一个工厂。
-
-
-
-
-
-
-
-## BeanFactory与ApplicationContext的比较
-
-- BeanFactory 是 Spring 框架的基础设施,面向Spring
-- ApplicationContext 面向使用 Spring 框架的开发者
-
-> 补充参考资料:
-
-## SpringIOC源码分析
-
-### 初始化
-Spring IOC的初始化过程,整个脉络很庞大,初始化的过程主要就是**读取XML资源**,并**解析**,
-最终**注册到Bean Factory中**。
-
-
-
-### 注入依赖
-当完成初始化IOC容器后,如果Bean没有设置lazy-init(延迟加载)属性,那么Bean的实例就会在初始化IOC完成之后,及时地进行**初始化**。
-初始化时会**创建实例**,然后根据配置利用反射对实例进行进一步操作,具体流程如下所示:
-
-
-
-### getBean方法的代码逻辑
+# 参考资料
-- 转换beanName
-- 从缓存中加载实例
-- 实例化Bean
-- 检测partentBeanFactory
-- 初始化依赖的Bean
-- 创建Bean
+- [Spring 框架小白的蜕变](https://www.imooc.com/learn/1108)
-### [Spring核心源码学习](https://yikun.github.io/2015/05/29/Spring-IOC%E6%A0%B8%E5%BF%83%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0/)
\ No newline at end of file
+- [Spring IOC知识点一网打尽!](https://segmentfault.com/a/1190000014979704)
+- [Spring核心思想,IoC与DI详解(如果还不明白,放弃java吧)](https://blog.csdn.net/Baple/article/details/53667767)
+- [15个经典的Spring面试常见问题](https://mp.weixin.qq.com/s/uVoeXRLNEMK8c00u3tm9KA)
\ No newline at end of file
diff --git a/docs/Spring/Spring AOP.md b/docs/Spring/Spring AOP.md
new file mode 100644
index 0000000..05a5326
--- /dev/null
+++ b/docs/Spring/Spring AOP.md
@@ -0,0 +1,776 @@
+
+* [SpringAOP](#SpringAOP)
+ * [SpringAOP概述](#SpringAOP概述)
+ * [AOP的底层实现](#AOP的底层实现)
+ * [Spring中的AOP](#Spring中的AOP)
+ * [Spring的AspectJ的AOP](#Spring的AspectJ的AOP)
+
+
+# SpringAOP
+
+## SpringAOP概述
+### 什么是AOP
+AOP(面向切面编程,Aspect Oriented Programing)。
+
+AOP采取**横向抽取**机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
+
+Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类**织入**增强代码
+
+AspecJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对AspectJ的支持,
+AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入。
+
+AOP底层原理就是**代理机制**。
+
+> 关注点分离:不同的问题交给不同部分去解决
+
+### Spring的AOP代理
+- JDK动态代理:对实现了接口的类生成代理
+- CGLib代理机制:对类生成代理
+
+### AOP的术语
+
+| 术语 | 描述 |
+| :--: | :--: |
+| Joinpoint(连接点) | 所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是**方法**,因为Spring只支持**方法类型的连接点**。 |
+| Pointcut(切入点) | 所谓切入点是指我们要**对哪些Joinpoint进行拦截**的定义。 |
+| Advice(通知/增强) | 所谓通知是指拦截到Joinpoint之后所要做的事情就是**通知**。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能) |
+| Introduction(引介) | 引介是一种**特殊的通知**在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field |
+| Target(目标对象) | 代理的目标对象 |
+| Weaving(织入) | 是指把增强应用到目标对象来创建新的代理对象的过程。有三种织入方式: Spring采用**动态代理织入**,而AspectJ采用**编译期织入**和**类装载期织入** |
+| Proxy(代理)| 一个类被AOP织入增强后,就产生一个结果代理类 |
+| Aspect(切面) | 是切入点和通知(/引介)的结合 |
+
+- 示例:
+
+
+
+## AOP的底层实现
+### JDK动态代理
+JDK动态代理:对**实现了接口的类**生成代理
+
+```java
+public interface IUserDao {
+ void add();
+ void delete();
+ void update();
+ void search();
+}
+```
+- UserDao实现了IUserDao接口
+```java
+public class UserDao implements IUserDao{
+ @Override
+ public void add() {
+ System.out.println("添加功能");
+ }
+
+ @Override
+ public void delete() {
+ System.out.println("删除功能");
+ }
+
+ @Override
+ public void update() {
+ System.out.println("更新功能");
+ }
+
+ @Override
+ public void search() {
+ System.out.println("查找功能");
+ }
+}
+```
+
+```java
+public class JdkProxy implements InvocationHandler{
+ private IUserDao iUserDao;
+
+ public JdkProxy(IUserDao iUserDao){
+ this.iUserDao=iUserDao;
+ }
+
+ public IUserDao getPrxoy(){
+ return (IUserDao)Proxy.newProxyInstance(
+ iUserDao.getClass().getClassLoader(),
+ iUserDao.getClass().getInterfaces(),
+ this
+ );
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ String methodName = method.getName();
+ Object obj = null;
+ //只对add方法进行增强
+ if ("add".equals(methodName)) {
+ System.out.println("开启事务");
+ obj = method.invoke(iUserDao, args);
+ System.out.println("结束事务");
+ } else {
+ obj = method.invoke(iUserDao, args);
+ }
+ return obj;
+ }
+}
+```
+- 对实现了接口的类UserDao生成代理
+```java
+public class JdkProxyDemo {
+ public static void main(String[] args) {
+ IUserDao userDao=new UserDao();
+ JdkProxy jdkProxy=new JdkProxy(userDao);
+ IUserDao userDao2=jdkProxy.getPrxoy();
+ userDao2.add();
+ }
+}
+```
+- 输出结果
+```html
+开启事务
+添加功能
+结束事务
+```
+
+### Cglib动态代理
+以**继承的方式**动态生成目标类的代理。
+
+CGLIB(Code Generation Library)是一个开源项目!
+是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
+
+```java
+public class ProductDao {
+ public void add() {
+ System.out.println("添加功能");
+ }
+
+ public void delete() {
+ System.out.println("删除功能");
+ }
+
+ public void update() {
+ System.out.println("更新功能");
+ }
+
+ public void search() {
+ System.out.println("查找功能");
+ }
+}
+```
+
+```java
+public class CGLibProxy implements MethodInterceptor {
+ private ProductDao productDao;
+
+ public CGLibProxy(ProductDao productDao) {
+ this.productDao = productDao;
+ }
+
+ public ProductDao getProxy(){
+ // 使用CGLIB生成代理:
+ // 1.创建核心类:
+ Enhancer enhancer = new Enhancer();
+ // 2.为其设置父类:
+ enhancer.setSuperclass(productDao.getClass());
+ // 3.设置回调:
+ enhancer.setCallback(this);
+ // 4.创建代理:
+ return (ProductDao) enhancer.create();
+ }
+
+ @Override
+ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
+ String menthodName=method.getName();
+ Object obj=null;
+ if("add".equals(menthodName)){
+ System.out.println("开启事务");
+ obj=methodProxy.invokeSuper(proxy,args);
+ System.out.println("结束事务");
+ }else{
+ obj=methodProxy.invokeSuper(proxy,args);
+ }
+ return obj;
+ }
+}
+```
+```java
+public class CGLibProxyDemo {
+ public static void main(String[] args) {
+ ProductDao productDao=new ProductDao();
+ CGLibProxy proxy=new CGLibProxy(productDao);
+ ProductDao productDao2=proxy.getProxy();
+ productDao2.add();
+ }
+}
+```
+- 输出结果
+```html
+开启事务
+添加功能
+结束事务
+```
+
+### 总结
+
+Spring框架中:
+
+- **如果类实现了接口,就使用JDK的动态代理生成代理对象**
+- **如果这个类没有实现任何接口,使用CGLIB生成代理对象**
+
+
+### 相关阅读
+
+- [代理设计模式](https://github.com/DuHouAn/Java/blob/master/Object_Oriented/notes/03%E7%BB%93%E6%9E%84%E5%9E%8B.md#7-%E4%BB%A3%E7%90%86proxy)
+
+Spring中代理模式的实现:
+
+- 真实实现类的逻辑包含在getBean方法中;
+- getBean方法返回的实际上是Proxy的实例;
+- Proxy实例是Spring 采用JDK Proxy或CGLIB动态生成的。
+
+## Spring中的AOP
+
+### Spring中通知
+Spring中的通知Advice其实是指"增强代码"。
+
+| 通知类型 | 全类名 | 说明 |
+| :--: | :--: | :--: |
+| 前置通知 | org.springframework.aop.MethodBeforeAdvice | 在目标方法执行前实施增强 |
+| 后置通知 | org.springframework.aop.AfterReturningAdvice | 在目标方法执行后实施增强 |
+| 环绕通知 | org.aopalliance.intercept.MethodInterceptor | 在目标方法执行前后实施增强 |
+| 异常抛出通知 | org.springframework.aop.ThrowsAdvice | 在方法抛出异常后实施增强 |
+| 引介通知 | org.springframework.aop.IntroductionInterceptor | 在目标类中添加一些新的方法和属性 |
+
+
+### Spring中切面类型
+Advisor : Spring中传统切面。
+- Advisor:一个切点和一个通知组合。
+- Aspect:多个切点和多个通知组合。
+
+Advisor : 代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截(不带有切点的切面.针对所有方法进行拦截)
+PointcutAdvisor : 代表具有切点的切面,可以指定拦截目标类哪些方法(带有切点的切面,针对某个方法进行拦截)
+IntroductionAdvisor : 代表引介切面,针对引介通知而使用切面(不要求掌握)
+
+### Spring的AOP的开发
+
+#### 1. 不带有切点的切面(针对所有方法的增强)
+> 第一步:导入相应jar包
+```html
+
+ aopalliance
+ aopalliance
+ 1.0
+
+```
+
+> 第二步:编写被代理对象
+
+IUserDao接口
+```java
+public interface IUserDao {
+ void add();
+ void update();
+ void delete();
+ void search();
+}
+```
+UserDao实现类
+```java
+public class UserDao implements IUserDao{
+ @Override
+ public void add() {
+ System.out.println("增加功能");
+ }
+
+ @Override
+ public void update() {
+ System.out.println("修改功能");
+ }
+
+ @Override
+ public void delete() {
+ System.out.println("删除功能");
+ }
+
+ @Override
+ public void search() {
+ System.out.println("查找功能");
+ }
+}
+```
+
+> 第三步:编写增强的代码
+```java
+import org.springframework.aop.MethodBeforeAdvice;
+import java.lang.reflect.Method;
+
+/**
+ * 前置增强
+ */
+public class MyBeforeAdvice implements MethodBeforeAdvice{
+ @Override
+ public void before(Method method, Object[] args, Object target) throws Throwable {
+ System.out.println("前置增强...");
+ }
+}
+```
+
+> 第四步:生成代理(配置生成代理)
+
+**生成代理Spring基于ProxyFactoryBean类。底层自动选择使用JDK的动态代理还是CGLIB的代理**。
+
+属性:
+- target : 代理的目标对象
+- proxyInterfaces : 代理要实现的接口
+```html
+如果多个接口可以使用以下格式赋值
+
+
+ ....
+
+```
+- proxyTargetClass : 是否对类代理而不是接口,设置为true时,使用CGLib代理
+- interceptorNames : 需要织入目标的Advice
+- singleton : 返回代理是否为单实例,默认为单例
+- optimize : 当设置为true时,强制使用CGLib
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+> 测试
+```java
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("classpath:applicationContext.xml")
+public class SpringTest {
+ @Autowired
+ @Qualifier("userDaoProxy")
+ // 注入是真实的对象,必须注入代理对象.
+ IUserDao iUserDao;
+
+ @Test
+ public void test(){
+ iUserDao.add();
+ iUserDao.delete();
+ iUserDao.update();
+ iUserDao.search();
+ }
+}
+```
+- 输出结果:
+```html
+前置增强...
+增加功能
+前置增强...
+删除功能
+前置增强...
+修改功能
+前置增强...
+查找功能
+```
+
+#### 2. 带有切点的切面(针对目标对象的某些方法进行增强)
+PointcutAdvisor 接口:
+- DefaultPointcutAdvisor 最常用的切面类型,它可以通过任意Pointcut和Advice组合定义切面
+- RegexpMethodPointcutAdvisor 构造正则表达式切点切面
+
+> 第一步:创建被代理对象
+```java
+public class OrderDao {
+ public void add() {
+ System.out.println("增加功能");
+ }
+
+ public void update() {
+ System.out.println("修改功能");
+ }
+
+ public void delete() {
+ System.out.println("删除功能");
+ }
+
+ public void search() {
+ System.out.println("查找功能");
+ }
+}
+```
+
+> 第二步:编写增强的代码
+
+```java
+/**
+ * 环绕增强
+ */
+public class MyAroundAdvice implements MethodInterceptor {
+ @Override
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+ System.out.println("环绕前增强...");
+ Object obj= invocation.proceed();
+ System.out.println("环绕后增强...");
+ return obj;
+ }
+}
+```
+
+> 第三步:生成代理(配置生成代理)
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+> 测试
+```java
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("classpath:applicationContext.xml")
+public class SpringTest {
+ @Autowired
+ @Qualifier("orderDaoProxy")
+ OrderDao orderDao;
+
+ @Test
+ public void test(){
+ orderDao.add();
+ orderDao.delete();
+ orderDao.update();
+ orderDao.search();
+ }
+}
+```
+
+- 输出结果:
+```html
+环绕前增强...
+增加功能
+环绕后增强...
+删除功能
+修改功能
+环绕前增强...
+查找功能
+环绕后增强...
+```
+
+#### 3. 自动代理
+前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,
+在实际开发中,非常多的**Bean每个都配置ProxyFactoryBean开发维护量巨大**。
+
+自动创建代理(**基于后处理Bean。在Bean创建的过程中完成的增强。生成Bean就是代理**。)
+- BeanNameAutoProxyCreator 根据Bean名称创建代理
+- DefaultAdvisorAutoProxyCreator 根据Advisor本身包含信息创建代理。
+其中AnnotationAwareAspectJAutoProxyCreator是基于Bean中的AspectJ 注解进行自动代理。
+
+**BeanNameAutoProxyCreator(按名称生成代理)**
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```java
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("classpath:applicationContext.xml")
+public class SpringTest {
+ @Autowired
+ @Qualifier("userDao")
+ UserDao userDao;
+ //代理的是实例类,不是接口。
+
+ @Autowired
+ @Qualifier("orderDao")
+ OrderDao orderDao;
+
+ @Test
+ public void test(){
+ userDao.add();
+ userDao.search();
+
+ orderDao.add();
+ orderDao.search();
+ }
+}
+```
+
+**DefaultAdvisorAutoProxyCreator(根据切面中定义的信息生成代理)**
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .*add.*
+ .*search.*
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```java
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("classpath:applicationContext.xml")
+public class SpringTest {
+ @Autowired
+ @Qualifier("userDao")
+ IUserDao userDao;
+
+ @Autowired
+ @Qualifier("orderDao")
+ OrderDao orderDao;
+
+ @Test
+ public void test(){
+ userDao.add();
+ userDao.search();
+
+ orderDao.add();
+ orderDao.search();
+ }
+}
+```
+
+### 区分基于ProxyFattoryBean的代理与自动代理区别?
+
+- ProxyFactoryBean:先有被代理对象,将被代理对象传入到代理类中生成代理。
+
+- 自动代理基于后处理Bean。**在Bean的生成过程中,就产生了代理对象**,把代理对象返回。
+生成的Bean已经是代理对象。
+
+## Spring的AspectJ的AOP
+
+### 基于XML
+> 第一步:编写被增强的类
+
+UserDao
+
+> 第二步:定义切面
+
+```java
+public class MyAspectXML {
+ public void before(){
+ System.out.println("前置通知...");
+ }
+
+ public void afterReturing(Object returnVal){
+ System.out.println("后置通知...返回值:"+returnVal);
+ }
+
+ public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
+ System.out.println("环绕前增强....");
+ Object result = proceedingJoinPoint.proceed();
+ System.out.println("环绕后增强....");
+ return result;
+ }
+
+ public void afterThrowing(Throwable e){
+ System.out.println("异常通知..."+e.getMessage());
+ }
+
+ public void after(){
+ System.out.println("最终通知....");
+ }
+}
+```
+
+> 第三步:配置applicationContext.xml
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+- 测试
+```java
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("classpath:applicationContext.xml")
+public class SpringTest {
+ @Autowired
+ @Qualifier("userDao")
+ IUserDao userDao;
+
+ @Test
+ public void test(){
+ userDao.add();
+ userDao.search();
+ }
+}
+```
+
+### 基于注解
+AspectJ的通知类型:
+
+| 通知类型 | 解释 | 说明 |
+| :--:| :--: | :--: |
+| @Before | 前置通知,相当于BeforeAdvice | 就在方法之前执行。没有办法阻止目标方法执行的。 |
+| @AfterReturning | 后置通知,相当于AfterReturningAdvice | 后置通知,获得方法返回值。 |
+| @Around | 环绕通知,相当于MethodInterceptor | 在可以方法之前和之后来执行的,而且可以阻止目标方法的执行。 |
+| @AfterThrowing | 抛出通知,相当于ThrowAdvice | |
+| @After | 最终final通知,不管是否异常,该通知都会执行 | |
+| @DeclareParents | 引介通知,相当于IntroductionInterceptor | |
+
+
+**Advisor和Aspect的区别**?
+- Advisor:Spring传统意义上的切面,支持一个切点和一个通知的组合。
+- Aspect:可以支持多个切点和多个通知的组合。
+
+> 使用注解编写切面类
+```java
+/**
+ * 切面类:就是切点与增强结合
+ */
+@Aspect //申明是切面
+public class MyAspect{
+ @Before("execution(* advice.UserDao.add(..))")
+ public void before(JoinPoint joinPoint){
+ System.out.println("前置增强...."+joinPoint);
+ }
+
+ @AfterReturning(value="execution(* advice.UserDao.update(..))",returning="returnVal")
+ public void afterReturin(Object returnVal){
+ System.out.println("后置增强....方法的返回值:"+returnVal);
+ }
+
+ @Around(value="MyAspect.myPointcut()")
+ public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
+ System.out.println("环绕前增强....");
+ Object obj = proceedingJoinPoint.proceed();
+ System.out.println("环绕后增强....");
+ return obj;
+ }
+
+ @AfterThrowing(value="MyAspect.myPointcut()",throwing="e")
+ public void afterThrowing(Throwable e){
+ System.out.println("不好了 出异常了!!!"+e.getMessage());
+ }
+
+ @After("MyAspect.myPointcut()")
+ public void after(){
+ System.out.println("最终通知...");
+ }
+
+ //定义切点
+ @Pointcut("execution(* advice.UserDao.search(..))")
+ private void myPointcut(){}
+}
+```
+> 配置applicationContext.xml
+```html
+
+
+
+
+
+
+
+
+```
+
+- 测试:
+```java
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("classpath:applicationContext.xml")
+public class SpringTest {
+ @Autowired
+ @Qualifier("userDao")
+ IUserDao userDao;
+
+ @Test
+ public void test(){
+ userDao.add();
+ userDao.search();
+ }
+}
+```
\ No newline at end of file
diff --git "a/docs/Spring/Spring 344円270円255円 Bean 347円232円204円347円224円237円345円221円275円345円221円250円346円234円237円.md" "b/docs/Spring/Spring 344円270円255円 Bean 347円232円204円347円224円237円345円221円275円345円221円250円346円234円237円.md"
new file mode 100644
index 0000000..024f5a2
--- /dev/null
+++ "b/docs/Spring/Spring 344円270円255円 Bean 347円232円204円347円224円237円345円221円275円345円221円250円346円234円237円.md"
@@ -0,0 +1,545 @@
+
+* [Spring中Bean的生命周期](#Spring中Bean的生命周期)
+ * [Bean的作用域](#Bean的作用域)
+ * [Bean的生命周期](#Bean的生命周期)
+ * [initialize和destroy](#initialize和destroy)
+ * [XxxAware接口](#XxxAware接口)
+ * [BeanPostProcessor](#BeanPostProcessor)
+ * [总结](#总结)
+
+
+# Spring中Bean的生命周期
+Spring 中,组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 Bean。
+简单地讲,Bean 就是由 IOC 容器初始化、装配及管理的对象。
+Bean 的定义以及 Bean 相互间的依赖关系通过**配置元数据**来描述。
+
+Spring中的Bean默认都是**单例**的,
+这些单例Bean在多线程程序下如何保证线程安全呢?
+例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,
+引入Spring框架之后,**每个Action都是单例的**,
+那么对于Spring托管的单例Service Bean,如何保证其安全呢?
+Spring使用ThreadLocal解决线程安全问题(为每一个线程都提供了一份变量,因此可以同时访问而互不影响)。
+Spring的单例是**基于BeanFactory**也就是Spring容器的,单例Bean在此容器内只有一个,
+**Java的单例是基于 JVM,每个 JVM 内只有一个实例**。
+
+## Bean的作用域
+
+Spring Framework支持五种作用域:
+
+| 类别 | 说明 |
+| :--:| :--: |
+| singleton | 在SpringIOC容器中仅存在一个Bean实例,Bean以单例方式存在 |
+| prototype | 每次从容器中调用Bean时,都返回一个新的实例 |
+| request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
+| session | 同一个Http Session共享一个Bean,不同Session使用不同Bean,仅适用于WebApplicationContext环境 |
+| globalSession | 一般同于Portlet应用环境,该作用域仅适用于WebApplicationContext环境 |
+
+注意:五种作用域中,
+request、session 和 global session
+三种作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),
+只能用在基于 web 的 Spring ApplicationContext 环境。
+
+### 1. singleton
+
+当一个 Bean 的作用域为 singleton,那么Spring IoC容器中只会存在一个**共享的 Bean 实例**,
+并且所有对 Bean 的请求,只要 id 与该 Bean 定义相匹配,则只会**返回 Bean 的同一实例**。
+
+singleton 是单例类型(对应于单例模式),就是**在创建容器时就同时自动创建一个Bean对象**,
+不管你是否使用,但我们可以指定Bean节点的 lazy-init="true" 来延迟初始化Bean,
+这时候,只有在第一次获取Bean时才会初始化Bean,即第一次请求该bean时才初始化。 每次获取到的对象都是同一个对象。
+注意,singleton 作用域是Spring中的**缺省作用域**。
+
+- 配置文件XML中将 Bean 定义成 singleton :
+```html
+
+```
+
+- @Scope 注解的方式:
+
+```java
+@Service
+@Scope("singleton")
+public class ServiceImpl{
+
+}
+```
+
+#### 2. prototype
+
+当一个Bean的作用域为 prototype,表示一个 Bean 定义对应多个对象实例。
+prototype 作用域的 Bean 会导致在每次对该 Bean 请求
+(将其注入到另一个 Bean 中,或者以程序的方式调用容器的 getBean() 方法)时都会创建一个新的 bean 实例。
+prototype 是原型类型,它在我们创建容器的时候并没有实例化,
+而是当我们获取Bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。
+
+根据经验,**对有状态的 Bean 应该使用 prototype 作用域,而对无状态的 Bean 则应该使用 singleton 作用域。**
+
+- 配置文件XML中将 Bean 定义成 prototype :
+
+```html
+
+```
+或者
+
+```html
+
+```
+
+- @Scope 注解的方式:
+
+```java
+@Service
+@Scope("prototype")
+public class ServiceImpl{
+
+}
+```
+
+### 3. request
+
+request只适用于**Web程序**,每一次 HTTP 请求都会产生一个新的 Bean ,
+同时该 Bean 仅在当前HTTP request内有效,当请求结束后,该对象的生命周期即告结束。
+
+在 XML 中将 bean 定义成 request ,可以这样配置:
+
+- 配置文件XML中将 Bean 定义成 prototype :
+
+```html
+
+```
+
+
+### 4. session
+
+session只适用于**Web程序**,
+session 作用域表示该针对每一次 HTTP 请求都会产生一个新的 Bean,
+同时**该 Bean 仅在当前 HTTP session 内有效**。
+与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,
+而别的 HTTP session 中根据 userPreferences 创建的实例,
+将不会看到这些特定于某个 HTTP session 的状态变化。
+当HTTP session最终被废弃的时候,在该HTTP session作用域内的bean也会被废弃掉。
+
+```html
+
+```
+
+### 5. globalSession
+
+globalSession 作用域**类似于标准的 HTTP session** 作用域,
+不过仅仅在基于 portlet 的 Web 应用中才有意义。
+Portlet 规范定义了全局 Session 的概念,
+它被所有构成某个 portlet web 应用的各种不同的 portlet所共享。
+在globalSession 作用域中定义的 bean 被限定于全局portlet Session的生命周期范围内。
+
+```html
+
+```
+
+## Bean的生命周期
+
+Spring容器在创建、初始化和销毁Bean的过程中做了哪些事情:
+
+```java
+Spring容器初始化
+=====================================
+调用GiraffeService无参构造函数
+GiraffeService中利用set方法设置属性值
+调用setBeanName:: Bean Name defined in context=giraffeService
+调用setBeanClassLoader,ClassLoader Name = sun.misc.Launcher$AppClassLoader
+调用setBeanFactory,setBeanFactory:: giraffe bean singleton=true
+调用setEnvironment
+调用setResourceLoader:: Resource File Name=spring-beans.xml
+调用setApplicationEventPublisher
+调用setApplicationContext:: Bean Definition Names=[giraffeService, org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#0, com.giraffe.spring.service.GiraffeServicePostProcessor#0]
+执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=giraffeService
+调用PostConstruct注解标注的方法
+执行InitializingBean接口的afterPropertiesSet方法
+执行配置的init-method
+执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=giraffeService
+Spring容器初始化完毕
+=====================================
+从容器中获取Bean
+giraffe Name=张三
+=====================================
+调用preDestroy注解标注的方法
+执行DisposableBean接口的destroy方法
+执行配置的destroy-method
+Spring容器关闭
+```
+
+### initialize和destroy
+
+有时我们需要在Bean属性值设置好之后和Bean销毁之前做一些事情,
+比如检查Bean中某个属性是否被正常的设置好了。
+Spring框架提供了多种方法让我们可以在Spring Bean的生命周期中执行initialization和pre-destroy方法。
+
+> InitializingBean 和 DisposableBean 接口
+
+- InitializingBean 接口 :
+```java
+public interface InitializingBean {
+ void afterPropertiesSet() throws Exception;
+}
+```
+
+- DisposableBean 接口 :
+```java
+public interface DisposableBean {
+ void destroy() throws Exception;
+}
+```
+
+实现 InitializingBean 接口的afterPropertiesSet()方法可以在**Bean属性值设置好之后做一些操作**,
+实现DisposableBean接口的destroy()方法可以在**销毁Bean之前做一些操作**。
+
+```java
+public class GiraffeService implements InitializingBean,DisposableBean {
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ System.out.println("执行InitializingBean接口的afterPropertiesSet方法");
+ }
+ @Override
+ public void destroy() throws Exception {
+ System.out.println("执行DisposableBean接口的destroy方法");
+ }
+}
+```
+这种方法比较简单,但是不建议使用。
+因为这样会**将Bean的实现和Spring框架耦合在一起**。
+
+> init-method 和 destroy-method 方法
+
+配置文件中的配值init-method 和 destroy-method :
+
+```html
+
+
+```
+
+```java
+public class GiraffeService {
+ //通过的destroy-method属性指定的销毁方法
+ public void destroyMethod() throws Exception {
+ System.out.println("执行配置的destroy-method");
+ }
+ //通过的init-method属性指定的初始化方法
+ public void initMethod() throws Exception {
+ System.out.println("执行配置的init-method");
+ }
+}
+```
+
+需要注意的是自定义的init-method和post-method方法**可以抛异常但是不能有参数**。
+
+这种方式比较推荐,因为可以**自己创建方法,无需将Bean的实现直接依赖于Spring的框架**。
+
+> @PostConstruct 和 @PreDestroy注解
+
+Spring 支持用 @PostConstruct和 @PreDestroy注解来指定 init 和 destroy 方法。
+这两个注解均在javax.annotation 包中。
+为了注解可以生效,
+需要在配置文件中定义
+org.springframework.context.annotation.CommonAnnotationBeanPostProcessor。
+
+```html
+
+```
+
+```java
+public class GiraffeService {
+ @PostConstruct
+ public void initPostConstruct(){
+ System.out.println("执行PostConstruct注解标注的方法");
+ }
+ @PreDestroy
+ public void preDestroy(){
+ System.out.println("执行preDestroy注解标注的方法");
+ }
+}
+```
+
+### XxxAware接口
+
+有些时候我们需要在 Bean 的初始化中**使用 Spring 框架自身的一些对象**来执行一些操作,
+比如获取 ServletContext 的一些参数,获取 ApplicaitionContext 中的 BeanDefinition 的名字,获取 Bean 在容器中的名字等等。
+为了让 Bean 可以获取到框架自身的一些对象,Spring 提供了一组名为 XxxAware 的接口。
+
+XxxAware接口均继承于org.springframework.beans.factory.Aware标记接口,
+并提供一个将由 Bean 实现的set*方法,
+Spring通过基于setter的依赖注入方式使相应的对象可以被Bean使用。
+
+常见的 XxxAware 接口:
+
+| 接口 | 说明 |
+| :--: | :--: |
+| ApplicationContextAware | 获得ApplicationContext对象,可以用来获取所有BeanDefinition的名字。 |
+| BeanFactoryAware | 获得BeanFactory对象,可以用来检测Bean的作用域。 |
+| BeanNameAware | 获得Bean在配置文件中定义的name。 |
+| ResourceLoaderAware | 获得ResourceLoader对象,可以获得classpath中某个文件。 |
+| ServletContextAware | 在一个MVC应用中可以获取ServletContext对象,可以读取context中的参数。 |
+| ServletConfigAware | 在一个MVC应用中可以获取ServletConfig对象,可以读取config中的参数。 |
+
+```java
+public class GiraffeService implements ApplicationContextAware,
+ ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware,
+ BeanNameAware, EnvironmentAware, ImportAware, ResourceLoaderAware{
+
+ @Override
+ public void setBeanClassLoader(ClassLoader classLoader) {
+ System.out.println("执行setBeanClassLoader,ClassLoader Name = " + classLoader.getClass().getName());
+ }
+
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ System.out.println("执行setBeanFactory,setBeanFactory:: giraffe bean singleton=" + beanFactory.isSingleton("giraffeService"));
+ }
+
+ @Override
+ public void setBeanName(String s) {
+ System.out.println("执行setBeanName:: Bean Name defined in context="
+ + s);
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ System.out.println("执行setApplicationContext:: Bean Definition Names="
+ + Arrays.toString(applicationContext.getBeanDefinitionNames()));
+ }
+
+ @Override
+ public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
+ System.out.println("执行setApplicationEventPublisher");
+ }
+
+ @Override
+ public void setEnvironment(Environment environment) {
+ System.out.println("执行setEnvironment");
+ }
+
+ @Override
+ public void setResourceLoader(ResourceLoader resourceLoader) {
+ Resource resource = resourceLoader.getResource("classpath:spring-beans.xml");
+ System.out.println("执行setResourceLoader:: Resource File Name="
+ + resource.getFilename());
+ }
+
+ @Override
+ public void setImportMetadata(AnnotationMetadata annotationMetadata) {
+ System.out.println("执行setImportMetadata");
+ }
+}
+```
+
+### BeanPostProcessor
+上面的XxxAware接口是**针对某个实现这些接口的Bean定制初始化的过程**,
+Spring同样可以针对容器中的所有Bean,或者某些Bean定制初始化过程,
+只需提供一个实现BeanPostProcessor接口的类即可。
+
+- BeanPostProcessor 接口:
+```java
+public interface BeanPostProcessor{
+ //postProcessBeforeInitialization方法会在容器中的Bean初始化之前执行
+ public abstract Object postProcessBeforeInitialization(Object obj, String s)
+ throws BeansException;
+
+ //postProcessAfterInitialization方法在容器中的Bean初始化之后执行
+ public abstract Object postProcessAfterInitialization(Object obj, String s)
+ throws BeansException;
+}
+```
+
+```java
+public class CustomerBeanPostProcessor implements BeanPostProcessor {
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName)
+ throws BeansException {
+ System.out.println("执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName="
+ + beanName);
+ return bean;
+ }
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName)
+ throws BeansException {
+ System.out.println("执行BeanPostProcessor的postProcessAfterInitialization方法,beanName="
+ + beanName);
+ return bean;
+ }
+}
+```
+
+将实现BeanPostProcessor接口的Bean像其他Bean一样定义在配置文件中:
+
+```html
+
+```
+
+### 总结
+Spring Bean的生命周期
+
+1. Bean容器找到配置文件中 Spring Bean 的定义。
+
+2. Bean容器利用Java Reflection API创建一个Bean的实例。
+
+3. 如果涉及到一些属性值,利用set方法设置一些属性值。
+
+4. 如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。
+
+5. 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
+
+6. 如果Bean实现了BeanFactoryAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
+
+7. 与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。
+
+8. 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,
+执行postProcessBeforeInitialization()方法
+
+9. 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
+
+10. 如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。
+
+11. 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,
+执行postProcessAfterInitialization()方法
+
+12. 当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法;
+如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。
+
+
+
+
+
+很多时候我们并不会真的去实现上面说描述的那些接口,
+那么下面我们就除去那些接口,针对 Bean 的单例和非单例来描述下 Bean 的生命周期:
+
+> **1. 单例管理的对象**
+
+scope="singleton",即默认情况下,会在启动容器时(即实例化容器时)时实例化。
+但我们可以指定 lazy-init="true"。这时候,只有在第一次获取 Bean 时才会初始化 Bean,
+即第一次请求该 Bean时才初始化。配置如下:
+
+```html
+
+```
+
+想对所有的默认单例Bean都应用延迟初始化,
+可以在根节点beans 指定default-lazy-init="true",如下所示:
+
+```html
+
+```
+
+默认情况下,Spring 在读取 xml 文件的时候,就会创建对象。
+在创建对象的时候先调用**构造器**,然后调用 **init-method 属性值中所指定的方法**。
+对象在被销毁的时候,会调用 **destroy-method 属性值中所指定的方法**。
+
+- LifeCycleBean :
+```java
+public class LifeCycleBean {
+ private String name;
+
+ public LifeCycleBean(){
+ System.out.println("LifeCycleBean()构造函数");
+ }
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ System.out.println("setName()");
+ this.name = name;
+ }
+
+ public void init(){
+ System.out.println("this is init of lifeBean");
+ }
+
+ public void destroy(){
+ System.out.println("this is destory of lifeBean " + this);
+ }
+}
+```
+
+- 配置文件 :
+```html
+
+```
+
+- 测试 :
+
+```java
+public class LifeTest {
+ @Test
+ public void test() {
+ AbstractApplicationContext context =
+ new ClassPathXmlApplicationContext("lifeCycleBean.xml");
+ LifeCycleBean life = (LifeCycleBean) context.getBean("lifeCycleBean");
+ System.out.println("life:"+life);
+ context.close();
+ }
+}
+```
+
+- 输出结果:
+
+```html
+LifeBean()构造函数
+this is init of lifeBean
+life:com.southeast.bean.LifeCycleBean@573f2bb1
+this is destory of lifeBean com.southeast.bean.LifeCycleBean@573f2bb1
+```
+
+> **2. 非单例管理的对象**
+
+当 scope= "prototype" 时,容器也会延迟初始化 Bean,
+Spring 读取xml 文件的时候,并不会立刻创建对象,而是在第一次请求该 Bean 时才初始化(如调用getBean方法时)。
+
+在第一次请求每一个 prototype 的 Bean 时,Spring容器都会调用其构造器创建这个对象,
+然后调用init-method属性值中所指定的方法。
+对象销毁的时候,Spring 容器不会帮我们调用任何方法,因为是非单例,
+这个类型的对象有很多个,**Spring容器一旦把这个对象交给你之后,就不再管理这个对象了**。
+
+- 配置文件 :
+```html
+
+```
+
+- 测试:
+
+```java
+public class LifeTest2 {
+ @Test
+ public void test() {
+ AbstractApplicationContext context =
+ new ClassPathXmlApplicationContext("lifeCycleBean.xml");
+ LifeCycleBean life = (LifeCycleBean) context.getBean("lifeCycleBean");
+ System.out.println("life:"+life);
+
+ LifeCycleBean life2 = (LifeCycleBean) context.getBean("lifeCycleBeans");
+ System.out.println("life2:"+life2);
+ context.close();
+ }
+}
+```
+
+- 输出结果:
+```html
+LifeBean()构造函数
+this is init of lifeBean
+life:com.southeast.bean.LifeCycleBean@573f2bb1
+LifeBean()构造函数
+this is init of lifeBean
+life2:com.southeast.bean.LifeCycleBean@5ae9a829
+this is destory of lifeBean LifeCycleBean@573f2bb1
+```
+
+作用域为 prototype 的 Bean ,其destroy方法并没有被调用。
+如果 bean 的 scope 设为prototype时,当容器关闭时,destroy 方法不会被调用。
+对于 prototype 作用域的 bean,有一点非常重要,
+
+**Spring 容器可以管理 singleton 作用域下 Bean 的生命周期,
+在此作用域下,Spring 能够精确地知道 Bean 何时被创建,何时初始化完成,以及何时被销毁。
+而对于 prototype 作用域的bean,Spring只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给了客户端的代码管理,
+Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的Bean的生命周期**。
\ No newline at end of file
diff --git "a/docs/Spring/Spring 344円270円255円346円266円211円345円217円212円345円210円260円347円232円204円350円256円276円350円256円241円346円250円241円345円274円217円.md" "b/docs/Spring/Spring 344円270円255円346円266円211円345円217円212円345円210円260円347円232円204円350円256円276円350円256円241円346円250円241円345円274円217円.md"
new file mode 100644
index 0000000..54b0777
--- /dev/null
+++ "b/docs/Spring/Spring 344円270円255円346円266円211円345円217円212円345円210円260円347円232円204円350円256円276円350円256円241円346円250円241円345円274円217円.md"
@@ -0,0 +1,29 @@
+# Spring 中涉及到的设计模式
+
+## [工厂模式](https://duhouan.github.io/Java-Notes/#/./OO/01创建型?id=_2-简单工厂(simple-factory))
+
+BeanFactory 用来创建各种不同的 Bean。
+
+## [单例模式](https://duhouan.github.io/Java-Notes/#/./OO/01创建型?id=_1-单例(singleton))
+
+在 Spring 配置文件中定义的 Bean 默认为单利模式。
+
+## [模板方法模式](https://duhouan.github.io/Java-Notes/#/./OO/02行为型?id=_10-模板方法(template-method))
+
+用来解决代码重复的问题。比如 JdbcTempolate。
+
+## [代理模式](https://duhouan.github.io/Java-Notes/#/./OO/03结构型?id=_7-代理(proxy))
+
+AOP、事务都大量运用了代理模式。
+
+## [原型模式](https://duhouan.github.io/Java-Notes/#/./OO/01创建型?id=_6-原型模式(prototype))
+
+特点在于通过"复制"一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的"原型",这个原型是可定制的。
+
+## [责任链模式](https://duhouan.github.io/Java-Notes/#/./OO/02行为型?id=_1-责任链(chain-of-responsibility))
+
+在 SpringMVC 中,会经常使用一些拦截器(HandlerInterceptor),当存在多个拦截器的时候,所有的拦截器就构成了一条拦截器链。
+
+## [观察者模式](https://duhouan.github.io/Java-Notes/#/./OO/02行为型?id=_7-观察者(observer))
+
+Spring 中提供了一种监听机制,即 ApplicationListenber,可以实现 Spring 容器内的事件监听。
\ No newline at end of file
diff --git a/docs/Spring/SpringIOC.md b/docs/Spring/SpringIOC.md
new file mode 100644
index 0000000..93eaaa4
--- /dev/null
+++ b/docs/Spring/SpringIOC.md
@@ -0,0 +1,463 @@
+# 二、Spring IOC
+
+## IoC 与 DI
+
+### IoC 概念
+
+控制反转(Iversion of Control,IoC)是一种**设计思想**。**将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理**。涉及到 2 个方面:
+
+- **控制**
+
+ 控制对象的创建及销毁(生命周期)。
+
+- **反转**
+
+ 将对象的控制权交给 IoC 容器。
+
+所有的类都会在 Spring 容器中注册,告诉 Spring 你是个什么东西,你需要什么东西,然后 Spring 会在系统运行到适当的时候,把你需要的东西主动给你
+
+**所有类的创建、销毁都由 Spring 来控制,也就是说控制对象生命周期的不是引用它的对象,而是 Spring**。对于某个具体对象而言,以前是它控制其他对象,现在所有对象都被 Spring 控制。
+
+### DI 概念
+
+依赖注入(Dependency Injection,DI) 。依赖注入举例:设计行李箱。
+
+
+
+相应的代码如下:
+
+
+
+size 固定值,改进后的代码:
+
+
+
+使用 DI 方式进行改进:
+
+
+
+改进后相应的代码如下:
+
+
+
+不难理解,依赖注入就是**将底层类作为参数传递给上层类,实现上层对下层的控制**,**依赖注入实现控制反转**。
+
+### IoC 和 DI 关系
+
+IoC 是在系统运行中,**动态的向某个对象提供它所需要的其他对象**,通过 DI 来实现。
+
+- IoC 和 DI 的关系如下图:
+
+
+
+- 依赖倒置原则、IoC、DI 和 IoC 容器的关系:
+
+
+
+## IoC 容器
+
+IoC 容器功能:
+
+- 管理 Bean 的生命周期
+- 控制 Bean 依赖注入
+
+使用 IoC 容器的好处:
+
+- 避免在各处使用 new 来创建类,并且可以做到统一维护
+
+- 创建实例的时候不需要了解其中的细节
+
+ 依赖注入方式获取实例:
+
+
+
+ 使用 IoC 容器获取获取实例(创建实例的时候不需要了解其中的细节):
+
+
+
+
+
+## XML 配置文件解析
+
+### BeanDefinition
+
+BeanDefinition 是一个接口。在 Spring 中存在 3 种实现:
+
+- RootBeanDefinition
+- ChildBeanDefinition
+- GenericBeanDefinition
+
+三种实现都继承了 AbstractBeanDefinition。**AbstractBeanDefinition 是对上述的共同的类信息进行抽象**。
+
+```java
+public abstract class AbstractBeanDefinition extends
+ BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable {
+ //...
+}
+```
+
+**BeanDefinition 是配置文件中 ``元素标签在容器中的内部表现形式**。
+
+在配置文件中,可以定义父`` 和子``,父`` 用 RootBeanDefinition 表示,而子``用ChildBeanDefinition 表示,而没有父`` 的 `` 就使用 RootBeanDefinition 表示。
+
+### BeanDefinitionRegistry
+
+Spring 通过 BeanDefinition 将配置文件中的``配置信息转换为容器的内部表示,并且将这些 BeanDefinition 注册 **BeanDefinitionRegistry** 中。
+
+Spring 容器的 BeanDefinitionRegistry 就是配置信息的 内存数据库,主要是以 map 的形式保存数据,后续操作直接从 BeanDefinitionRegistry 中读取配置信息。
+
+### Resource
+
+Spring 采用 Resource 来对各种资源进行统一抽象。
+
+Resource 是一个接口,定义了资源的基本操作,包括是否存在、是否可读、是否已经打开等等。
+
+Resource 继承了 InputStreamSource。
+
+```java
+public interface Resource extends InputStreamSource {
+ //...
+}
+```
+
+InputStreamSource 封装任何可以返回 InputStream 的类。
+
+```java
+/*
+* InputStreamSource 封装任何可以返回 InputStream 的类。
+*/
+public interface InputStreamSource {
+ /*
+ * 用于返回一个新的 InputStream 对象
+ */
+ InputStream getInputStream() throws IOException;
+}
+```
+
+对于不同来源的资源文件都有相应的 Resource 实现:
+
+| 资源来源 | Resource 实现类 |
+| :--------------: | :-----------------: |
+| 文件 | FileSystemResource |
+| classpath 资源 | ClassPathResource |
+| URL 资源 | UrlResource |
+| InputStream 资源 | InputStreamResource |
+| Byte 数组 | ByteArrayResource |
+
+### 解析过程
+
+分为 2 大步骤:
+
+**1、配置文件的封装**
+
+Resource 接口**抽象了所有 Spring 内部使用到的底层资源**:File、URL、ClassPath 等。
+
+**2、加载 Bean**
+
+- 封装资源文件。XMLBeanDefinitionReader 对参数 Resource 使用 EncodeResource 类进行封装。
+- 获取输入流。从 Resource 中获取对应的 InputStream 并构造 InputSource。
+- 通过构造的 InputSource 实例和 Resource 实例继续调用函数 `doLoadBeanDefinition`。
+ * 获取 XML 文件的实体解析器和验证模式(常见的验证模式有 DTD 和 XSD 两种)
+ * 加载 XML 文件,并得到对应的 Document
+ * 根据返回的 Document 解析并注册 BeanDefinition
+
+## Spring 的 IoC 容器
+
+
+
+IoC 容器其实就是一个大工厂,它用来管理我们所有的对象以及依赖关系:
+
+- 根据 Bean 配置信息在容器内部创建 Bean 定义注册表
+- 根据注册表加载,实例化 Bean,**建立 Bean 与 Bean 之间的依赖关系**
+- 将 Bean 实例放入 Spring IoC 容器中,等待应用程序调用
+
+### Spring IoC 支持的功能
+
+- 依赖注入
+- 依赖检查
+- 自动装配属性
+- 可指定初始化方法和销毁方法
+
+### Spring 的 2 种 IoC 容器
+
+- **BeanFactory**
+ - IoC 容器要实现的最基础的接口
+ - 采用**延迟初始化策略**(容器初始化完成后并不会创建 Bean 对象,只有当收到初始化请求时才进行初始化)
+ - 由于是延迟初始化策略,因此启动速度较快,占用资源较少
+
+- **ApplicationConext**
+ - 在 BeanFactory 基础上,增加了更为高级的特性:事件发布、国际化等。
+ - 在容器启动时,完成所有 Bean 的创建
+ - 启动时间较长,占用资源较多
+
+### IoC 容器的初始化过程
+
+Spring IoC 容器的初始化过程分为 3 个阶段:Resource 定位、BeanDefinition 的载入和向 IoC 容器注册 BeanDefinition。Spring 将这 3 个阶段分离,并使用不同的模块来完成,这样可以让用户更加灵活的对这 3 个阶段进行扩展。
+
+
+
+1、**Resource 定位**
+
+Resource 定位指的是 BeanDefinition 的资源定位,它由 ResourceLoader 通过统一的 Resource 接口来完成,Resource 为各种形式的 BeanDefinition 的使用都提供了统一的接口。
+
+2、**BeanDefinition 的载入**
+
+BeanDefinition 实际上就是 POJO 对象在 IoC 容器中的抽象,通过 BeanDefiniton,IoC 容器可以方便对 POJO 对象进行管理。
+
+3、 **向 IoC 容器注册 BeanDefinition**
+
+向 IoC 容器注册 BeanDefinition 是通过调用 BeanDefinitionRegistry 接口来的实现来完成的,这个注册过程是把载入的 BeanDefinition 向 IoC 容器进行注册。实际上 IoC 容器内部维护这一个 HashMap,而这个注册过程其实就是将 BeanDefinition 添加至该 HashMap 中。
+
+> 注意:BeanFactory 和 FactoryBean 的区别
+
+BeanFactory 是 IoC 最基本的容器,负责生产和管理 Bean,为其他具体的 IoC 容器提供了最基本的规范。
+
+FactoryBean 是一个 Bean,是一个接口,当 IoC 容器中的 Bean 实现了 FactoryBean 后,
+
+通过 getBean(String beanName) 获取到的 Bean 对象并不是 FactoryBean 的实现类对象,而是这个实现类中的 getObject() 方法返回的对象。
+
+要想获取 FactoryBean 的实现类对象,就是在 beanName 前面加上 "&"。
+
+## Spring Bean 加载流程
+
+AbstractBeanFactory 中 `getBean`最终调用 `doGetBean`方法。
+
+```java
+protected T doGetBean(String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
+ String beanName = this.transformedBeanName(name);
+ Object sharedInstance = this.getSingleton(beanName);
+ Object bean;
+ if (sharedInstance != null && args == null) {
+ if (this.logger.isTraceEnabled()) {
+ if (this.isSingletonCurrentlyInCreation(beanName)) {
+ this.logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
+ } else {
+ this.logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
+ }
+ }
+
+ bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
+ } else {
+ if (this.isPrototypeCurrentlyInCreation(beanName)) {
+ throw new BeanCurrentlyInCreationException(beanName);
+ }
+
+ BeanFactory parentBeanFactory = this.getParentBeanFactory();
+ if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
+ String nameToLookup = this.originalBeanName(name);
+ if (parentBeanFactory instanceof AbstractBeanFactory) {
+ return ((AbstractBeanFactory)parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
+ }
+
+ if (args != null) {
+ return parentBeanFactory.getBean(nameToLookup, args);
+ }
+
+ if (requiredType != null) {
+ return parentBeanFactory.getBean(nameToLookup, requiredType);
+ }
+
+ return parentBeanFactory.getBean(nameToLookup);
+ }
+
+ if (!typeCheckOnly) {
+ this.markBeanAsCreated(beanName);
+ }
+
+ try {
+ RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
+ this.checkMergedBeanDefinition(mbd, beanName, args);
+ String[] dependsOn = mbd.getDependsOn();
+ String[] var11;
+ if (dependsOn != null) {
+ var11 = dependsOn;
+ int var12 = dependsOn.length;
+
+ for(int var13 = 0; var13 < var12; ++var13) { + String dep = var11[var13]; + if (this.isDependent(beanName, dep)) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); + } + + this.registerDependentBean(dep, beanName); + + try { + this.getBean(dep); + } catch (NoSuchBeanDefinitionException var24) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", var24); + } + } + } + + if (mbd.isSingleton()) { + sharedInstance = this.getSingleton(beanName, () -> {
+ try {
+ return this.createBean(beanName, mbd, args);
+ } catch (BeansException var5) {
+ this.destroySingleton(beanName);
+ throw var5;
+ }
+ });
+ bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
+ } else if (mbd.isPrototype()) {
+ var11 = null;
+
+ Object prototypeInstance;
+ try {
+ this.beforePrototypeCreation(beanName);
+ prototypeInstance = this.createBean(beanName, mbd, args);
+ } finally {
+ this.afterPrototypeCreation(beanName);
+ }
+
+ bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
+ } else {
+ String scopeName = mbd.getScope();
+ Scope scope = (Scope)this.scopes.get(scopeName);
+ if (scope == null) {
+ throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
+ }
+
+ try {
+ Object scopedInstance = scope.get(beanName, () -> {
+ this.beforePrototypeCreation(beanName);
+
+ Object var4;
+ try {
+ var4 = this.createBean(beanName, mbd, args);
+ } finally {
+ this.afterPrototypeCreation(beanName);
+ }
+
+ return var4;
+ });
+ bean = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
+ } catch (IllegalStateException var23) {
+ throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", var23);
+ }
+ }
+ } catch (BeansException var26) {
+ this.cleanupAfterBeanCreationFailure(beanName);
+ throw var26;
+ }
+ }
+
+ if (requiredType != null && !requiredType.isInstance(bean)) {
+ try {
+ T convertedBean = this.getTypeConverter().convertIfNecessary(bean, requiredType);
+ if (convertedBean == null) {
+ throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
+ } else {
+ return convertedBean;
+ }
+ } catch (TypeMismatchException var25) {
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", var25);
+ }
+
+ throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
+ }
+ } else {
+ return bean;
+ }
+}
+```
+
+doGetBean 大致流程如下:
+
+**1、获取参数 name 对应的真正 beanName**
+
+**2、尝试从缓存中加载单例**
+
+单例在 Spring 的同一个容器中只会被创建一次,后续再获取 Bean,就直接从**单例缓存**中获取。
+
+这里只是尝试加载,首先尝试从**缓存**中加载,如果加载不成功则再次尝试从 **singletonFactories** 中加载。
+
+在创建依赖的时候为了避免循环依赖,在 Spring 中创建 Bean 的原则:**不等 bean 创建完成就会将创建 Bean 的 ObjectFactory 提早曝光加入到缓存中,一旦下一个 Bean 创建时需要依赖上一个 Bean 则直接使用 ObjectFactory**。
+
+**3、Bean 的实例化**
+
+如果从缓存中得到了 Bean 的原始状态,则需要对 Bean 进行实例化。这里有必要强调一下,缓存中记录的是原始的 Bean 状态,并一定是我们最终想要的 Bean。
+
+**4、原型模式的依赖检查**
+
+只有在**单例情况下**才会尝试解决循环依赖。
+
+如果存在 A 中有 B 的属性,B 中有 A 的属性,那么当依赖注入的时 候,就会产生当 A 还未创建完的时候因为对于 B 的创建再次返回创建 A ,造成循环依赖,即 isPrototypeCurrentlyInCreation(String beanName) 判断 true。
+
+**5、检测 parentBeanFactory**
+
+如果缓存没有数据的话,直接转到 parentBeanFactory 去加载,条件是:
+
+```java
+parentBeanFactory != null && !this.containsBeanDefinition(beanName)
+```
+
+containsBeanDefinition 方法是检测如果当前 XML 中没有配置对应的 beanName,只能到 parentBeanfactory 中尝试一下。
+
+**6、将 GenericBeanDefinition 对象转换为 RootBeanDefiniion 对象**
+
+从 XML 配置文件中读取 Bean 信息会放到 GenericBeanDefinition 中, 但是所有的 Bean 后续处理都是针对 RootBeanDefinition 的。
+
+这里做一下转换,转换的同时如果父类 Bean 不为空的话,则会进一步合并父类的属性。
+
+**7、初始化依赖的 Bean**
+
+Spring 在初始化某一个 Bean 的时候首先初始化这个 Bean 的依赖。
+
+**8、依据当前 bean 的作用域对 bean 进行实例化**
+
+Spring 中可以指定各种 scope,默认是 singleton 。
+
+还有其他的配置比如 prototype、request 等,Spring 会根据不同的配置进行不同的初始化策略。
+
+**9、类型转换**
+
+将返回的 Bean 转化为 requiredType 所指定的类型。
+
+**10、返回 Bean**
+
+## Spring 解决循环依赖
+
+循环依赖就是循环引用,就是两个或者多个 Bean 之间相互持有对方,比如 CircleA 引用 CircleB,CircleB 引用 CircleC,CircleC 引用 CircleA,则它们最终反映为一个环。
+
+### 构造器循环依赖
+
+表示通过构造器注入构成的循环依赖。此依赖是无法解决的,只能抛出 BeanConcurrentlyCreationException 异常表示循环依赖。
+
+### setter 循环依赖
+
+表示通过 setter 注入构成的循环依赖。 解决该依赖问题是通过 Spring 容器提前暴露刚完成构造器注入但未完成其他步骤(如 setter 注入) 的 Bean 来完成的,而且只能解决单例作用域的 Bean 的循环依赖。
+
+对于 `scope="prototype"`的 Bean,Spring 容器无法完成依赖注入,因为 "prototype" 作用域 Bean,Spring 容器不进行缓存,因此无法提前暴露一个创建中的 Bean。对于 "singleton" 作用域 Bean,可以通过setAllowCircleReferences(false) 来禁用循环引用。
+
+对于单例对象来说,在 Spring 整个容器的生命周期内,有且只有一个对象,很容易想到这个对象应该存在于**缓存**中,Spring 在解决循环依赖问题的过程中就使用了"三级缓存":
+
+- **singletonObjects **
+
+ 用于保存 beanName 和创建 Bean 实例之间的关系,缓存的是 **Bean 实例**。
+
+- **singletonFactories**
+
+ 用于保存 beanName 和创建 Bean 的工厂之间的关系,缓存的是为解决循环依赖而准备的 **ObjectFactory**。
+
+- **earlySingletonObjects**
+
+ 用于保存 beanName 和创建 Bean 实例之间的关系,与 singletonObjects 的不同之处在于,当一个单例 Bean 被放到这里面后,当 Bean 还在创建过程中,就可以通过 getBean 方法获得,其目的是检查循环引用。这边缓存的也是实例,只是这边的是为解决循环依赖而提早暴露出来的实例,其实就是 ObjectFactory。
+
+首先尝试从 singletonObjects 中获取实例,如果获取不到,再从 earlySingletonObjects 中获取,如果还获取不到,再尝试从 singletonFactories 中获取 beanName 对应的 ObjectFactory,然后调用 getObject 来创建 Bean,并放到 earlySingletonObjects 中去,并且从 singletonFactories 中 remove 掉这个 ObjectFactory。
+
+> 注意:singleObjects 和 earlySingletonObjects
+
+- 两者都是以 beanName 为key,Bean 实例为 value 进行存储
+
+- 两者得出区别在于 singletonObjects 存储的是实例化的完成的 Bean 实例,而 earlySingletonObjects 存储的是正在实例化中的 Bean,所以两个集合的内容是互斥的。
+
+
+# 参考资料
+
+- [Spring 框架小白的蜕变](https://www.imooc.com/learn/1108)
+
+- [Spring IOC知识点一网打尽!](https://segmentfault.com/a/1190000014979704)
+- [Spring核心思想,IoC与DI详解(如果还不明白,放弃java吧)](https://blog.csdn.net/Baple/article/details/53667767)
+- [15个经典的Spring面试常见问题](https://mp.weixin.qq.com/s/uVoeXRLNEMK8c00u3tm9KA)
\ No newline at end of file
From eaf2f436fafe225b7ae8851f17ce98832ab6c6e8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=9C=E5=8E=9A=E5=AE=89?=
<33805265+duhouan@users.noreply.github.com>
Date: 2019年6月28日 13:36:30 +0800
Subject: [PATCH 05/17] =?UTF-8?q?Update=20=E5=BC=80=E5=8F=91=E6=A1=86?=
=?UTF-8?q?=E6=9E=B6-=E7=9B=AE=E5=BD=95.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...7221円346円241円206円346円236円266円-347円233円256円345円275円225円.md" | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git "a/docs/345円274円200円345円217円221円346円241円206円346円236円266円-347円233円256円345円275円225円.md" "b/docs/345円274円200円345円217円221円346円241円206円346円236円266円-347円233円256円345円275円225円.md"
index 87d8c53..1bc1f6e 100644
--- "a/docs/345円274円200円345円217円221円346円241円206円346円236円266円-347円233円256円345円275円225円.md"
+++ "b/docs/345円274円200円345円217円221円346円241円206円346円236円266円-347円233円256円345円275円225円.md"
@@ -3,13 +3,11 @@
### Spring
- [Spring概述](./Spring/00Spring概述.md)
-- [SpringIOC](./Spring/01SpringIOC.md)
+- [SpringIOC](./Spring/SpringIOC.md)
- [SpringAOP](./Spring/02SpringAOP.md)
-- [手撕TinySpring](./Spring/03手撕TinySpring.md)
- [Spring事务管理](./Spring/04Spring事务管理.md)
- [Spring中Bean的生命周期](./Spring/05Spring中Bean的生命周期.md)
### SpringMVC
- [SpringMVC](./Spring/06SpringMVC.md)
-- [常见面试题总结](./Spring/07常见面试题总结.md)
\ No newline at end of file
From d423bb63f7cccb983f62faa7a148e3152c6efeab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=9C=E5=8E=9A=E5=AE=89?=
<33805265+duhouan@users.noreply.github.com>
Date: 2019年6月28日 13:37:02 +0800
Subject: [PATCH 06/17] =?UTF-8?q?Update=20=E5=BC=80=E5=8F=91=E6=A1=86?=
=?UTF-8?q?=E6=9E=B6-=E7=9B=AE=E5=BD=95.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...46241円206円346円236円266円-347円233円256円345円275円225円.md" | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git "a/docs/345円274円200円345円217円221円346円241円206円346円236円266円-347円233円256円345円275円225円.md" "b/docs/345円274円200円345円217円221円346円241円206円346円236円266円-347円233円256円345円275円225円.md"
index 1bc1f6e..3ad4caf 100644
--- "a/docs/345円274円200円345円217円221円346円241円206円346円236円266円-347円233円256円345円275円225円.md"
+++ "b/docs/345円274円200円345円217円221円346円241円206円346円236円266円-347円233円256円345円275円225円.md"
@@ -2,11 +2,11 @@
### Spring
-- [Spring概述](./Spring/00Spring概述.md)
-- [SpringIOC](./Spring/SpringIOC.md)
-- [SpringAOP](./Spring/02SpringAOP.md)
-- [Spring事务管理](./Spring/04Spring事务管理.md)
-- [Spring中Bean的生命周期](./Spring/05Spring中Bean的生命周期.md)
+- [Spring 概述](./Spring/00Spring概述.md)
+- [Spring IoC](./Spring/SpringIOC.md)
+- [Spring AOP](./Spring/02SpringAOP.md)
+- [Spring 事务管理](./Spring/04Spring事务管理.md)
+- [Spring 中 Bean 的生命周期](./Spring/05Spring中Bean的生命周期.md)
### SpringMVC
From 7d7729e1ba503340a384c040adb166757268b9b9 Mon Sep 17 00:00:00 2001
From: IvanLu1024 <501275190@qq.com>
Date: 2019年6月28日 20:35:24 +0800
Subject: [PATCH 07/17] Update Kafka.md
---
docs/BigData/Kafka.md | 53 ++++++++++++++++++++++---------------------
1 file changed, 27 insertions(+), 26 deletions(-)
diff --git a/docs/BigData/Kafka.md b/docs/BigData/Kafka.md
index b25a3a5..85a9ba4 100644
--- a/docs/BigData/Kafka.md
+++ b/docs/BigData/Kafka.md
@@ -144,31 +144,7 @@ LEO(Log End Offset)是所有的副本都会有的一个 offset 标记,它
Producers 往 Brokers 中指定的 Topic 写消息,Consumers 从 Brokers 里面拉去指定 Topic 的消息,然后进行业务处理。
图中有两个 Topic,Topic-0 有两个 Partition,Topic-1 有一个 Partition,三副本备份。可以看到 Consumer-Group-1 中的 Consumer-2 没有分到 Partition 处理,这是有可能出现的。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-//===============================================
-
-Kafka 工作流程:
+#### Kafka 工作流程
生产者会根据业务逻辑产生消息,之后根据路由规则将消息发送到指定分区的 Leader 副本所在的 Broker 上。在Kafka 服务端接收到消息后,会将消息追加到 Log 中保存,之后 Follower 副本会与 Leader 副本进行同步,当 ISR集合中所有副本都完成了此消息的同步后,则 Leader 副本的 HW 会增加,并向生产者返回响应。
@@ -283,7 +259,32 @@ Kafka将数据以日志的形式保存在磁盘中。
- 每一个 log 文件中又分为多个 segment。
-## 七、Kafka 的分布式实现
+## 七、Kafka 的高可用
+
+#### 高可用如何实现?
+
+Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机制。每个 partition 的数据都会同步到其它机器上,形成自己的多个 replica 副本。所有 replica 会选举一个 leader 出来,那么生产和消费都跟这个 leader 打交道,然后其他 replica 就是 follower。写的时候,leader 会负责把数据同步到所有 follower 上去,读的时候就直接读 leader 上的数据即可。只能读写 leader?很简单,**要是你可以随意读写每个 follower,那么就要 care 数据一致性的问题**,系统复杂度太高,很容易出问题。Kafka 会均匀地将一个 partition 的所有 replica 分布在不同的机器上,这样才可以提高容错性。
+
+这样就算某个broker宕机,该broker上面的partition也不会丢失,因为其他broker上都有副本,这样Kafka就具有了高可用性。如果这个宕机的 broker 上面有某个 partition 的 leader,那么此时会从 follower 中**重新选举**一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就有所谓的高可用性了。
+
+#### 那么如何选举leader呢?
+
+最简单最直观的方案是,所有Follower都在Zookeeper上设置一个Watch,一旦Leader宕机,其对应的ephemeral znode会自动删除,此时所有Follower都尝试创建该节点,而创建成功者(Zookeeper保证只有一个能创建成功)即是新的Leader,其它Replica即为Follower。
+
+**写数据**的时候,生产者就写 leader,然后 leader 将数据落地写本地磁盘,接着其他 follower 自己主动从 leader 来 pull 数据。一旦所有 follower 同步好数据了,就会发送 ack 给 leader,leader 收到所有 follower 的 ack 之后,就会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为)
+
+**消费**的时候,只会从 leader 去读,但是只有当一个消息已经被所有 follower 都同步成功返回 ack 的时候,这个消息才会被消费者读到。
+
+#### 如何处理所有Replica都不工作?
+
+ 上文提到,在ISR中至少有一个follower时,Kafka可以确保已经commit的数据不丢失,但如果某个Partition的所有Replica都宕机了,就无法保证数据不丢失了。这种情况下有两种可行的方案:
+
+- 等待ISR中的任一个Replica"活"过来,并且选它作为Leader
+- 选择第一个"活"过来的Replica(不一定是ISR中的)作为Leader
+
+ 这就需要在可用性和一致性当中作出一个简单的折衷。如果一定要等待ISR中的Replica"活"过来,那不可用的时间就可能会相对较长。而且如果ISR中的所有Replica都无法"活"过来了,或者数据都丢失了,这个Partition将永远不可用。选择第一个"活"过来的Replica作为Leader,而这个Replica不是ISR中的Replica,那即使它并不保证已经包含了所有已commit的消息,它也会成为Leader而作为consumer的数据源(前文有说明,所有读写都由Leader完成)。Kafka0.8.*使用了第二种方式。根据Kafka的文档,在以后的版本中,Kafka支持用户通过配置选择这两种方式中的一种,从而根据不同的使用场景选择高可用性还是强一致性。
+
+## 八、Kafka 的分布式实现
From 194d3f948c66382558fe094557c39532aea10475 Mon Sep 17 00:00:00 2001
From: DuHouAn <18351926682@163.com>
Date: 2019年6月28日 23:35:44 +0800
Subject: [PATCH 08/17] Java-Notes
---
docs/Spring/02SpringAOP.md | 65 +-
...13345円212円241円347円256円241円347円220円206円.md" | 174 ++--
...37345円221円275円345円221円250円346円234円237円.md" | 200 ++---
docs/Spring/Spring AOP.md | 776 ------------------
...37345円221円275円345円221円250円346円234円237円.md" | 545 ------------
...70350円247円201円346円263円250円350円247円243円.md" | 50 ++
.../SpringBoot 344円273円213円347円273円215円.md" | 22 +
7 files changed, 260 insertions(+), 1572 deletions(-)
delete mode 100644 docs/Spring/Spring AOP.md
delete mode 100644 "docs/Spring/Spring 344円270円255円 Bean 347円232円204円347円224円237円345円221円275円345円221円250円346円234円237円.md"
create mode 100644 "docs/Spring/Spring 345円270円270円350円247円201円346円263円250円350円247円243円.md"
create mode 100644 "docs/Spring/SpringBoot 344円273円213円347円273円215円.md"
diff --git a/docs/Spring/02SpringAOP.md b/docs/Spring/02SpringAOP.md
index 05a5326..c1d87bd 100644
--- a/docs/Spring/02SpringAOP.md
+++ b/docs/Spring/02SpringAOP.md
@@ -6,10 +6,10 @@
* [Spring的AspectJ的AOP](#Spring的AspectJ的AOP)
-# SpringAOP
+# Spring AOP
-## SpringAOP概述
-### 什么是AOP
+## AOP 概述
+### 什么是 AOP
AOP(面向切面编程,Aspect Oriented Programing)。
AOP采取**横向抽取**机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
@@ -21,11 +21,7 @@ AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提
AOP底层原理就是**代理机制**。
-> 关注点分离:不同的问题交给不同部分去解决
-
-### Spring的AOP代理
-- JDK动态代理:对实现了接口的类生成代理
-- CGLib代理机制:对类生成代理
+> 关注点分离:不同的问题交给不同部分去解决。
### AOP的术语
@@ -36,16 +32,29 @@ AOP底层原理就是**代理机制**。
| Advice(通知/增强) | 所谓通知是指拦截到Joinpoint之后所要做的事情就是**通知**。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能) |
| Introduction(引介) | 引介是一种**特殊的通知**在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field |
| Target(目标对象) | 代理的目标对象 |
-| Weaving(织入) | 是指把增强应用到目标对象来创建新的代理对象的过程。有三种织入方式: Spring采用**动态代理织入**,而AspectJ采用**编译期织入**和**类装载期织入** |
+| Weaving(织入) | 是指把增强应用到目标对象来创建新的代理对象的过程。
有三种织入方式: Spring采用**动态代理织入**,而AspectJ采用**编译期织入**和**类装载期织入** |
| Proxy(代理)| 一个类被AOP织入增强后,就产生一个结果代理类 |
| Aspect(切面) | 是切入点和通知(/引介)的结合 |
-- 示例:
-
-
-
-## AOP的底层实现
-### JDK动态代理
+- 示例:在 IUserDao() 中
+
+ ```java
+ public interface IUserDao {
+ void add();
+ void delete();
+ void update();
+ void search();
+ }
+ // IUserDao 被增强的对象,就是 Target(目标对象)
+ // add()、delete()、update() 和 search() 都是 JoinPoint(连接点)
+ // 这里要对 add() 和 update() JoinPoint 进行拦截,则 add() 和 update() 就是 Pointcut(切入点)
+ // Advice 指的是要增强的代码,也就是代码的增强
+ // Weaving:指的是把增强(Advice)应用到目标对象(Target)创建新的代理对象得人过程
+ // Aspect:是切入点和通知的结合,在 add 或 delete 方法上应用增强
+ ```
+
+## AOP 的底层实现
+### JDK 动态代理
JDK动态代理:对**实现了接口的类**生成代理
```java
@@ -131,7 +140,7 @@ public class JdkProxyDemo {
结束事务
```
-### Cglib动态代理
+### Cglib 动态代理
以**继承的方式**动态生成目标类的代理。
CGLIB(Code Generation Library)是一个开源项目!
@@ -215,21 +224,11 @@ Spring框架中:
- **如果类实现了接口,就使用JDK的动态代理生成代理对象**
- **如果这个类没有实现任何接口,使用CGLIB生成代理对象**
+- **可以通过配置文件指定对接口使用 CGLIB 生成代理对象**
+## Spring 中的 AOP
-### 相关阅读
-
-- [代理设计模式](https://github.com/DuHouAn/Java/blob/master/Object_Oriented/notes/03%E7%BB%93%E6%9E%84%E5%9E%8B.md#7-%E4%BB%A3%E7%90%86proxy)
-
-Spring中代理模式的实现:
-
-- 真实实现类的逻辑包含在getBean方法中;
-- getBean方法返回的实际上是Proxy的实例;
-- Proxy实例是Spring 采用JDK Proxy或CGLIB动态生成的。
-
-## Spring中的AOP
-
-### Spring中通知
+### Spring 中通知
Spring中的通知Advice其实是指"增强代码"。
| 通知类型 | 全类名 | 说明 |
@@ -241,7 +240,7 @@ Spring中的通知Advice其实是指"增强代码"。
| 引介通知 | org.springframework.aop.IntroductionInterceptor | 在目标类中添加一些新的方法和属性 |
-### Spring中切面类型
+### Spring 中切面类型
Advisor : Spring中传统切面。
- Advisor:一个切点和一个通知组合。
- Aspect:多个切点和多个通知组合。
@@ -250,7 +249,7 @@ Advisor : 代表一般切面,Advice本身就是一个切面,对目标类所
PointcutAdvisor : 代表具有切点的切面,可以指定拦截目标类哪些方法(带有切点的切面,针对某个方法进行拦截)
IntroductionAdvisor : 代表引介切面,针对引介通知而使用切面(不要求掌握)
-### Spring的AOP的开发
+### Spring 的 AOP 的开发
#### 1. 不带有切点的切面(针对所有方法的增强)
> 第一步:导入相应jar包
@@ -608,9 +607,9 @@ public class SpringTest {
- 自动代理基于后处理Bean。**在Bean的生成过程中,就产生了代理对象**,把代理对象返回。
生成的Bean已经是代理对象。
-## Spring的AspectJ的AOP
+## Spring 的 AspectJ 的 AOP
-### 基于XML
+### 基于 XML
> 第一步:编写被增强的类
UserDao
diff --git "a/docs/Spring/04Spring344円272円213円345円212円241円347円256円241円347円220円206円.md" "b/docs/Spring/04Spring344円272円213円345円212円241円347円256円241円347円220円206円.md"
index 667b85b..9b0ff4c 100644
--- "a/docs/Spring/04Spring344円272円213円345円212円241円347円256円241円347円220円206円.md"
+++ "b/docs/Spring/04Spring344円272円213円345円212円241円347円256円241円347円220円206円.md"
@@ -1,120 +1,114 @@
-
+# Spring 事务管理
-
+## 事务属性
-# 简介
+事务属性可以理解成事务的一些基本配置,描述事务策略如何应用到方法上。
-Spring 的事务有两种类型:
+事务属性包含了 5 个方面:
-- 全局事务(分布式事务)
-- 局部事务(单机事务)
+- 传播行为
+- 隔离级别
+- 是否只读事务
+- 事务超时
+- 回滚规则
-我们平时常用的是单机事务,也就是操作一个数据库的事务。
+### 1、传播行为
-按照使用方式来分,又可以分为编程式事务模型(TransactionTemplate)和声明式事务模型(@Transactional注解),后者可以理解为AOP + 编程式事务模型。
-
-# 一、编程式事务模型
-
-## 1.1 统一事务模型
-
-在统一事务模型中,最重要的一个类就是TransactionTemplate,其中最重要的方法是execute方法,其源码如下:
-
-```java
-@Override
- public T execute(TransactionCallback action) throws TransactionException {
- if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
- return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
- }
- else {
- //获取当前事务的状态
- TransactionStatus status = this.transactionManager.getTransaction(this);
- T result;
- try {
- result = action.doInTransaction(status);
- }
- catch (RuntimeException ex) {
- // 出现异常的回滚操作
- rollbackOnException(status, ex);
- throw ex;
- }
- catch (Error err) {
- // 出现Error的回滚操作
- rollbackOnException(status, err);
- throw err;
- }
- catch (Throwable ex) {
- // Transactional code threw unexpected exception -> rollback
- rollbackOnException(status, ex);
- throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
- }
- this.transactionManager.commit(status);
- return result;
- }
- }
-```
-
-其中transactionManager是PlatformTransactionManager接口,负责事务的创建、提交、回滚策略。这里TransactionTemplate相当于实现了[模板方法]()+[策略模式]()这两种设计模式。
-
-针对不同的厂商,只需要提供不同的PlatformTransactionManager实现即可。我们在使用的时候,只需要通过Spring IOC,告诉Spring,要注入哪个TransactionManager,要使用哪种策略即可。
-
-> Spring 如何保证所有数据库操作都是使用同一个数据库连接呢?
->
-> [补充资料]()
-
-## 1.2 事务传播级别
-
-事务传播行为:**用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的事务时如何传播**。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
+当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
+
+事务传播行为:**用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的事务时如何传播**。
Spring中有七种事务传播类型:
-| 类型 | 说明 |
-| :--------------------------------------- | :----------------------------------------------------------- |
-| PROPAGATION_REQUIRED(required) | 表示**当前方法必须运行在事务中**。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务。 |
-| PROPAGATION_SUPPORTS(support) | 表示支持当前事务,如果当前没有事务,就以无事务方式执行。 |
-| PROPAGATION_MANDATORY(mandatory) | 表示使用当前的事务,如果当前没有事务,就抛出异常。 |
-| PROPAGATION_REQUIRES_NEW(required_new) | 表示新建事务,如果当前存在事务,把当前事务挂起。 |
+| 类型 | 说明 |
+| :--------------------------------------: | :----------------------------------------------------------: |
+| PROPAGATION_REQUIRED(required) | 表示**当前方法必须运行在事务中**。
如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务。 |
+| PROPAGATION_SUPPORTS(support) | 表示支持当前事务,如果当前没有事务,就以无事务方式执行。 |
+| PROPAGATION_MANDATORY(mandatory) | 表示使用当前的事务,如果当前没有事务,就抛出异常。 |
+| PROPAGATION_REQUIRES_NEW(required_new) | 表示新建事务,如果当前存在事务,把当前事务挂起。 |
| PROPAGATION_NOT_SUPPORTED(not_support) | 表示以无事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
-| PROPAGATION_NEVER(never) | 表示以无事务方式执行,如果当前存在事务,则抛出异常。 |
-| PROPAGATION_NESTED(nested) | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
-
+| PROPAGATION_NEVER(never) | 表示以无事务方式执行,如果当前存在事务,则抛出异常。 |
+| PROPAGATION_NESTED(nested) | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
+### 2、隔离级别
-## 1.3 事务隔离级别
+事务隔离级别是指多个事务之间的隔离程度。
-事务隔离级别是指多个事务之间的隔离程度,其中TransactionDefinition 接口中定义了五个表示隔离级别的常量:
+TransactionDefinition 接口中定义了五个表示隔离级别的常量:
-| 隔离级别 | 说明 |
-| -------------------------------------- | ------------------------------------------------------------ |
-| ISOLATION_DEFAULT(默认) | 这是默认值,表示使用底层数据库的默认隔离级别。 |
+| 隔离级别 | 说明 |
+| :------------------------------------: | :----------------------------------------------------------: |
+| ISOLATION_DEFAULT(默认) | 这是默认值,表示使用底层数据库的默认隔离级别。 |
| ISOLATION_READ_UNCOMMITTED(读未提交) | 该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据,该级别不能防止脏读和不可重复读,因此很少使用该隔离级别 |
-| ISOLATION_READ_COMMITTED(读可提交) | 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值 |
+| ISOLATION_READ_COMMITTED(读可提交) | 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值 |
| ISOLATION_REPEATABLE_READ(可重复读) | 该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读 |
-| ISOLATION_SERIALIZABLE(可串行化) | 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是,这将严重影响程序的性能,通常情况下也不会用到该级别 |
+| ISOLATION_SERIALIZABLE(可串行化) | 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是,这将严重影响程序的性能,通常情况下也不会用到该级别 |
+
+### 3、是否只读事务
+
+如果事务只对后端的数据库进行操作,数据库可以利用事务的只读特性来进行一些特定的优化。
+
+### 4、事务超时
+
+为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。
+
+事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自然回滚,而不是一直等待其结束。
+
+### 5、回滚规则
+
+这些规则定义了哪些异常会导致事务回滚哪些不会。
+
+默认情况下,事务只有遇到运行期异常事时才回滚,而在遇到检查型异常时不会回滚,但是我们可以声明事务在遇到特殊性的检查型异常时像遇到运行期异常那样回滚。同样,我们也可以声明遇到特定的异常不回滚,及时这些异常时运行期异常。
-> 这里和数据库中的隔离级别的概念是相通的,可参考[Java-Notes]()
+## Spring事务管理
-# 二、声明式事务模型
+Spring 的事务有两种类型:
+
+- 全局事务(分布式事务)
+- 局部事务(单机事务)
+
+我们平时常用的是单机事务,也就是操作一个数据库的事务。
+
+按照使用方式来分,又可以分为编程式事务(TransactionTemplate)和声明式事务(@Transactional注解):
+
+- 编程式事务
+- 声明式事务
+
+### 编程式事务
+
+编程式事务指的是通过编码方式实现事务,类似 JDBC 编程实现事务管理。
+
+### 声明式事务
+
+声明式事务实现方式:
+
+- XML 实现
+- @Transactional 注解实现
常使用的 @Transactional 注解方式来使用事务,比编程式方式方便得多。
-主要是利用了[AOP原理]()来实现了这个功能,对于@Transactional 修饰的方法,Spring会为其创建一个代理对象,这个代理对象中就会有事务开启、提交、回滚等逻辑。
+声明式事务主要是利用了[AOP 原理](https://duhouan.github.io/Java-Notes/#/./Spring/02SpringAOP)来实现了这个功能,对于 @Transactional 修饰的方法,Spring 会为其创建一个**代理对象**,这个代理对象中就会有事务开启、提交、回滚等逻辑。
-Spring中,事务对应的Advisor是BeanFactoryTransactionAttributeSourceAdvisor,它的adivce是TransactionInterceptor,pointcut是TransactionAttributeSourcePointcut,其作用是判断方法是否被@Transactional 注解修饰。
+### 编程式和声明式的对比
+| 类型 | 优点 | 缺点 |
+| :--------: | :------------------: | :------------------------------: |
+| 编程式事务 | 显式调用,不易出错 | 侵入式代码,编码量大 |
+| 声明式事务 | 简洁,对代码侵入性小 | 隐藏了实现细节,出现问题不易定位 |
+### Spring 事务管理
-# 三、编程式和声明式的对比
+Spring 对事务管理是通过**事务管理器**实现的,Spring 提供了许多内置事务管理器实现。
-| 类型 | 优点 | 缺点 |
-| -------------- | -------------------- | ---------------------------------------------- |
-| 编程式事务模型 | 显式调用,不易出错 | 侵入式代码,编码量大 |
-| 声明式事务模型 | 简洁,对代码侵入性小 | 隐藏了实现细节,出现问题不易定位,容易导致误用 |
+Spring 事务管理器的接口是 PlatformTransactionManager,我们在使用的时候,只需要通过 Spring IoC,告诉Spring,要注入哪个 TransactionManager,要使用哪种策略即可。
+具体的事务管理机制对 Spring 来说是透明的,而具体的事务管理机制是各个平台(如 JDBC 等)自己处理。
+Spring 事务管理机制的提个优点就是为不同事务的 API 提供一致的编程模型。
-# 四、补充资料
+## 补充资料
- [使用方式](https://juejin.im/post/5b010f27518825426539ba38)
@@ -122,9 +116,11 @@ Spring中,事务对应的Advisor是BeanFactoryTransactionAttributeSourceAdviso
# 参考资料
-
+https://zhuanlan.zhihu.com/p/38772486
+
+https://zhuanlan.zhihu.com/p/41864893
-
+https://segmentfault.com/a/1190000013341344
-
+- [全面分析 Spring 的编程式事务管理及声明式事务管理](https://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/index.html)
diff --git "a/docs/Spring/05Spring344円270円255円Bean347円232円204円347円224円237円345円221円275円345円221円250円346円234円237円.md" "b/docs/Spring/05Spring344円270円255円Bean347円232円204円347円224円237円345円221円275円345円221円250円346円234円237円.md"
index 024f5a2..8d42d52 100644
--- "a/docs/Spring/05Spring344円270円255円Bean347円232円204円347円224円237円345円221円275円345円221円250円346円234円237円.md"
+++ "b/docs/Spring/05Spring344円270円255円Bean347円232円204円347円224円237円345円221円275円345円221円250円346円234円237円.md"
@@ -1,49 +1,33 @@
-
-* [Spring中Bean的生命周期](#Spring中Bean的生命周期)
- * [Bean的作用域](#Bean的作用域)
- * [Bean的生命周期](#Bean的生命周期)
- * [initialize和destroy](#initialize和destroy)
- * [XxxAware接口](#XxxAware接口)
- * [BeanPostProcessor](#BeanPostProcessor)
- * [总结](#总结)
-
-
-# Spring中Bean的生命周期
-Spring 中,组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 Bean。
-简单地讲,Bean 就是由 IOC 容器初始化、装配及管理的对象。
-Bean 的定义以及 Bean 相互间的依赖关系通过**配置元数据**来描述。
-
-Spring中的Bean默认都是**单例**的,
-这些单例Bean在多线程程序下如何保证线程安全呢?
-例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,
-引入Spring框架之后,**每个Action都是单例的**,
-那么对于Spring托管的单例Service Bean,如何保证其安全呢?
-Spring使用ThreadLocal解决线程安全问题(为每一个线程都提供了一份变量,因此可以同时访问而互不影响)。
+# Spring 中 Bean 的生命周期
+
+Spring 中,组成应用程序的主体及由 Spring IoC 容器所管理的对象,被称之为 Bean。
+简单地讲,Bean 就是由 IoC 容器初始化、装配及管理的对象,Bean 的定义以及 Bean 相互间的依赖关系通过**配置元数据**来描述。
+
+Spring 中的 Bean 默认都是**单例**的,这些单例 Bean 在多线程程序下如何保证线程安全呢?
+例如对于 Web 应用来说,Web 容器对于每个用户请求都创建一个单独的 Sevlet 线程来处理请求,
+引入 Spring 框架之后,**每个 Action 都是单例的**,那么对于 Spring 托管的单例 Service Bean,如何保证其安全呢? Spring 使用 ThreadLocal 解决线程安全问题(为每一个线程都提供了一份变量,因此可以同时访问而互不影响)。
Spring的单例是**基于BeanFactory**也就是Spring容器的,单例Bean在此容器内只有一个,
**Java的单例是基于 JVM,每个 JVM 内只有一个实例**。
-## Bean的作用域
+## Bean 的作用域
-Spring Framework支持五种作用域:
+Spring Framework 支持五种作用域:
| 类别 | 说明 |
| :--:| :--: |
-| singleton | 在SpringIOC容器中仅存在一个Bean实例,Bean以单例方式存在 |
-| prototype | 每次从容器中调用Bean时,都返回一个新的实例 |
-| request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
-| session | 同一个Http Session共享一个Bean,不同Session使用不同Bean,仅适用于WebApplicationContext环境 |
-| globalSession | 一般同于Portlet应用环境,该作用域仅适用于WebApplicationContext环境 |
+| singleton | 在 SpringIoC 容器中仅存在一个 Bean 实例,Bean 以单例方式存在 |
+| prototype | 每次从容器中调用 Bean 时,都返回一个新的实例 |
+| request | 每次HTTP请求都会创建一个新的 Bean,该作用域仅适用于 WebApplicationContext 环境 |
+| session | 同一个 Http Session 共享一个 Bean,不同 Session 使用不同 Bean,仅适用于WebApplicationContext 环境 |
+| globalSession | 一般同于 Portlet 应用环境,该作用域仅适用于 WebApplicationContext 环境 |
-注意:五种作用域中,
-request、session 和 global session
-三种作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),
-只能用在基于 web 的 Spring ApplicationContext 环境。
+注意:五种作用域中,request、session 和 global session 三种作用域仅在基于 web 的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于 web 的 Spring ApplicationContext 环境。
### 1. singleton
当一个 Bean 的作用域为 singleton,那么Spring IoC容器中只会存在一个**共享的 Bean 实例**,
并且所有对 Bean 的请求,只要 id 与该 Bean 定义相匹配,则只会**返回 Bean 的同一实例**。
-
+
singleton 是单例类型(对应于单例模式),就是**在创建容器时就同时自动创建一个Bean对象**,
不管你是否使用,但我们可以指定Bean节点的 lazy-init="true" 来延迟初始化Bean,
这时候,只有在第一次获取Bean时才会初始化Bean,即第一次请求该bean时才初始化。 每次获取到的对象都是同一个对象。
@@ -64,7 +48,7 @@ public class ServiceImpl{
}
```
-#### 2. prototype
+### 2. prototype
当一个Bean的作用域为 prototype,表示一个 Bean 定义对应多个对象实例。
prototype 作用域的 Bean 会导致在每次对该 Bean 请求
@@ -99,7 +83,7 @@ public class ServiceImpl{
request只适用于**Web程序**,每一次 HTTP 请求都会产生一个新的 Bean ,
同时该 Bean 仅在当前HTTP request内有效,当请求结束后,该对象的生命周期即告结束。
-
+
在 XML 中将 bean 定义成 request ,可以这样配置:
- 配置文件XML中将 Bean 定义成 prototype :
@@ -135,39 +119,51 @@ Portlet 规范定义了全局 Session 的概念,
```
-## Bean的生命周期
+## Bean 的生命周期
-Spring容器在创建、初始化和销毁Bean的过程中做了哪些事情:
+### Bean 的创建过程
-```java
-Spring容器初始化
-=====================================
-调用GiraffeService无参构造函数
-GiraffeService中利用set方法设置属性值
-调用setBeanName:: Bean Name defined in context=giraffeService
-调用setBeanClassLoader,ClassLoader Name = sun.misc.Launcher$AppClassLoader
-调用setBeanFactory,setBeanFactory:: giraffe bean singleton=true
-调用setEnvironment
-调用setResourceLoader:: Resource File Name=spring-beans.xml
-调用setApplicationEventPublisher
-调用setApplicationContext:: Bean Definition Names=[giraffeService, org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#0, com.giraffe.spring.service.GiraffeServicePostProcessor#0]
-执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=giraffeService
-调用PostConstruct注解标注的方法
-执行InitializingBean接口的afterPropertiesSet方法
-执行配置的init-method
-执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=giraffeService
-Spring容器初始化完毕
-=====================================
-从容器中获取Bean
-giraffe Name=张三
-=====================================
-调用preDestroy注解标注的方法
-执行DisposableBean接口的destroy方法
-执行配置的destroy-method
-Spring容器关闭
-```
+
+
+### Bean 的销毁过程
+
+- 若实现了 DisposableBean 接口,则会调用 destroy 方法
+
+- 若配置了 destroy-method 属性,则会调用其配置的销毁方法
+
+### Bean 的详细生命周期
+
+1、Spring 对 Bean 进行实例化。
+
+2、Spring 将值和 Bean 的引用注入到 Bean 对应的属性中。
+
+3、如果 Bean 实现了 BeanNameAware 接口,Spring 将 bean 的 id 传递给 setBeanName() 接口方法。
+
+4、如果 Bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanFactory() 接口方法,将 BeanFactory 容器实例传入。
+
+5、如果 Bean 实现了 ApplicationContextAware 接口,Spring 将调用 setApplicationContext() 接口方法,将应用上下文的引用传入。
+
+6、如果 Bean 实现了BeanPostProcessor 接口,Spring 将调用 postProcessBeforeInitialization() 接口方法。
-### initialize和destroy
+7、如果 Bean 实现了InitializingBean 接口,Spring 将调用他们的 afterPropertiesSet() 接口方法,类似地,如果Bean 实现了 init-method 声明了初始化方法,该方法也会被调用。
+
+8、如果 Bean 实现了BeanPostProcessor 接口,Spring 将调用 postProcessAfterInitialization() 接口方法。
+
+9、此时 Bean 已经准备就绪,可以被应用程序使用了,他们将一一直驻留在应用上下文中,一直到该应用上下文被销毁。
+
+10、如果 Bean 实现了 DisposableBean 接口,Spring 将调用它的 destroy() 接口方法。同样,如果 Bean 使用 destroy-method 声明了销毁方法,方法也会被调用。
+
+
+
+
+
+
+
+
+
+### 实践
+
+#### initialize 和 destroy
有时我们需要在Bean属性值设置好之后和Bean销毁之前做一些事情,
比如检查Bean中某个属性是否被正常的设置好了。
@@ -260,7 +256,7 @@ public class GiraffeService {
}
```
-### XxxAware接口
+#### XxxAware接口
有些时候我们需要在 Bean 的初始化中**使用 Spring 框架自身的一些对象**来执行一些操作,
比如获取 ServletContext 的一些参数,获取 ApplicaitionContext 中的 BeanDefinition 的名字,获取 Bean 在容器中的名字等等。
@@ -332,7 +328,8 @@ public class GiraffeService implements ApplicationContextAware,
}
```
-### BeanPostProcessor
+#### BeanPostProcessor
+
上面的XxxAware接口是**针对某个实现这些接口的Bean定制初始化的过程**,
Spring同样可以针对容器中的所有Bean,或者某些Bean定制初始化过程,
只需提供一个实现BeanPostProcessor接口的类即可。
@@ -375,65 +372,12 @@ public class CustomerBeanPostProcessor implements BeanPostProcessor {
```
-### 总结
-Spring Bean的生命周期
-
-1. Bean容器找到配置文件中 Spring Bean 的定义。
+## 注意
-2. Bean容器利用Java Reflection API创建一个Bean的实例。
-
-3. 如果涉及到一些属性值,利用set方法设置一些属性值。
-
-4. 如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。
-
-5. 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
-
-6. 如果Bean实现了BeanFactoryAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
-
-7. 与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。
-
-8. 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,
-执行postProcessBeforeInitialization()方法
-
-9. 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
-
-10. 如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。
-
-11. 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,
-执行postProcessAfterInitialization()方法
-
-12. 当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法;
-如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。
-
-
-
-
-
-很多时候我们并不会真的去实现上面说描述的那些接口,
-那么下面我们就除去那些接口,针对 Bean 的单例和非单例来描述下 Bean 的生命周期:
-
-> **1. 单例管理的对象**
-
-scope="singleton",即默认情况下,会在启动容器时(即实例化容器时)时实例化。
-但我们可以指定 lazy-init="true"。这时候,只有在第一次获取 Bean 时才会初始化 Bean,
-即第一次请求该 Bean时才初始化。配置如下:
-
-```html
-
-```
-
-想对所有的默认单例Bean都应用延迟初始化,
-可以在根节点beans 指定default-lazy-init="true",如下所示:
-
-```html
-
-```
-
-默认情况下,Spring 在读取 xml 文件的时候,就会创建对象。
-在创建对象的时候先调用**构造器**,然后调用 **init-method 属性值中所指定的方法**。
-对象在被销毁的时候,会调用 **destroy-method 属性值中所指定的方法**。
+**Spring 容器可以管理 singleton 作用域下 Bean 的生命周期,在此作用域下,Spring 能够精确地知道 Bean 何时被创建,何时初始化完成,以及何时被销毁。而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给了客户端的代码管理,Spring 容器将不再跟踪其生命周期,并且不会管理那些被配置成 prototype 作用域的 Bean 的生命周期**。
- LifeCycleBean :
+
```java
public class LifeCycleBean {
private String name;
@@ -461,6 +405,7 @@ public class LifeCycleBean {
```
- 配置文件 :
+
```html
@@ -501,6 +446,7 @@ Spring 读取xml 文件的时候,并不会立刻创建对象,而是在第一
这个类型的对象有很多个,**Spring容器一旦把这个对象交给你之后,就不再管理这个对象了**。
- 配置文件 :
+
```html
@@ -525,6 +471,7 @@ public class LifeTest2 {
```
- 输出结果:
+
```html
LifeBean()构造函数
this is init of lifeBean
@@ -537,9 +484,4 @@ this is destory of lifeBean LifeCycleBean@573f2bb1
作用域为 prototype 的 Bean ,其destroy方法并没有被调用。
如果 bean 的 scope 设为prototype时,当容器关闭时,destroy 方法不会被调用。
-对于 prototype 作用域的 bean,有一点非常重要,
-
-**Spring 容器可以管理 singleton 作用域下 Bean 的生命周期,
-在此作用域下,Spring 能够精确地知道 Bean 何时被创建,何时初始化完成,以及何时被销毁。
-而对于 prototype 作用域的bean,Spring只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给了客户端的代码管理,
-Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的Bean的生命周期**。
\ No newline at end of file
+对于 prototype 作用域的 bean,有一点非常重要,
\ No newline at end of file
diff --git a/docs/Spring/Spring AOP.md b/docs/Spring/Spring AOP.md
deleted file mode 100644
index 05a5326..0000000
--- a/docs/Spring/Spring AOP.md
+++ /dev/null
@@ -1,776 +0,0 @@
-
-* [SpringAOP](#SpringAOP)
- * [SpringAOP概述](#SpringAOP概述)
- * [AOP的底层实现](#AOP的底层实现)
- * [Spring中的AOP](#Spring中的AOP)
- * [Spring的AspectJ的AOP](#Spring的AspectJ的AOP)
-
-
-# SpringAOP
-
-## SpringAOP概述
-### 什么是AOP
-AOP(面向切面编程,Aspect Oriented Programing)。
-
-AOP采取**横向抽取**机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
-
-Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类**织入**增强代码
-
-AspecJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对AspectJ的支持,
-AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入。
-
-AOP底层原理就是**代理机制**。
-
-> 关注点分离:不同的问题交给不同部分去解决
-
-### Spring的AOP代理
-- JDK动态代理:对实现了接口的类生成代理
-- CGLib代理机制:对类生成代理
-
-### AOP的术语
-
-| 术语 | 描述 |
-| :--: | :--: |
-| Joinpoint(连接点) | 所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是**方法**,因为Spring只支持**方法类型的连接点**。 |
-| Pointcut(切入点) | 所谓切入点是指我们要**对哪些Joinpoint进行拦截**的定义。 |
-| Advice(通知/增强) | 所谓通知是指拦截到Joinpoint之后所要做的事情就是**通知**。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能) |
-| Introduction(引介) | 引介是一种**特殊的通知**在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field |
-| Target(目标对象) | 代理的目标对象 |
-| Weaving(织入) | 是指把增强应用到目标对象来创建新的代理对象的过程。有三种织入方式: Spring采用**动态代理织入**,而AspectJ采用**编译期织入**和**类装载期织入** |
-| Proxy(代理)| 一个类被AOP织入增强后,就产生一个结果代理类 |
-| Aspect(切面) | 是切入点和通知(/引介)的结合 |
-
-- 示例:
-
-
-
-## AOP的底层实现
-### JDK动态代理
-JDK动态代理:对**实现了接口的类**生成代理
-
-```java
-public interface IUserDao {
- void add();
- void delete();
- void update();
- void search();
-}
-```
-- UserDao实现了IUserDao接口
-```java
-public class UserDao implements IUserDao{
- @Override
- public void add() {
- System.out.println("添加功能");
- }
-
- @Override
- public void delete() {
- System.out.println("删除功能");
- }
-
- @Override
- public void update() {
- System.out.println("更新功能");
- }
-
- @Override
- public void search() {
- System.out.println("查找功能");
- }
-}
-```
-
-```java
-public class JdkProxy implements InvocationHandler{
- private IUserDao iUserDao;
-
- public JdkProxy(IUserDao iUserDao){
- this.iUserDao=iUserDao;
- }
-
- public IUserDao getPrxoy(){
- return (IUserDao)Proxy.newProxyInstance(
- iUserDao.getClass().getClassLoader(),
- iUserDao.getClass().getInterfaces(),
- this
- );
- }
-
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- String methodName = method.getName();
- Object obj = null;
- //只对add方法进行增强
- if ("add".equals(methodName)) {
- System.out.println("开启事务");
- obj = method.invoke(iUserDao, args);
- System.out.println("结束事务");
- } else {
- obj = method.invoke(iUserDao, args);
- }
- return obj;
- }
-}
-```
-- 对实现了接口的类UserDao生成代理
-```java
-public class JdkProxyDemo {
- public static void main(String[] args) {
- IUserDao userDao=new UserDao();
- JdkProxy jdkProxy=new JdkProxy(userDao);
- IUserDao userDao2=jdkProxy.getPrxoy();
- userDao2.add();
- }
-}
-```
-- 输出结果
-```html
-开启事务
-添加功能
-结束事务
-```
-
-### Cglib动态代理
-以**继承的方式**动态生成目标类的代理。
-
-CGLIB(Code Generation Library)是一个开源项目!
-是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
-
-```java
-public class ProductDao {
- public void add() {
- System.out.println("添加功能");
- }
-
- public void delete() {
- System.out.println("删除功能");
- }
-
- public void update() {
- System.out.println("更新功能");
- }
-
- public void search() {
- System.out.println("查找功能");
- }
-}
-```
-
-```java
-public class CGLibProxy implements MethodInterceptor {
- private ProductDao productDao;
-
- public CGLibProxy(ProductDao productDao) {
- this.productDao = productDao;
- }
-
- public ProductDao getProxy(){
- // 使用CGLIB生成代理:
- // 1.创建核心类:
- Enhancer enhancer = new Enhancer();
- // 2.为其设置父类:
- enhancer.setSuperclass(productDao.getClass());
- // 3.设置回调:
- enhancer.setCallback(this);
- // 4.创建代理:
- return (ProductDao) enhancer.create();
- }
-
- @Override
- public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
- String menthodName=method.getName();
- Object obj=null;
- if("add".equals(menthodName)){
- System.out.println("开启事务");
- obj=methodProxy.invokeSuper(proxy,args);
- System.out.println("结束事务");
- }else{
- obj=methodProxy.invokeSuper(proxy,args);
- }
- return obj;
- }
-}
-```
-```java
-public class CGLibProxyDemo {
- public static void main(String[] args) {
- ProductDao productDao=new ProductDao();
- CGLibProxy proxy=new CGLibProxy(productDao);
- ProductDao productDao2=proxy.getProxy();
- productDao2.add();
- }
-}
-```
-- 输出结果
-```html
-开启事务
-添加功能
-结束事务
-```
-
-### 总结
-
-Spring框架中:
-
-- **如果类实现了接口,就使用JDK的动态代理生成代理对象**
-- **如果这个类没有实现任何接口,使用CGLIB生成代理对象**
-
-
-### 相关阅读
-
-- [代理设计模式](https://github.com/DuHouAn/Java/blob/master/Object_Oriented/notes/03%E7%BB%93%E6%9E%84%E5%9E%8B.md#7-%E4%BB%A3%E7%90%86proxy)
-
-Spring中代理模式的实现:
-
-- 真实实现类的逻辑包含在getBean方法中;
-- getBean方法返回的实际上是Proxy的实例;
-- Proxy实例是Spring 采用JDK Proxy或CGLIB动态生成的。
-
-## Spring中的AOP
-
-### Spring中通知
-Spring中的通知Advice其实是指"增强代码"。
-
-| 通知类型 | 全类名 | 说明 |
-| :--: | :--: | :--: |
-| 前置通知 | org.springframework.aop.MethodBeforeAdvice | 在目标方法执行前实施增强 |
-| 后置通知 | org.springframework.aop.AfterReturningAdvice | 在目标方法执行后实施增强 |
-| 环绕通知 | org.aopalliance.intercept.MethodInterceptor | 在目标方法执行前后实施增强 |
-| 异常抛出通知 | org.springframework.aop.ThrowsAdvice | 在方法抛出异常后实施增强 |
-| 引介通知 | org.springframework.aop.IntroductionInterceptor | 在目标类中添加一些新的方法和属性 |
-
-
-### Spring中切面类型
-Advisor : Spring中传统切面。
-- Advisor:一个切点和一个通知组合。
-- Aspect:多个切点和多个通知组合。
-
-Advisor : 代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截(不带有切点的切面.针对所有方法进行拦截)
-PointcutAdvisor : 代表具有切点的切面,可以指定拦截目标类哪些方法(带有切点的切面,针对某个方法进行拦截)
-IntroductionAdvisor : 代表引介切面,针对引介通知而使用切面(不要求掌握)
-
-### Spring的AOP的开发
-
-#### 1. 不带有切点的切面(针对所有方法的增强)
-> 第一步:导入相应jar包
-```html
-
- aopalliance
- aopalliance
- 1.0
-
-```
-
-> 第二步:编写被代理对象
-
-IUserDao接口
-```java
-public interface IUserDao {
- void add();
- void update();
- void delete();
- void search();
-}
-```
-UserDao实现类
-```java
-public class UserDao implements IUserDao{
- @Override
- public void add() {
- System.out.println("增加功能");
- }
-
- @Override
- public void update() {
- System.out.println("修改功能");
- }
-
- @Override
- public void delete() {
- System.out.println("删除功能");
- }
-
- @Override
- public void search() {
- System.out.println("查找功能");
- }
-}
-```
-
-> 第三步:编写增强的代码
-```java
-import org.springframework.aop.MethodBeforeAdvice;
-import java.lang.reflect.Method;
-
-/**
- * 前置增强
- */
-public class MyBeforeAdvice implements MethodBeforeAdvice{
- @Override
- public void before(Method method, Object[] args, Object target) throws Throwable {
- System.out.println("前置增强...");
- }
-}
-```
-
-> 第四步:生成代理(配置生成代理)
-
-**生成代理Spring基于ProxyFactoryBean类。底层自动选择使用JDK的动态代理还是CGLIB的代理**。
-
-属性:
-- target : 代理的目标对象
-- proxyInterfaces : 代理要实现的接口
-```html
-如果多个接口可以使用以下格式赋值
-
-
- ....
-
-```
-- proxyTargetClass : 是否对类代理而不是接口,设置为true时,使用CGLib代理
-- interceptorNames : 需要织入目标的Advice
-- singleton : 返回代理是否为单实例,默认为单例
-- optimize : 当设置为true时,强制使用CGLib
-
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-> 测试
-```java
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration("classpath:applicationContext.xml")
-public class SpringTest {
- @Autowired
- @Qualifier("userDaoProxy")
- // 注入是真实的对象,必须注入代理对象.
- IUserDao iUserDao;
-
- @Test
- public void test(){
- iUserDao.add();
- iUserDao.delete();
- iUserDao.update();
- iUserDao.search();
- }
-}
-```
-- 输出结果:
-```html
-前置增强...
-增加功能
-前置增强...
-删除功能
-前置增强...
-修改功能
-前置增强...
-查找功能
-```
-
-#### 2. 带有切点的切面(针对目标对象的某些方法进行增强)
-PointcutAdvisor 接口:
-- DefaultPointcutAdvisor 最常用的切面类型,它可以通过任意Pointcut和Advice组合定义切面
-- RegexpMethodPointcutAdvisor 构造正则表达式切点切面
-
-> 第一步:创建被代理对象
-```java
-public class OrderDao {
- public void add() {
- System.out.println("增加功能");
- }
-
- public void update() {
- System.out.println("修改功能");
- }
-
- public void delete() {
- System.out.println("删除功能");
- }
-
- public void search() {
- System.out.println("查找功能");
- }
-}
-```
-
-> 第二步:编写增强的代码
-
-```java
-/**
- * 环绕增强
- */
-public class MyAroundAdvice implements MethodInterceptor {
- @Override
- public Object invoke(MethodInvocation invocation) throws Throwable {
- System.out.println("环绕前增强...");
- Object obj= invocation.proceed();
- System.out.println("环绕后增强...");
- return obj;
- }
-}
-```
-
-> 第三步:生成代理(配置生成代理)
-
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-> 测试
-```java
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration("classpath:applicationContext.xml")
-public class SpringTest {
- @Autowired
- @Qualifier("orderDaoProxy")
- OrderDao orderDao;
-
- @Test
- public void test(){
- orderDao.add();
- orderDao.delete();
- orderDao.update();
- orderDao.search();
- }
-}
-```
-
-- 输出结果:
-```html
-环绕前增强...
-增加功能
-环绕后增强...
-删除功能
-修改功能
-环绕前增强...
-查找功能
-环绕后增强...
-```
-
-#### 3. 自动代理
-前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,
-在实际开发中,非常多的**Bean每个都配置ProxyFactoryBean开发维护量巨大**。
-
-自动创建代理(**基于后处理Bean。在Bean创建的过程中完成的增强。生成Bean就是代理**。)
-- BeanNameAutoProxyCreator 根据Bean名称创建代理
-- DefaultAdvisorAutoProxyCreator 根据Advisor本身包含信息创建代理。
-其中AnnotationAwareAspectJAutoProxyCreator是基于Bean中的AspectJ 注解进行自动代理。
-
-**BeanNameAutoProxyCreator(按名称生成代理)**
-
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-```java
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration("classpath:applicationContext.xml")
-public class SpringTest {
- @Autowired
- @Qualifier("userDao")
- UserDao userDao;
- //代理的是实例类,不是接口。
-
- @Autowired
- @Qualifier("orderDao")
- OrderDao orderDao;
-
- @Test
- public void test(){
- userDao.add();
- userDao.search();
-
- orderDao.add();
- orderDao.search();
- }
-}
-```
-
-**DefaultAdvisorAutoProxyCreator(根据切面中定义的信息生成代理)**
-
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .*add.*
- .*search.*
-
-
-
-
-
-
-
-
-
-
-
-```
-
-```java
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration("classpath:applicationContext.xml")
-public class SpringTest {
- @Autowired
- @Qualifier("userDao")
- IUserDao userDao;
-
- @Autowired
- @Qualifier("orderDao")
- OrderDao orderDao;
-
- @Test
- public void test(){
- userDao.add();
- userDao.search();
-
- orderDao.add();
- orderDao.search();
- }
-}
-```
-
-### 区分基于ProxyFattoryBean的代理与自动代理区别?
-
-- ProxyFactoryBean:先有被代理对象,将被代理对象传入到代理类中生成代理。
-
-- 自动代理基于后处理Bean。**在Bean的生成过程中,就产生了代理对象**,把代理对象返回。
-生成的Bean已经是代理对象。
-
-## Spring的AspectJ的AOP
-
-### 基于XML
-> 第一步:编写被增强的类
-
-UserDao
-
-> 第二步:定义切面
-
-```java
-public class MyAspectXML {
- public void before(){
- System.out.println("前置通知...");
- }
-
- public void afterReturing(Object returnVal){
- System.out.println("后置通知...返回值:"+returnVal);
- }
-
- public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
- System.out.println("环绕前增强....");
- Object result = proceedingJoinPoint.proceed();
- System.out.println("环绕后增强....");
- return result;
- }
-
- public void afterThrowing(Throwable e){
- System.out.println("异常通知..."+e.getMessage());
- }
-
- public void after(){
- System.out.println("最终通知....");
- }
-}
-```
-
-> 第三步:配置applicationContext.xml
-
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-- 测试
-```java
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration("classpath:applicationContext.xml")
-public class SpringTest {
- @Autowired
- @Qualifier("userDao")
- IUserDao userDao;
-
- @Test
- public void test(){
- userDao.add();
- userDao.search();
- }
-}
-```
-
-### 基于注解
-AspectJ的通知类型:
-
-| 通知类型 | 解释 | 说明 |
-| :--:| :--: | :--: |
-| @Before | 前置通知,相当于BeforeAdvice | 就在方法之前执行。没有办法阻止目标方法执行的。 |
-| @AfterReturning | 后置通知,相当于AfterReturningAdvice | 后置通知,获得方法返回值。 |
-| @Around | 环绕通知,相当于MethodInterceptor | 在可以方法之前和之后来执行的,而且可以阻止目标方法的执行。 |
-| @AfterThrowing | 抛出通知,相当于ThrowAdvice | |
-| @After | 最终final通知,不管是否异常,该通知都会执行 | |
-| @DeclareParents | 引介通知,相当于IntroductionInterceptor | |
-
-
-**Advisor和Aspect的区别**?
-- Advisor:Spring传统意义上的切面,支持一个切点和一个通知的组合。
-- Aspect:可以支持多个切点和多个通知的组合。
-
-> 使用注解编写切面类
-```java
-/**
- * 切面类:就是切点与增强结合
- */
-@Aspect //申明是切面
-public class MyAspect{
- @Before("execution(* advice.UserDao.add(..))")
- public void before(JoinPoint joinPoint){
- System.out.println("前置增强...."+joinPoint);
- }
-
- @AfterReturning(value="execution(* advice.UserDao.update(..))",returning="returnVal")
- public void afterReturin(Object returnVal){
- System.out.println("后置增强....方法的返回值:"+returnVal);
- }
-
- @Around(value="MyAspect.myPointcut()")
- public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
- System.out.println("环绕前增强....");
- Object obj = proceedingJoinPoint.proceed();
- System.out.println("环绕后增强....");
- return obj;
- }
-
- @AfterThrowing(value="MyAspect.myPointcut()",throwing="e")
- public void afterThrowing(Throwable e){
- System.out.println("不好了 出异常了!!!"+e.getMessage());
- }
-
- @After("MyAspect.myPointcut()")
- public void after(){
- System.out.println("最终通知...");
- }
-
- //定义切点
- @Pointcut("execution(* advice.UserDao.search(..))")
- private void myPointcut(){}
-}
-```
-> 配置applicationContext.xml
-```html
-
-
-
-
-
-
-
-
-```
-
-- 测试:
-```java
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration("classpath:applicationContext.xml")
-public class SpringTest {
- @Autowired
- @Qualifier("userDao")
- IUserDao userDao;
-
- @Test
- public void test(){
- userDao.add();
- userDao.search();
- }
-}
-```
\ No newline at end of file
diff --git "a/docs/Spring/Spring 344円270円255円 Bean 347円232円204円347円224円237円345円221円275円345円221円250円346円234円237円.md" "b/docs/Spring/Spring 344円270円255円 Bean 347円232円204円347円224円237円345円221円275円345円221円250円346円234円237円.md"
deleted file mode 100644
index 024f5a2..0000000
--- "a/docs/Spring/Spring 344円270円255円 Bean 347円232円204円347円224円237円345円221円275円345円221円250円346円234円237円.md"
+++ /dev/null
@@ -1,545 +0,0 @@
-
-* [Spring中Bean的生命周期](#Spring中Bean的生命周期)
- * [Bean的作用域](#Bean的作用域)
- * [Bean的生命周期](#Bean的生命周期)
- * [initialize和destroy](#initialize和destroy)
- * [XxxAware接口](#XxxAware接口)
- * [BeanPostProcessor](#BeanPostProcessor)
- * [总结](#总结)
-
-
-# Spring中Bean的生命周期
-Spring 中,组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 Bean。
-简单地讲,Bean 就是由 IOC 容器初始化、装配及管理的对象。
-Bean 的定义以及 Bean 相互间的依赖关系通过**配置元数据**来描述。
-
-Spring中的Bean默认都是**单例**的,
-这些单例Bean在多线程程序下如何保证线程安全呢?
-例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,
-引入Spring框架之后,**每个Action都是单例的**,
-那么对于Spring托管的单例Service Bean,如何保证其安全呢?
-Spring使用ThreadLocal解决线程安全问题(为每一个线程都提供了一份变量,因此可以同时访问而互不影响)。
-Spring的单例是**基于BeanFactory**也就是Spring容器的,单例Bean在此容器内只有一个,
-**Java的单例是基于 JVM,每个 JVM 内只有一个实例**。
-
-## Bean的作用域
-
-Spring Framework支持五种作用域:
-
-| 类别 | 说明 |
-| :--:| :--: |
-| singleton | 在SpringIOC容器中仅存在一个Bean实例,Bean以单例方式存在 |
-| prototype | 每次从容器中调用Bean时,都返回一个新的实例 |
-| request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
-| session | 同一个Http Session共享一个Bean,不同Session使用不同Bean,仅适用于WebApplicationContext环境 |
-| globalSession | 一般同于Portlet应用环境,该作用域仅适用于WebApplicationContext环境 |
-
-注意:五种作用域中,
-request、session 和 global session
-三种作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),
-只能用在基于 web 的 Spring ApplicationContext 环境。
-
-### 1. singleton
-
-当一个 Bean 的作用域为 singleton,那么Spring IoC容器中只会存在一个**共享的 Bean 实例**,
-并且所有对 Bean 的请求,只要 id 与该 Bean 定义相匹配,则只会**返回 Bean 的同一实例**。
-
-singleton 是单例类型(对应于单例模式),就是**在创建容器时就同时自动创建一个Bean对象**,
-不管你是否使用,但我们可以指定Bean节点的 lazy-init="true" 来延迟初始化Bean,
-这时候,只有在第一次获取Bean时才会初始化Bean,即第一次请求该bean时才初始化。 每次获取到的对象都是同一个对象。
-注意,singleton 作用域是Spring中的**缺省作用域**。
-
-- 配置文件XML中将 Bean 定义成 singleton :
-```html
-
-```
-
-- @Scope 注解的方式:
-
-```java
-@Service
-@Scope("singleton")
-public class ServiceImpl{
-
-}
-```
-
-#### 2. prototype
-
-当一个Bean的作用域为 prototype,表示一个 Bean 定义对应多个对象实例。
-prototype 作用域的 Bean 会导致在每次对该 Bean 请求
-(将其注入到另一个 Bean 中,或者以程序的方式调用容器的 getBean() 方法)时都会创建一个新的 bean 实例。
-prototype 是原型类型,它在我们创建容器的时候并没有实例化,
-而是当我们获取Bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。
-
-根据经验,**对有状态的 Bean 应该使用 prototype 作用域,而对无状态的 Bean 则应该使用 singleton 作用域。**
-
-- 配置文件XML中将 Bean 定义成 prototype :
-
-```html
-
-```
-或者
-
-```html
-
-```
-
-- @Scope 注解的方式:
-
-```java
-@Service
-@Scope("prototype")
-public class ServiceImpl{
-
-}
-```
-
-### 3. request
-
-request只适用于**Web程序**,每一次 HTTP 请求都会产生一个新的 Bean ,
-同时该 Bean 仅在当前HTTP request内有效,当请求结束后,该对象的生命周期即告结束。
-
-在 XML 中将 bean 定义成 request ,可以这样配置:
-
-- 配置文件XML中将 Bean 定义成 prototype :
-
-```html
-
-```
-
-
-### 4. session
-
-session只适用于**Web程序**,
-session 作用域表示该针对每一次 HTTP 请求都会产生一个新的 Bean,
-同时**该 Bean 仅在当前 HTTP session 内有效**。
-与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,
-而别的 HTTP session 中根据 userPreferences 创建的实例,
-将不会看到这些特定于某个 HTTP session 的状态变化。
-当HTTP session最终被废弃的时候,在该HTTP session作用域内的bean也会被废弃掉。
-
-```html
-
-```
-
-### 5. globalSession
-
-globalSession 作用域**类似于标准的 HTTP session** 作用域,
-不过仅仅在基于 portlet 的 Web 应用中才有意义。
-Portlet 规范定义了全局 Session 的概念,
-它被所有构成某个 portlet web 应用的各种不同的 portlet所共享。
-在globalSession 作用域中定义的 bean 被限定于全局portlet Session的生命周期范围内。
-
-```html
-
-```
-
-## Bean的生命周期
-
-Spring容器在创建、初始化和销毁Bean的过程中做了哪些事情:
-
-```java
-Spring容器初始化
-=====================================
-调用GiraffeService无参构造函数
-GiraffeService中利用set方法设置属性值
-调用setBeanName:: Bean Name defined in context=giraffeService
-调用setBeanClassLoader,ClassLoader Name = sun.misc.Launcher$AppClassLoader
-调用setBeanFactory,setBeanFactory:: giraffe bean singleton=true
-调用setEnvironment
-调用setResourceLoader:: Resource File Name=spring-beans.xml
-调用setApplicationEventPublisher
-调用setApplicationContext:: Bean Definition Names=[giraffeService, org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#0, com.giraffe.spring.service.GiraffeServicePostProcessor#0]
-执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=giraffeService
-调用PostConstruct注解标注的方法
-执行InitializingBean接口的afterPropertiesSet方法
-执行配置的init-method
-执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=giraffeService
-Spring容器初始化完毕
-=====================================
-从容器中获取Bean
-giraffe Name=张三
-=====================================
-调用preDestroy注解标注的方法
-执行DisposableBean接口的destroy方法
-执行配置的destroy-method
-Spring容器关闭
-```
-
-### initialize和destroy
-
-有时我们需要在Bean属性值设置好之后和Bean销毁之前做一些事情,
-比如检查Bean中某个属性是否被正常的设置好了。
-Spring框架提供了多种方法让我们可以在Spring Bean的生命周期中执行initialization和pre-destroy方法。
-
-> InitializingBean 和 DisposableBean 接口
-
-- InitializingBean 接口 :
-```java
-public interface InitializingBean {
- void afterPropertiesSet() throws Exception;
-}
-```
-
-- DisposableBean 接口 :
-```java
-public interface DisposableBean {
- void destroy() throws Exception;
-}
-```
-
-实现 InitializingBean 接口的afterPropertiesSet()方法可以在**Bean属性值设置好之后做一些操作**,
-实现DisposableBean接口的destroy()方法可以在**销毁Bean之前做一些操作**。
-
-```java
-public class GiraffeService implements InitializingBean,DisposableBean {
- @Override
- public void afterPropertiesSet() throws Exception {
- System.out.println("执行InitializingBean接口的afterPropertiesSet方法");
- }
- @Override
- public void destroy() throws Exception {
- System.out.println("执行DisposableBean接口的destroy方法");
- }
-}
-```
-这种方法比较简单,但是不建议使用。
-因为这样会**将Bean的实现和Spring框架耦合在一起**。
-
-> init-method 和 destroy-method 方法
-
-配置文件中的配值init-method 和 destroy-method :
-
-```html
-
-
-```
-
-```java
-public class GiraffeService {
- //通过的destroy-method属性指定的销毁方法
- public void destroyMethod() throws Exception {
- System.out.println("执行配置的destroy-method");
- }
- //通过的init-method属性指定的初始化方法
- public void initMethod() throws Exception {
- System.out.println("执行配置的init-method");
- }
-}
-```
-
-需要注意的是自定义的init-method和post-method方法**可以抛异常但是不能有参数**。
-
-这种方式比较推荐,因为可以**自己创建方法,无需将Bean的实现直接依赖于Spring的框架**。
-
-> @PostConstruct 和 @PreDestroy注解
-
-Spring 支持用 @PostConstruct和 @PreDestroy注解来指定 init 和 destroy 方法。
-这两个注解均在javax.annotation 包中。
-为了注解可以生效,
-需要在配置文件中定义
-org.springframework.context.annotation.CommonAnnotationBeanPostProcessor。
-
-```html
-
-```
-
-```java
-public class GiraffeService {
- @PostConstruct
- public void initPostConstruct(){
- System.out.println("执行PostConstruct注解标注的方法");
- }
- @PreDestroy
- public void preDestroy(){
- System.out.println("执行preDestroy注解标注的方法");
- }
-}
-```
-
-### XxxAware接口
-
-有些时候我们需要在 Bean 的初始化中**使用 Spring 框架自身的一些对象**来执行一些操作,
-比如获取 ServletContext 的一些参数,获取 ApplicaitionContext 中的 BeanDefinition 的名字,获取 Bean 在容器中的名字等等。
-为了让 Bean 可以获取到框架自身的一些对象,Spring 提供了一组名为 XxxAware 的接口。
-
-XxxAware接口均继承于org.springframework.beans.factory.Aware标记接口,
-并提供一个将由 Bean 实现的set*方法,
-Spring通过基于setter的依赖注入方式使相应的对象可以被Bean使用。
-
-常见的 XxxAware 接口:
-
-| 接口 | 说明 |
-| :--: | :--: |
-| ApplicationContextAware | 获得ApplicationContext对象,可以用来获取所有BeanDefinition的名字。 |
-| BeanFactoryAware | 获得BeanFactory对象,可以用来检测Bean的作用域。 |
-| BeanNameAware | 获得Bean在配置文件中定义的name。 |
-| ResourceLoaderAware | 获得ResourceLoader对象,可以获得classpath中某个文件。 |
-| ServletContextAware | 在一个MVC应用中可以获取ServletContext对象,可以读取context中的参数。 |
-| ServletConfigAware | 在一个MVC应用中可以获取ServletConfig对象,可以读取config中的参数。 |
-
-```java
-public class GiraffeService implements ApplicationContextAware,
- ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware,
- BeanNameAware, EnvironmentAware, ImportAware, ResourceLoaderAware{
-
- @Override
- public void setBeanClassLoader(ClassLoader classLoader) {
- System.out.println("执行setBeanClassLoader,ClassLoader Name = " + classLoader.getClass().getName());
- }
-
- @Override
- public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
- System.out.println("执行setBeanFactory,setBeanFactory:: giraffe bean singleton=" + beanFactory.isSingleton("giraffeService"));
- }
-
- @Override
- public void setBeanName(String s) {
- System.out.println("执行setBeanName:: Bean Name defined in context="
- + s);
- }
-
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- System.out.println("执行setApplicationContext:: Bean Definition Names="
- + Arrays.toString(applicationContext.getBeanDefinitionNames()));
- }
-
- @Override
- public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
- System.out.println("执行setApplicationEventPublisher");
- }
-
- @Override
- public void setEnvironment(Environment environment) {
- System.out.println("执行setEnvironment");
- }
-
- @Override
- public void setResourceLoader(ResourceLoader resourceLoader) {
- Resource resource = resourceLoader.getResource("classpath:spring-beans.xml");
- System.out.println("执行setResourceLoader:: Resource File Name="
- + resource.getFilename());
- }
-
- @Override
- public void setImportMetadata(AnnotationMetadata annotationMetadata) {
- System.out.println("执行setImportMetadata");
- }
-}
-```
-
-### BeanPostProcessor
-上面的XxxAware接口是**针对某个实现这些接口的Bean定制初始化的过程**,
-Spring同样可以针对容器中的所有Bean,或者某些Bean定制初始化过程,
-只需提供一个实现BeanPostProcessor接口的类即可。
-
-- BeanPostProcessor 接口:
-```java
-public interface BeanPostProcessor{
- //postProcessBeforeInitialization方法会在容器中的Bean初始化之前执行
- public abstract Object postProcessBeforeInitialization(Object obj, String s)
- throws BeansException;
-
- //postProcessAfterInitialization方法在容器中的Bean初始化之后执行
- public abstract Object postProcessAfterInitialization(Object obj, String s)
- throws BeansException;
-}
-```
-
-```java
-public class CustomerBeanPostProcessor implements BeanPostProcessor {
- @Override
- public Object postProcessBeforeInitialization(Object bean, String beanName)
- throws BeansException {
- System.out.println("执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName="
- + beanName);
- return bean;
- }
- @Override
- public Object postProcessAfterInitialization(Object bean, String beanName)
- throws BeansException {
- System.out.println("执行BeanPostProcessor的postProcessAfterInitialization方法,beanName="
- + beanName);
- return bean;
- }
-}
-```
-
-将实现BeanPostProcessor接口的Bean像其他Bean一样定义在配置文件中:
-
-```html
-
-```
-
-### 总结
-Spring Bean的生命周期
-
-1. Bean容器找到配置文件中 Spring Bean 的定义。
-
-2. Bean容器利用Java Reflection API创建一个Bean的实例。
-
-3. 如果涉及到一些属性值,利用set方法设置一些属性值。
-
-4. 如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。
-
-5. 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
-
-6. 如果Bean实现了BeanFactoryAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
-
-7. 与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。
-
-8. 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,
-执行postProcessBeforeInitialization()方法
-
-9. 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
-
-10. 如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。
-
-11. 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,
-执行postProcessAfterInitialization()方法
-
-12. 当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法;
-如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。
-
-
-
-
-
-很多时候我们并不会真的去实现上面说描述的那些接口,
-那么下面我们就除去那些接口,针对 Bean 的单例和非单例来描述下 Bean 的生命周期:
-
-> **1. 单例管理的对象**
-
-scope="singleton",即默认情况下,会在启动容器时(即实例化容器时)时实例化。
-但我们可以指定 lazy-init="true"。这时候,只有在第一次获取 Bean 时才会初始化 Bean,
-即第一次请求该 Bean时才初始化。配置如下:
-
-```html
-
-```
-
-想对所有的默认单例Bean都应用延迟初始化,
-可以在根节点beans 指定default-lazy-init="true",如下所示:
-
-```html
-
-```
-
-默认情况下,Spring 在读取 xml 文件的时候,就会创建对象。
-在创建对象的时候先调用**构造器**,然后调用 **init-method 属性值中所指定的方法**。
-对象在被销毁的时候,会调用 **destroy-method 属性值中所指定的方法**。
-
-- LifeCycleBean :
-```java
-public class LifeCycleBean {
- private String name;
-
- public LifeCycleBean(){
- System.out.println("LifeCycleBean()构造函数");
- }
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- System.out.println("setName()");
- this.name = name;
- }
-
- public void init(){
- System.out.println("this is init of lifeBean");
- }
-
- public void destroy(){
- System.out.println("this is destory of lifeBean " + this);
- }
-}
-```
-
-- 配置文件 :
-```html
-
-```
-
-- 测试 :
-
-```java
-public class LifeTest {
- @Test
- public void test() {
- AbstractApplicationContext context =
- new ClassPathXmlApplicationContext("lifeCycleBean.xml");
- LifeCycleBean life = (LifeCycleBean) context.getBean("lifeCycleBean");
- System.out.println("life:"+life);
- context.close();
- }
-}
-```
-
-- 输出结果:
-
-```html
-LifeBean()构造函数
-this is init of lifeBean
-life:com.southeast.bean.LifeCycleBean@573f2bb1
-this is destory of lifeBean com.southeast.bean.LifeCycleBean@573f2bb1
-```
-
-> **2. 非单例管理的对象**
-
-当 scope= "prototype" 时,容器也会延迟初始化 Bean,
-Spring 读取xml 文件的时候,并不会立刻创建对象,而是在第一次请求该 Bean 时才初始化(如调用getBean方法时)。
-
-在第一次请求每一个 prototype 的 Bean 时,Spring容器都会调用其构造器创建这个对象,
-然后调用init-method属性值中所指定的方法。
-对象销毁的时候,Spring 容器不会帮我们调用任何方法,因为是非单例,
-这个类型的对象有很多个,**Spring容器一旦把这个对象交给你之后,就不再管理这个对象了**。
-
-- 配置文件 :
-```html
-
-```
-
-- 测试:
-
-```java
-public class LifeTest2 {
- @Test
- public void test() {
- AbstractApplicationContext context =
- new ClassPathXmlApplicationContext("lifeCycleBean.xml");
- LifeCycleBean life = (LifeCycleBean) context.getBean("lifeCycleBean");
- System.out.println("life:"+life);
-
- LifeCycleBean life2 = (LifeCycleBean) context.getBean("lifeCycleBeans");
- System.out.println("life2:"+life2);
- context.close();
- }
-}
-```
-
-- 输出结果:
-```html
-LifeBean()构造函数
-this is init of lifeBean
-life:com.southeast.bean.LifeCycleBean@573f2bb1
-LifeBean()构造函数
-this is init of lifeBean
-life2:com.southeast.bean.LifeCycleBean@5ae9a829
-this is destory of lifeBean LifeCycleBean@573f2bb1
-```
-
-作用域为 prototype 的 Bean ,其destroy方法并没有被调用。
-如果 bean 的 scope 设为prototype时,当容器关闭时,destroy 方法不会被调用。
-对于 prototype 作用域的 bean,有一点非常重要,
-
-**Spring 容器可以管理 singleton 作用域下 Bean 的生命周期,
-在此作用域下,Spring 能够精确地知道 Bean 何时被创建,何时初始化完成,以及何时被销毁。
-而对于 prototype 作用域的bean,Spring只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给了客户端的代码管理,
-Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的Bean的生命周期**。
\ No newline at end of file
diff --git "a/docs/Spring/Spring 345円270円270円350円247円201円346円263円250円350円247円243円.md" "b/docs/Spring/Spring 345円270円270円350円247円201円346円263円250円350円247円243円.md"
new file mode 100644
index 0000000..ab2a945
--- /dev/null
+++ "b/docs/Spring/Spring 345円270円270円350円247円201円346円263円250円350円247円243円.md"
@@ -0,0 +1,50 @@
+# Spring 中常见注解
+
+## @Contoller
+
+SpringMVC 中,控制器 Controller 负责处理 DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个 Model,然后再把该 Model 返回给对应的 View 进行展示。
+
+SpringMVC 提供了一个非常简便的定义 Controller 的方法,你无需继承特定的类或者接口,只需使用 @Controller 标记一个类是 Contoller。
+
+## @RequestMapping
+
+使用 @RequestMapping 来映射 URL 到控制器,或者到 Controller 控制器的处理方法上。method 的值一旦指定,则处理方法只对指定的 HTTP method 类型请求处理。
+
+可以为多个方法映射相同的 URI,不同的 HTTP method 类型,Spring MVC 根据请求的 method 类型是可以区分开这些方法的。
+
+## @RequestParam 和 @PathVariable
+
+在 SpringMVC 中,两者的作用都是将 request 里的参数的值绑定到 Controller 里的方法参数中,区别在于 URL 的写法不同。
+
+- 使用 @RequestParam 时,URL 是这样的:
+
+```html
+http://host:port/path?参数名=参数值
+```
+
+- 使用 @PathVariable 时,URL 是这样的:
+
+```html
+http://host:port/path/参数值
+```
+
+## @Autowired
+
+@Autowired 可以对成员变量、成员方法和构造函数进行标注,来完成自动装配工作。
+
+## @Service、 @Contrller、 @Repository 和 @Component
+
+ @Service、 @Contrller、 @Repository 其实这 3 个注解和 @Component 是等效的,用在实现类上:
+
+- @Service 用于标注业务层组件
+- @Controller 用于标注控制层组件
+- @Repository 用于编著数据访问组件
+- @Component 泛指组件,当组件不好归类时,可以使用这个注解进行标注
+
+## @Value
+
+在 Spring 3.0 中,可以通过使用 @Value,对一些如 xxx.properties 文件中的文件,进行键值对的注入。
+
+## @ResponseBody
+
+该注解用于将 Controller 中方法返回的对象,通过适当的 HttpMessageConverter 转换为指定的格式后,写入到 Response 对象的 body s数据区。
\ No newline at end of file
diff --git "a/docs/Spring/SpringBoot 344円273円213円347円273円215円.md" "b/docs/Spring/SpringBoot 344円273円213円347円273円215円.md"
new file mode 100644
index 0000000..e871f0f
--- /dev/null
+++ "b/docs/Spring/SpringBoot 344円273円213円347円273円215円.md"
@@ -0,0 +1,22 @@
+# SpringBoot 介绍
+
+**Spring Boot 简化 Spring 应用程序开发**。从本质上来说,Spring Boot 就是 Spring,基于 **"习惯优于配置"** 理念使得项目快速运行起来。
+
+所谓 "习惯优先配置" 理念即项目中存在大量的配置,此外还内置了一个习惯性的配置,让程序开发人员无需手动进行配置。
+
+## 自动配置
+
+针对很多 Spring 应用程序常见的应用功能,Spring Boot 能够自动提供相关配置。当开发人员在 pom.xml 文件汇中添加 starter 依赖后, maven 或者 gradle 会自动下载很多 jar 包到 classpath 中。
+
+当 Spring Boot 检测到特定类的存在,就会针对这个应用做一定的配置,自动创建和织入需要 Spring Bean 到程序上下文中。@SpringBootApplication 开启 Spring 组件扫描和 Spring Boot 的自动配置功能。
+
+## 起步依赖
+
+告诉 SpringBoot 需要什么功能,它就能引入需要的库,利用了传递依赖解析,把常用库聚合在一起,组成了几个特定功能而定制的依赖,不需要担心版本不兼容的问题。
+
+## 命令行界面
+
+这是 SpringBoot 的可选性质,借此你只需要能完成完整的应用程序,无需传统项目构建。
+
+
+
From d69765fa71a287e44270d6e5b095317e0f19041b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=9C=E5=8E=9A=E5=AE=89?=
<33805265+duhouan@users.noreply.github.com>
Date: 2019年6月28日 23:39:06 +0800
Subject: [PATCH 09/17] =?UTF-8?q?Update=20=E5=BC=80=E5=8F=91=E6=A1=86?=
=?UTF-8?q?=E6=9E=B6-=E7=9B=AE=E5=BD=95.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...17221円346円241円206円346円236円266円-347円233円256円345円275円225円.md" | 3 +++
1 file changed, 3 insertions(+)
diff --git "a/docs/345円274円200円345円217円221円346円241円206円346円236円266円-347円233円256円345円275円225円.md" "b/docs/345円274円200円345円217円221円346円241円206円346円236円266円-347円233円256円345円275円225円.md"
index 3ad4caf..5679326 100644
--- "a/docs/345円274円200円345円217円221円346円241円206円346円236円266円-347円233円256円345円275円225円.md"
+++ "b/docs/345円274円200円345円217円221円346円241円206円346円236円266円-347円233円256円345円275円225円.md"
@@ -7,6 +7,9 @@
- [Spring AOP](./Spring/02SpringAOP.md)
- [Spring 事务管理](./Spring/04Spring事务管理.md)
- [Spring 中 Bean 的生命周期](./Spring/05Spring中Bean的生命周期.md)
+- [Spring 中涉及到的设计模式](./Spring/Spring%20中涉及到的设计模式.md)
+- [Spring 常见注解](./Spring/Spring%20常见注解.md)
+- [SpringBoot 介绍](./Spring/SpringBoot%20介绍.md)
### SpringMVC
From 7cfd28389d8183897c6445f701eb067b4ff21689 Mon Sep 17 00:00:00 2001
From: IvanLu1024 <501275190@qq.com>
Date: Wed, 3 Jul 2019 20:57:29 +0800
Subject: [PATCH 10/17] Update Redis.md
---
docs/DataBase/Redis.md | 40 ++++++++++++++++++++++++++++++++--------
1 file changed, 32 insertions(+), 8 deletions(-)
diff --git a/docs/DataBase/Redis.md b/docs/DataBase/Redis.md
index 7d4eefe..7cb7a05 100644
--- a/docs/DataBase/Redis.md
+++ b/docs/DataBase/Redis.md
@@ -871,11 +871,13 @@ Cache Aside Pattern 最经典的缓存结合数据库读写的模式。
问题:**为什么是删除缓存,而不是更新缓存?**
+原因:如果每次更新了数据库,都要更新缓存【这里指的是频繁更新的场景,这会耗费一定的性能】,倒不如直接删除掉。等再次读取时,缓存里没有,那再去数据库找,在数据库找到再写到缓存里边(体现**懒加载**)。
+
举个例子,一个缓存涉及的表的字段,在 1 分钟内就修改了 100 次,那么缓存更新 100 次;但是这个缓存在 1 分钟内只被**读取**了 1 次,有**大量的冷数据**。实际上,如果你只是删除缓存的话,那么在 1 分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低。
其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,**不要每次都重新做复杂的计算,不管它会不会用到,而是让它在需要被使用时才重新计算**。
-### 简单的数据不一致问题
+### 先更新数据库,再删除缓存
> **问题**
@@ -885,27 +887,49 @@ Cache Aside Pattern 最经典的缓存结合数据库读写的模式。
-> **解决方案**
+如果在高并发的场景下,出现数据库与缓存数据不一致的**概率特别低**,也不是没有:
+
+- 缓存**刚好**失效
+- 线程A查询数据库,得一个旧值
+- 线程B将新值写入数据库
+- 线程B删除缓存
+- 线程A将查到的旧值写入缓存
+
+要达成上述情况,还是说一句**概率特别低**:
-**先删除缓存,再修改数据库**。
+> 因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,**而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存**,所有的这些条件都具备的概率基本并不大。
-如果数据库修改失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。读的时候缓存中没有数据,就会读数据库中旧数据,然后更新到缓存中。
+对于这种策略,其实是一种设计模式:`Cache Aside Pattern`
-### 复杂的数据不一致问题
+> **删除缓存失败的解决思路**
+
+- 将需要删除的key发送到消息队列中
+- 自己消费消息,获得需要删除的key
+- **不断重试删除操作,直到成功**
+
+### 先删除缓存,再更新数据库
> **问题**
数据更新,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。随后更新数据的程序完成了数据库的修改,此时,数据库和缓存中的数据又不一致了。
-只有在对一个数据在**并发**的进行读写的时候,才可能会出现这种问题。如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现这种问题。
+并发场景下分析:
+
+- 线程A删除了缓存
+- 线程B查询,发现缓存已不存在
+- 线程B去数据库查询得到旧值
+- 线程B将旧值写入缓存
+- 线程A将新值写入数据库
+
+所以也会导致数据库和缓存不一致的问题。
> **解决方案**
-读请求和写请求串行化,串到一个**内存队列**里去,这样就可以保证一定不会出现不一致的情况。串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
+读请求和写请求串行化,串到一个**内存队列**里去,这样就可以保证一定不会出现不一致的情况。串行化之后,就会导致系统的**吞吐量会大幅度的降低**,用比正常情况下多几倍的机器去支撑线上的一个请求。
### 比较
-- 先更新数据库,再删除缓存:在高并发下表现优异,在原子性被破坏时表现不如意。
+- 先更新数据库,再删除缓存(`Cache Aside Pattern`设计模式):在高并发下表现优异,在原子性被破坏时表现不如意。
- 先删除缓存,再更新数据库:在高并发下表现不如意,在原子性被破坏时表现优异。
From 8e45b7d63b228d240d4250afbbb59154c588e461 Mon Sep 17 00:00:00 2001
From: IvanLu1024 <501275190@qq.com>
Date: Wed, 3 Jul 2019 20:57:34 +0800
Subject: [PATCH 11/17] =?UTF-8?q?Update=20Java-=E8=99=9A=E6=8B=9F=E6=9C=BA?=
=?UTF-8?q?.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
"docs/Java/Java-350円231円232円346円213円237円346円234円272円.md" | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git "a/docs/Java/Java-350円231円232円346円213円237円346円234円272円.md" "b/docs/Java/Java-350円231円232円346円213円237円346円234円272円.md"
index 768d022..9598d72 100644
--- "a/docs/Java/Java-350円231円232円346円213円237円346円234円272円.md"
+++ "b/docs/Java/Java-350円231円232円346円213円237円346円234円272円.md"
@@ -221,7 +221,7 @@ Hotspot虚拟机的对象头包括两部分信息:
一部分用于存储对象自身的运行时数据(哈希码、GC分代年龄、锁状态标志等等),
-另一部分是类型指针,即对象指向它的**类元数据的指针**,虚拟机通过这个指针来**确定这个对象是那个类的实例**。
+另一部分是类型指针,即对象指向它的**类元数据的指针**,虚拟机通过这个指针来**确定这个对象是哪个类的实例**。
- **实例数据**
@@ -241,7 +241,7 @@ Hotspot虚拟机的对象头包括两部分信息:
- **使用句柄**
-如果使用句柄的话,那么 **Java 堆**中将会划分出一块内存来作为句柄池,reference 中存储的就是**对象的句柄地址**,而句柄中包含了对象实例数据与类型数据各自的具体地址信息 。
+如果使用[句柄](https://zh.wikipedia.org/wiki/%E5%8F%A5%E6%9F%84)的话,那么 **Java 堆**中将会划分出一块内存来作为句柄池,reference 中存储的就是**对象的句柄地址**,而句柄中包含了对象实例数据与类型数据各自的具体地址信息 。
From 0ac0d4a791c438b0464a03b15b61334c8de8c823 Mon Sep 17 00:00:00 2001
From: DuHouAn <18351926682@163.com>
Date: Sun, 7 Jul 2019 19:27:51 +0800
Subject: [PATCH 12/17] Java-Notes
---
...code-Database 351円242円230円350円247円243円.md" | 435 +--
docs/DataBase/Redis.md | 2 +-
"docs/Java/Java 345円271円266円345円217円221円.md" | 2494 +++++++++++++++++
3 files changed, 2743 insertions(+), 188 deletions(-)
create mode 100644 "docs/Java/Java 345円271円266円345円217円221円.md"
diff --git "a/docs/DataBase/09Leetcode-Database 351円242円230円350円247円243円.md" "b/docs/DataBase/09Leetcode-Database 351円242円230円350円247円243円.md"
index 5b4a3d0..4597546 100644
--- "a/docs/DataBase/09Leetcode-Database 351円242円230円350円247円243円.md"
+++ "b/docs/DataBase/09Leetcode-Database 351円242円230円350円247円243円.md"
@@ -1,27 +1,8 @@
-
-* [595. Big Countries](#595-big-countries)
-* [627. Swap Salary](#627-swap-salary)
-* [620. Not Boring Movies](#620-not-boring-movies)
-* [596. Classes More Than 5 Students](#596-classes-more-than-5-students)
-* [182. Duplicate Emails](#182-duplicate-emails)
-* [196. Delete Duplicate Emails](#196-delete-duplicate-emails)
-* [175. Combine Two Tables](#175-combine-two-tables)
-* [181. Employees Earning More Than Their Managers](#181-employees-earning-more-than-their-managers)
-* [183. Customers Who Never Order](#183-customers-who-never-order)
-* [184. Department Highest Salary](#184-department-highest-salary)
-* [176. Second Highest Salary](#176-second-highest-salary)
-* [177. Nth Highest Salary](#177-nth-highest-salary)
-* [178. Rank Scores](#178-rank-scores)
-* [180. Consecutive Numbers](#180-consecutive-numbers)
-* [626. Exchange Seats](#626-exchange-seats)
-
-
-
-# 595. Big Countries
-
-https://leetcode.com/problems/big-countries/description/
-
-## Description
+# 1、大的国家(595)
+
+[595. 大的国家](https://leetcode-cn.com/problems/big-countries/)
+
+- 问题描述
```html
+-----------------+------------+------------+--------------+---------------+
@@ -46,7 +27,7 @@ https://leetcode.com/problems/big-countries/description/
+--------------+-------------+--------------+
```
-## SQL Schema
+- SQL Schema
```sql
DROP TABLE
@@ -62,24 +43,22 @@ VALUES
( 'Angola', 'Africa', '1246700', '20609294', '1009900000' );
```
-## Solution
+- 解题
```sql
-SELECT name,
- population,
- area
-FROM
- World
-WHERE
- area> 3000000
- OR population> 25000000;
+# 思路:
+# 1、根据样例,我们知道。查询字段是 name population 和 area
+# 2、查询条件是 area> 3000000 || population> 25000000
+SELECT name,population,area
+FROM World
+WHERE area> 3000000 || population> 25000000;
```
-# 627. Swap Salary
+# 2、交换工资(627)
-https://leetcode.com/problems/swap-salary/description/
+[627. 交换工资](https://leetcode-cn.com/problems/swap-salary/)
-## Description
+- 问题描述
```html
| id | name | sex | salary |
@@ -101,7 +80,7 @@ https://leetcode.com/problems/swap-salary/description/
| 4 | D | m | 500 |
```
-## SQL Schema
+- SQL Schema
```sql
DROP TABLE
@@ -116,18 +95,22 @@ VALUES
( '4', 'D', 'f', '500' );
```
-## Solution
+- 解题
```sql
-UPDATE salary
-SET sex = CHAR ( ASCII(sex) ^ ASCII( 'm' ) ^ ASCII( 'f' ) );
+# 思路:
+# l、利用位运算:比如若 x^b^a = a,则判断 x=b
+# 2、利用 ASCII 函数将字符转换为数值进行运算,然后再利用 CHAR 函数将数值转换为字符
+UPDATE
+salary
+SET sex = CHAR(ASCII(sex)^ASCII('m')^ASCII('f'));
```
-# 620. Not Boring Movies
+# 3、有趣的电影(62)
-https://leetcode.com/problems/not-boring-movies/description/
+[620. 有趣的电影](https://leetcode-cn.com/problems/not-boring-movies/)
-## Description
+- 问题描述
```html
@@ -153,7 +136,7 @@ https://leetcode.com/problems/not-boring-movies/description/
+---------+-----------+--------------+-----------+
```
-## SQL Schema
+- SQL Schema
```sql
DROP TABLE
@@ -169,25 +152,23 @@ VALUES
( 5, 'House card', 'Interesting', 9.1 );
```
-## Solution
+- 解题
```sql
-SELECT
- *
-FROM
- cinema
-WHERE
- id % 2 = 1
- AND description != 'boring'
-ORDER BY
- rating DESC;
+#思路:
+#1、观察测试用例的查询结果,我们知道,其实查询的是所有的字段
+#2、id 为奇数,则查询条件为 id % 2 = 1
+SELECT id,movie,description,rating
+FROM cinema
+WHERE id%2=1 AND description != 'boring'
+ORDER BY rating DESC;
```
-# 596. Classes More Than 5 Students
+# 4、超过5名学生的课(596)
-https://leetcode.com/problems/classes-more-than-5-students/description/
+[596. 超过5名学生的课](https://leetcode-cn.com/problems/classes-more-than-5-students/)
-## Description
+- 问题描述
```html
+---------+------------+
@@ -215,7 +196,7 @@ https://leetcode.com/problems/classes-more-than-5-students/description/
+---------+
```
-## SQL Schema
+- SQL Schema
```sql
DROP TABLE
@@ -235,24 +216,23 @@ VALUES
( 'I', 'Math' );
```
-## Solution
+- 解答
```sql
-SELECT
- class
-FROM
- courses
-GROUP BY
- class
-HAVING
- count( DISTINCT student )>= 5;
+# 思路:
+# 1、很显然要按照 class 进行分组
+# 2、然后按照分组后的 class 来统计学生的人数
+SELECT class
+FROM courses
+GROUP BY class
+HAVING COUNT( DISTINCT student)>= 5;
```
-# 182. Duplicate Emails
+# 5、查找重复的电子邮箱(182)
-https://leetcode.com/problems/duplicate-emails/description/
+[182. 查找重复的电子邮箱](https://leetcode-cn.com/problems/duplicate-emails/)
-## Description
+- 问题描述
邮件地址表:
@@ -276,7 +256,7 @@ https://leetcode.com/problems/duplicate-emails/description/
+---------+
```
-## SQL Schema
+- SQL Schema
```sql
DROP TABLE
@@ -290,24 +270,23 @@ VALUES
( 3, 'a@b.com' );
```
-## Solution
+- 解题
```sql
-SELECT
- Email
-FROM
- Person
-GROUP BY
- Email
-HAVING
- COUNT( * )>= 2;
+# 思路:与 596 题类似
+# 1、按照 mail 进行分组
+# 2、统计出现次数>=2 就是重复的邮件
+SELECT Email
+FROM Person
+GROUP BY EMAIL
+HAVING COUNT(id)>= 2;
```
-# 196. Delete Duplicate Emails
+# *6、删除重复的电子邮箱(196)
https://leetcode.com/problems/delete-duplicate-emails/description/
-## Description
+- 问题描述
邮件地址表:
@@ -332,15 +311,14 @@ https://leetcode.com/problems/delete-duplicate-emails/description/
+----+------------------+
```
-## SQL Schema
+- SQL Schema
与 182 相同。
-## Solution
-
-连接:
+- 解题:
```sql
+# 思路一:将一张表看成两张表来进行操作
DELETE p1
FROM
Person p1,
@@ -350,33 +328,37 @@ WHERE
AND p1.Id> p2.Id
```
-子查询:
-
```sql
+# 思路二:
+# 第一步:根据 email 进行分组,获取 email 对应的最小 id,一个 email 对应一个最小的 id
+SELECT min( id ) AS id FROM Person GROUP BY email;
+
+# 第二步:删除不在该 id 集合中的数据
DELETE
FROM
Person
WHERE
id NOT IN ( SELECT id FROM ( SELECT min( id ) AS id FROM Person GROUP BY email ) AS m );
+# 应该注意的是上述解法额外嵌套了一个 SELECT 语句。
+# 如果不这么做,会出现错误:You can't specify target table 'Person' for update in FROM clause。
```
-应该注意的是上述解法额外嵌套了一个 SELECT 语句,如果不这么做,会出现错误:You can't specify target table 'Person' for update in FROM clause。以下演示了这种错误解法。
-
```sql
DELETE
FROM
Person
WHERE
- id NOT IN ( SELECT min( id ) AS id FROM Person GROUP BY email );
+ id NOT IN ( SELECT id FROM ( SELECT min( id ) AS id FROM Person GROUP BY email ) AS m );
+# 发生 You can't specify target table 'Person' for update in FROM clause。
```
参考:[pMySQL Error 1093 - Can't specify target table for update in FROM clause](https://stackoverflow.com/questions/45494/mysql-error-1093-cant-specify-target-table-for-update-in-from-clause)
-# 175. Combine Two Tables
+# 7、组合两个表(175)
https://leetcode.com/problems/combine-two-tables/description/
-## Description
+- 问题描述:
Person 表:
@@ -407,7 +389,7 @@ AddressId is the primary key column for this table.
查找 FirstName, LastName, City, State 数据,而不管一个用户有没有填地址信息。
-## SQL Schema
+- SQL Schema
```sql
DROP TABLE
@@ -426,27 +408,35 @@ VALUES
( 1, 2, 'New York City', 'New York' );
```
-## Solution
-
-使用左外连接。
+- 解题:
```sql
-SELECT
- FirstName,
- LastName,
- City,
- State
-FROM
- Person P
- LEFT JOIN Address A
- ON P.PersonId = A.PersonId;
+# 思路:左外连接
+SELECT p.FirstName,p.LastName,a.City,a.State
+FROM Person p
+LEFT JOIN Address a
+ON p.PersonId=a.PersonId;
```
-# 181. Employees Earning More Than Their Managers
+- 扩展:
+
+ * 内连接:返回两张表的交集部分。
+
+
+
+ * 左连接:
+
+
+
+ * 右连接:
+
+
+
+# *8、超过经理收入的员工(181)
https://leetcode.com/problems/employees-earning-more-than-their-managers/description/
-## Description
+- 问题描述:
Employee 表:
@@ -463,7 +453,7 @@ Employee 表:
查找薪资大于其经理薪资的员工信息。
-## SQL Schema
+- SQL Schema
```sql
DROP TABLE
@@ -478,23 +468,45 @@ VALUES
( 4, 'Max', 90000, NULL );
```
-## Solution
+- 解题:
```sql
-SELECT
- E1.NAME AS Employee
-FROM
- Employee E1
- INNER JOIN Employee E2
- ON E1.ManagerId = E2.Id
- AND E1.Salary> E2.Salary;
+# 思路:Employee e1 INNER JOIN Employee e2 ON e1.managerid = e2.id 比如
+
++----+-------+--------+-----------+
+| Id | Name | Salary | ManagerId |
++----+-------+--------+-----------+
+| 1 | Joe | 70000 | 3 |
+| 2 | Henry | 80000 | 4 |
+| 3 | Sam | 60000 | NULL |
+| 4 | Max | 90000 | NULL |
++----+-------+--------+-----------+
+
+根据 e1.managerid = e2.id 条件进行内连接后,得到
+
++----+-------+--------+-----------+-------+--------+-----------+
+| Id | Name | Salary | ManagerId | Name | Salary | ManagerId |
++----+-------+--------+-----------+-------+--------+-----------+
+| 1 | Joe | 70000 | 3 | Sam | 60000 | NULL |
+| 2 | Henry | 80000 | 4 | Max | 90000 | NULL |
++----+-------+--------+-----------+-------+--------+-----------+
```
-# 183. Customers Who Never Order
+```sql
+SELECT
+ e1.name AS Employee # 注意:这里的 Employee 是给该字段起的别名,实际上查找的是姓名
+FROM
+ Employee e1
+ INNER JOIN Employee e2
+ ON e1.managerid = e2.id
+ AND e1.salary> e2.salary;
+```
+
+# 9、从不订购的客户(183)
https://leetcode.com/problems/customers-who-never-order/description/
-## Description
+- 问题描述:
Curstomers 表:
@@ -531,7 +543,7 @@ Orders 表:
+-----------+
```
-## SQL Schema
+- SQL Schema
```sql
DROP TABLE
@@ -554,37 +566,34 @@ VALUES
( 2, 1 );
```
-## Solution
-
-左外链接
+- 解答
```sql
+# 解法一:左外连接
SELECT
- C.Name AS Customers
+ c.Name AS Customers
FROM
- Customers C
- LEFT JOIN Orders O
- ON C.Id = O.CustomerId
+ Customers c
+ LEFT JOIN Orders o
+ ON c.Id = o.CustomerId
WHERE
- O.CustomerId IS NULL;
+ o.CustomerId IS NULL;
```
-子查询
-
```sql
-SELECT
- Name AS Customers
-FROM
+# 解法二:子查询方式
+SELECT Name AS Customers
+FROM
Customers
-WHERE
- Id NOT IN ( SELECT CustomerId FROM Orders );
+WHERE
+ Id NOT IN (SELECT CustomerId FROM Orders);
```
-# 184. Department Highest Salary
+# *10、部门工资最高的员工(184)
https://leetcode.com/problems/department-highest-salary/description/
-## Description
+- 问题描述:
Employee 表:
@@ -621,7 +630,7 @@ Department 表:
+------------+----------+--------+
```
-## SQL Schema
+- SQL Schema
```sql
DROP TABLE IF EXISTS Employee;
@@ -640,32 +649,42 @@ VALUES
( 2, 'Sales' );
```
-## Solution
+- 解题:
-创建一个临时表,包含了部门员工的最大薪资。可以对部门进行分组,然后使用 MAX() 汇总函数取得最大薪资。
+```sql
+# 创建一个临时表,包含了部门员工的最大薪资。
+# 可以对部门进行分组,然后使用 MAX() 汇总函数取得最大薪资。
-之后使用连接找到一个部门中薪资等于临时表中最大薪资的员工。
+SELECT DepartmentId, MAX( Salary ) Salary FROM Employee GROUP BY DepartmentId;
+
+# 结果:
++--------------+--------+
+| DepartmentId | Salary |
++--------------+--------+
+| 1 | 90000 |
+| 2 | 80000 |
++--------------+--------+
+```
+
+使用连接找到一个部门中薪资等于临时表中最大薪资的员工。
```sql
-SELECT
- D.NAME Department,
- E.NAME Employee,
- E.Salary
-FROM
- Employee E,
- Department D,
- ( SELECT DepartmentId, MAX( Salary ) Salary FROM Employee GROUP BY DepartmentId ) M
-WHERE
- E.DepartmentId = D.Id
- AND E.DepartmentId = M.DepartmentId
- AND E.Salary = M.Salary;
+SELECT d.name as Department, e.name as Employee, m.Salary
+FROM
+ Employee e,
+ Department d,
+ (SELECT DepartmentId, MAX( Salary ) Salary FROM Employee GROUP BY DepartmentId) m
+WHERE
+ e.DepartmentId=d.Id
+ AND e.DepartmentId=m.DepartmentId
+ AND e.Salary=m.Salary;
```
-# 176. Second Highest Salary
+# 11、第二高的薪水(176)
https://leetcode.com/problems/second-highest-salary/description/
-## Description
+- 问题描述:
```html
+----+--------+
@@ -689,7 +708,7 @@ https://leetcode.com/problems/second-highest-salary/description/
没有找到返回 null 而不是不返回数据。
-## SQL Schema
+- SQL Schema
```sql
DROP TABLE
@@ -703,41 +722,54 @@ VALUES
( 3, 300 );
```
-## Solution
+- 解题:
-为了在没有查找到数据时返回 null,需要在查询结果外面再套一层 SELECT。
+```sql
+# 查询所有 salary 按照降序排列
+SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC;
+```
+
+```sql
+# 要获取第二高的的薪水,就是获取第二个元素,
+# 使用 limit start,count; start:开始查询的位置,count 是查询多少条语句
+SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1,1;
+```
```sql
+# 为了在没有查找到数据时返回 null,需要在查询结果外面再套一层 SELECT。
SELECT
( SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1, 1 ) SecondHighestSalary;
```
-# 177. Nth Highest Salary
+# 12、第N高的薪水(177)
+
+[177. 第N高的薪水](https://leetcode-cn.com/problems/nth-highest-salary/)
-## Description
+- 问题描述:
查找工资第 N 高的员工。
-## SQL Schema
+- SQL Schema
同 176。
-## Solution
+- 解题:
```sql
+# 思路:其实与 176题目类似
CREATE FUNCTION getNthHighestSalary ( N INT ) RETURNS INT BEGIN
-SET N = N - 1;
+SET N = N - 1; #注意 LIMIT 的 start 是从 0 开始的,第 N 实际上是第 (N-1)
RETURN ( SELECT ( SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT N, 1 ) );
END
```
-# 178. Rank Scores
+# 13、分数排名(178)
https://leetcode.com/problems/rank-scores/description/
-## Description
+- 问题描述:
得分表:
@@ -769,7 +801,7 @@ https://leetcode.com/problems/rank-scores/description/
+-------+------+
```
-## SQL Schema
+- SQL Schema
```sql
DROP TABLE
@@ -786,9 +818,11 @@ VALUES
( 6, 3.65 );
```
-## Solution
+- 解题:
```sql
+#思路:关键在于如何统计 Rank
+#这里使用同一张表进行内连接,注意连接的条件 S1.score <= S2.score 就是为了方便统计 Rank SELECT S1.score, COUNT( DISTINCT S2.score ) Rank @@ -802,11 +836,9 @@ ORDER BY S1.score DESC; ``` -# 180. Consecutive Numbers - -https://leetcode.com/problems/consecutive-numbers/description/ +# *14、连续出现的数字(180) -## Description +- 问题描述: 数字表: @@ -834,7 +866,7 @@ https://leetcode.com/problems/consecutive-numbers/description/ +-----------------+ ``` -## SQL Schema +- SQL Schema ```sql DROP TABLE @@ -852,26 +884,55 @@ VALUES ( 7, 2 ); ``` -## Solution +- 解题 + +- ```sql -SELECT - DISTINCT L1.num ConsecutiveNums +# 思路:要求是连续出现 3 次,可使用 3 张该表 +SELECT L1.Num ConsecutiveNums FROM - Logs L1, - Logs L2, - Logs L3 -WHERE L1.id = l2.id - 1 - AND L2.id = L3.id - 1 - AND L1.num = L2.num - AND l2.num = l3.num; + Logs L1, + Logs L2, + Logs L3 +WHERE + L1.Id = L2.Id-1 + AND L2.ID = L3.Id-1 + AND L1.Num = L2.Num + AND L2.Num = L3.Num; +# 判断条件 Id 是不相同的,但是 Num 是相同的,并且 Id 是连续变化的 +``` + +```sql +# 由于要求是至少出现 3 次,所以需要 DISTINCT 进行去重 +SELECT DISTINCT L1.Num ConsecutiveNums +FROM + Logs L1, + Logs L2, + Logs L3 +WHERE + L1.Id = L2.Id-1 + AND L2.ID = L3.Id-1 + AND L1.Num = L2.Num + AND L2.Num = L3.Num; +``` + +```sql +# 另外一种写法 +SELECT DISTINCT L1.Num ConsecutiveNums +FROM + Logs L1 + LEFT JOIN Logs L2 ON L1.Id = L2.Id-1 + LEFT JOIN Logs L3 ON L1.Id = L3.Id-2 +WHERE L1.Num = L2.Num + AND L2.Num = L3.Num; ``` -# 626. Exchange Seats +# 15、换座位(626)(了解) https://leetcode.com/problems/exchange-seats/description/ -## Description +- 问题描述: seat 表存储着座位对应的学生。 @@ -901,7 +962,7 @@ seat 表存储着座位对应的学生。 +---------+---------+ ``` -## SQL Schema +- SQL Schema ```sql DROP TABLE @@ -917,7 +978,7 @@ VALUES ( '5', 'Jeames' ); ``` -## Solution +- 解题: 使用多个 union。 diff --git a/docs/DataBase/Redis.md b/docs/DataBase/Redis.md index 7d4eefe..4d365d4 100644 --- a/docs/DataBase/Redis.md +++ b/docs/DataBase/Redis.md @@ -10,7 +10,7 @@ Redis 支持很多特性,例如将内存中的数据持久化到硬盘中, 主要从"高性能"和"高并发"这两点来看待这个问题。 -性能:假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,那么同步改变缓存中相应的数据即可! +性能:假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。**操作缓存就是直接操作内存**,所以速度相当快。如果数据库中的对应数据改变的之后,那么同步改变缓存中相应的数据即可! 并发:高并发情况下,所有请求直接访问数据库,数据库会出现连接异常,直接操作缓存能够承受的请求是远远大于直接访问数据库的,因此,可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存而不用经过数据库。 diff --git "a/docs/Java/Java 345円271円266円345円217円221円.md" "b/docs/Java/Java 345円271円266円345円217円221円.md" new file mode 100644 index 0000000..0901273 --- /dev/null +++ "b/docs/Java/Java 345円271円266円345円217円221円.md" @@ -0,0 +1,2494 @@ +## 一、进程与线程 + +### 进程与线程的区别 + +> **进程与线程的由来**
+
+- 串行:初期的计算机只能串行执行任务,并且需要长时间等待用户输入
+- 批处理:预先将用户的指令集中成清单,批量串行处理用户指令,仍然无法并发执行
+- 进程:**进程独占内存空间**,保存各自运行状态,相互间**互不干扰**并且可以互相**切换**,为并发处理任务提供了可能
+- 线程:共享进程的内存资源,相互间切换更快速,支持更细粒度的任务控制,使进程内的子任务得以并发执行
+
+> **进程和线程的区别**
+
+**进程是资源分配的最小单位,线程是 CPU 调度的最小单位**
+
+- 所有与进程相关的资源,都被记录在 PCB 中
+
+
+
+- 进程是抢占处理机的调度单位;线程属于某个进程,共享其资源
+- 线程只由堆栈寄存器、程序计数器和 TCP 组成
+
+
+
+> **总结**
+
+- 线程不能看做独立应用,而进程可看做独立应用
+- 进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径
+- 线程没有独立的地址空间,多进程程序比多线程程序健壮
+- 进程的切换比线程切换开销大
+
+> **多进程与多线程**
+
+- 多进程的意义
+
+CPU 在某个时间点上只能做一件事情,计算机是在进程 1 和 进程 2 间做着频繁切换,且切换速度很快,所以,我们感觉进程 1 和进程 2 在同时进行,其实并不是同时执行的。
+
+**多进程的作用不是提高执行速度,而是提高 CPU 的使用率**。
+
+- 多线程的意义
+
+ 多个线程共享同一个进程的资源(堆内存和方法区),但是栈内存是独立的,一个线程一个栈。所以他们仍然是在抢 CPU 的资源执行。一个时间点上只有能有一个线程执行。而且谁抢到,这个不一定,所以,造成了线程运行的随机性。
+
+**多线程的作用不是提高执行速度,而是为了提高应用程序的使用率**。
+
+### Java 进程与线程的关系
+
+- Java 对操作系统提供的功能进行封装,包括进程和线程
+- 运行程序会产生一个进程,进程包含至少一个线程
+- 每个进程对应一个 JVM 实例多个线程共享 JVM 里的堆
+- Java 采用单线程编程模型,程序会自动创建主线程
+- 主线程可以创建子线程,原则上要后与子线程完成执行
+
+## 二、使用线程
+
+- 继承 Thread 类;
+
+- 实现 Runnable 接口;
+- 实现 Callable 接口。
+
+实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。
+
+### 继承 Thread 类
+
+需要实现 run() 方法,因为 Thread 类实现了 Runnable 接口。
+
+当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。
+
+```java
+public class MyThread extends Thread{
+ private void attack() {
+ System.out.println("Fight");
+ System.out.println("Current Thread is : " + Thread.currentThread().getName());
+ }
+
+ @Override
+ public void run() { //重写 run() 方法
+ attack();
+ }
+}
+```
+
+> **Java 中 start 和 run 方法的区别**
+
+- 调用 start 方法会创建一个新的子线程并启动
+- run 方法只是 Thread 只是 Thread 的一个普通方法
+
+
+
+```java
+public class MyThreadTest {
+ public static void main(String[] args) {
+ Thread t = new MyThread();
+
+ System.out.println("current main thread is : " + Thread.currentThread().getName());
+ t.run(); //调用 run() 方法
+ }
+}
+//输出结果:
+//current main thread is : main
+//Fight
+//Current Thread is : main
+```
+
+```java
+public class MyThreadTest2 {
+ public static void main(String[] args) {
+ Thread t = new MyThread();
+
+ System.out.println("current main thread is : " + Thread.currentThread().getName());
+ t.start(); //调用 run() 方法
+ }
+}
+//输出结果:
+//current main thread is : main
+//Fight
+//Current Thread is : Thread-0
+```
+
+### 实现 Runnable 接口
+
+需要实现 run() 方法。
+
+通过 Thread 调用 start() 方法来启动线程。
+
+```java
+public class MyRunnable implements Runnable {
+ public void run() {
+ // ...
+ }
+}
+```
+
+```java
+public static void main(String[] args) {
+ MyRunnable instance = new MyRunnable();
+ Thread thread = new Thread(instance);
+ thread.start();
+}
+```
+
+> **实现接口 VS 继承 Thread**
+
+实现接口会更好一些,因为:
+
+- Thread 是实现了 Runnable 接口的类,使得 run 支持多线程
+
+- Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
+- 类可能只要求可执行就行,继承整个 Thread 类开销过大。
+
+### 实现 Callable 接口
+
+与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
+
+```java
+public class MyCallable implements Callable {
+ public Integer call() {
+ return 123;
+ }
+}
+```
+
+```java
+public static void main(String[] args) throws ExecutionException, InterruptedException {
+ MyCallable mc = new MyCallable();
+ FutureTask ft = new FutureTask(mc);
+ Thread thread = new Thread(ft);
+ thread.start();
+ System.out.println(ft.get());
+}
+```
+
+> **实现处理线程的返回值**
+
+- **主线程等待法**
+
+```java
+public class CycleWait implements Runnable{
+ private String value;
+
+ @Override
+ public void run() {
+ try {
+ Thread.currentThread().sleep(5000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ value = "we have data now";
+ }
+}
+```
+
+```java
+public static void main(String[] args) {
+ CycleWait cycleWait = new CycleWait();
+ Thread t = new Thread(cycleWait);
+ t.start();
+
+ System.out.println("value:"+cycleWait.value);
+ //输出结果:
+ //value:null
+ }
+```
+
+```java
+ public static void main(String[] args) throws InterruptedException {
+ CycleWait cycleWait = new CycleWait();
+ Thread t = new Thread(cycleWait);
+ t.start();
+
+ while (cycleWait.value == null){ //主线程等待法
+ Thread.sleep(1000);
+ }
+ System.out.println("value:"+cycleWait.value);
+ //输出结果:
+ //value:we have data now
+ }
+```
+
+- **使用 Thread 的 join() 阻塞当前线程以等待子线程处理完毕**
+
+```java
+public static void main(String[] args) {
+ CycleWait cycleWait = new CycleWait();
+ Thread t = new Thread(cycleWait);
+ t.start();
+ t.join(); //阻塞当前线程以等待子线程执行完毕
+ System.out.println("value:"+cycleWait.value);
+ //输出结果:
+ //value:we have data now
+ }
+```
+
+- **通过 Callable 接口实现:通过 FutureTask 或者线程池获取**
+
+方式一:通过 FutureTask 获取
+
+```java
+public class CycleWait2 implements Callable{
+ private String value;
+ @Override
+ public String call() throws Exception {
+ try {
+ Thread.currentThread().sleep(5000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ value = "we have data now";
+ return value;
+ }
+
+ public static void main(String[] args) throws ExecutionException, InterruptedException {
+ Callable cycleWait2 = new CycleWait2();
+ FutureTask ft = new FutureTask(cycleWait2);
+ Thread t = new Thread(ft);
+ t.start();
+
+ if(!ft.isDone()){
+ System.out.println("task has not finished,please wait!");
+ }
+
+ System.out.println("value:"+ft.get());
+ //输出结果:
+ //value:we have data now
+ }
+}
+```
+
+方式二:通过线程池获取
+
+```java
+public class CycleWait3 implements Callable{
+ private String value;
+ @Override
+ public String call() throws Exception {
+ try {
+ Thread.currentThread().sleep(5000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ value = "we have data now";
+ return value;
+ }
+
+ public static void main(String[] args){
+ ExecutorService service = Executors.newCachedThreadPool();
+ Future future = service.submit(new CycleWait3());
+
+ if(!future.isDone()){
+ System.out.println("task has not finished,please wait!");
+ }
+
+ try {
+ System.out.println("value:"+future.get());
+ //输出结果:
+ //value:we have data now
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }finally {
+ service.shutdown(); //关闭线程池
+ }
+ }
+}
+```
+
+
+
+## 三、线程状态转换
+
+
+
+
+
+> **新建(New)**
+
+创建后尚未启动。
+
+> **可运行(Runnable)**
+
+可能正在运行,也可能正在等待 CPU 时间片。
+
+包含了操作系统线程状态中的 Running 和 Ready。
+
+调用 `start()` 方法后开始运行,线程这时候处于 Ready 状态。可运行状态的线程获得了 CPU 时间片后就处于 Running 状态。
+
+> **阻塞(Blocked)**
+
+等待获取一个**排它锁**,如果其他线程释放了锁就会结束此状态。
+
+> **无限期等待(Waiting)**
+
+等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。
+
+| 进入方法 | 退出方法 |
+| --- | --- |
+| 没有设置 Timeout 参数的 Object.wait() 方法 | Object.notify() / Object.notifyAll() |
+| 没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 |
+| LockSupport.park() 方法 | LockSupport.unpark(Thread) |
+
+> **限期等待(Timed Waiting)**
+
+无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
+
+调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用"使一个线程睡眠"进行描述。
+
+调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用"挂起一个线程"进行描述。
+
+睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。
+
+阻塞和等待的区别在于,阻塞是被动的,它是在等待获取一个排它锁。而等待是主动的,通过调用 Thread.sleep() 和 Object.wait() 等方法进入。
+
+| 进入方法 | 退出方法 |
+| --- | --- |
+| Thread.sleep() 方法 | 时间结束 |
+| 设置了 Timeout 参数的 Object.wait() 方法 | 时间结束 / Object.notify() / Object.notifyAll() |
+| 设置了 Timeout 参数的 Thread.join() 方法 | 时间结束 / 被调用的线程执行完毕 |
+| LockSupport.parkNanos() 方法 | LockSupport.unpark(Thread) |
+| LockSupport.parkUntil() 方法 | LockSupport.unpark(Thread) |
+
+> **死亡(Terminated)**
+
+可以是线程结束任务之后自己结束,或者产生了异常而结束。
+
+
+## 四、基础线程机制
+
+### Executor
+
+Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。
+
+主要有三种 Executor:
+
+- CachedThreadPool:一个任务创建一个线程;
+- FixedThreadPool:所有任务只能使用固定大小的线程;
+- SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool。
+
+```java
+public static void main(String[] args) {
+ ExecutorService executorService = Executors.newCachedThreadPool();
+ for (int i = 0; i < 5; i++) { + executorService.execute(new MyRunnable()); + } + executorService.shutdown(); //关闭线程池 +} +``` + +### Daemon + +守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。 + +当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。 + +main() 属于非守护线程。 + +使用 setDaemon() 方法将一个线程设置为守护线程。 + +```java +public static void main(String[] args) { + Thread thread = new Thread(new MyRunnable()); + thread.setDaemon(true); +} +``` + +### sleep() + +Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒。 + +sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。 + +```java +public void run() { + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } +} +``` + +### yield() + +对静态方法 Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法**只是对线程调度器的一个建议**,而且也只是建议具有相同优先级的其它线程可以运行。 + +```java +public class YieldDemo { + public static void main(String[] args) { + Runnable yieldTask = new Runnable() { + @Override + public void run() { + for(int i=1;i<=10;i++){ + System.out.println(Thread.currentThread().getName()+"\t"+i); + if(i==5){ + Thread.yield(); //只是对线程调度器的一个建议 + } + } + } + }; + + Thread t1 = new Thread(yieldTask,"Thread-A"); + Thread t2 = new Thread(yieldTask,"Thread-B"); + + t1.start(); + t2.start(); + } +} +``` + +```html +Thread-A 1 +Thread-A 2 +Thread-A 3 +Thread-A 4 +Thread-A 5 +Thread-B 1 +Thread-B 2 +Thread-B 3 +Thread-B 4 +Thread-A 6 +Thread-B 5 +Thread-A 7 +Thread-B 6 +Thread-A 8 +Thread-B 7 +Thread-A 9 +Thread-B 8 +Thread-B 9 +Thread-A 10 +Thread-B 10 +``` + +## 五、中断 + +一个线程执行完毕之后会自动结束,如果在运行过程中发生异常也会提前结束。 + +### interrupt() + +通过调用一个线程的 interrupt() 来中断该线程: + +- 如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是**不能中断 I/O 阻塞和 synchronized 锁阻塞**。 +- 如果该线程处于正常活动状态,那么会将该线程的中断标志设置为 true。被设置中断标志的线程将继续正常运行,不受影响。 + +对于以下代码。在 main() 中启动一个线程之后再中断它,由于线程中调用了 Thread.sleep() 方法,因此会抛出一个 InterruptedException,从而提前结束线程,不执行之后的语句。 + +```java +public class InterruptExample1 { + private static class MyThread1 extends Thread { + @Override + public void run() { + try { + Thread.sleep(2000); //thread1 进入限期等待状态 + System.out.println("Thread run"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("Thread run"); + } + } + + public static void main(String[] args) throws InterruptedException { + Thread thread1 = new MyThread1(); + thread1.start(); + thread1.interrupt(); //中断 thread1 + System.out.println("Main run"); + } +} +``` + +```java +Main run +java.lang.InterruptedException: sleep interrupted + at java.lang.Thread.sleep(Native Method) + at InterruptExample.lambda$main0ドル(InterruptExample.java:5) + at InterruptExample$$Lambda1ドル/713338599.run(Unknown Source) + at java.lang.Thread.run(Thread.java:745) +``` + +```java +public class InterruptExample2 { + private static class MyThread1 extends Thread { + @Override + public void run() { //thread1 线程处于正常活动状态 + System.out.println("Thread run"); + } + } + + public static void main(String[] args) throws InterruptedException { + Thread thread1 = new MyThread1(); + thread1.start(); + thread1.interrupt(); //中断 thread1 + System.out.println("Main run"); + } +} +``` + +```html +Main run +Thread run +``` + +### interrupted() + +如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。 + +但是调用 interrupt() 方法会设置线程的中断标记,此时**调用 interrupted() 方法会返回 true**。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。 + +```java +public class InterruptExample { + public static void main(String[] args) throws InterruptedException { + Runnable interruptTask = new Runnable() { + @Override + public void run() { + int i = 0; + try { + //在正常运行任务时,经常检查本线程的中断标志位, + //如果被设置了中断标志就自行停止线程 + while (!Thread.currentThread().isInterrupted()) { + Thread.sleep(100); // 休眠100ms + i++; + System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + ") loop " + i); + } + } catch (InterruptedException e) { + //在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程。) + System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + ") catch InterruptedException."); + } + } + }; + Thread t1 = new Thread(interruptTask, "t1"); + System.out.println(t1.getName() +" ("+t1.getState()+") is new."); + + t1.start(); // 启动"线程t1" + System.out.println(t1.getName() +" ("+t1.getState()+") is started."); + + // 主线程休眠300ms,然后主线程给t1发"中断"指令。 + Thread.sleep(300); + t1.interrupt(); + System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted."); + + // 主线程休眠300ms,然后查看t1的状态。 + Thread.sleep(300); + System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now."); + } +} +``` + +```java +t1 (NEW) is new. +t1 (RUNNABLE) is started. +t1 (RUNNABLE) loop 1 +t1 (RUNNABLE) loop 2 +t1 (TIMED_WAITING) is interrupted. +t1 (RUNNABLE) catch InterruptedException. +t1 (TERMINATED) is interrupted now. +``` + +### Executor 的中断操作 + +调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。 + +以下使用 Lambda 创建线程,相当于创建了一个匿名内部线程。 + +```java +public static void main(String[] args) { + ExecutorService executorService = Executors.newCachedThreadPool(); + executorService.execute(() -> {
+ try {
+ Thread.sleep(2000);
+ System.out.println("Thread run");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ });
+ executorService.shutdownNow();
+ System.out.println("Main run");
+}
+```
+
+```java
+Main run
+java.lang.InterruptedException: sleep interrupted
+ at java.lang.Thread.sleep(Native Method)
+ at ExecutorInterruptExample.lambda$main0ドル(ExecutorInterruptExample.java:9)
+ at ExecutorInterruptExample$$Lambda1ドル/1160460865.run(Unknown Source)
+ at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
+ at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
+ at java.lang.Thread.run(Thread.java:745)
+```
+
+如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。
+
+```java
+Future> future = executorService.submit(() -> {
+ // ..
+});
+future.cancel(true);
+```
+
+## 六、线程之间的协作
+
+当多个线程可以一起工作去解决某个问题时,如果某些部分必须在其它部分之前完成,那么就需要对线程进行协调。
+
+### join()
+
+在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
+
+对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。
+
+```java
+public class JoinExample {
+
+ private class A extends Thread {
+ @Override
+ public void run() {
+ System.out.println("A");
+ }
+ }
+
+ private class B extends Thread {
+
+ private A a;
+
+ B(A a) {
+ this.a = a;
+ }
+
+ @Override
+ public void run() {
+ try {
+ a.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println("B");
+ }
+ }
+
+ public void test() {
+ A a = new A();
+ B b = new B(a);
+ b.start();
+ a.start();
+ }
+}
+```
+
+```java
+public static void main(String[] args) {
+ JoinExample example = new JoinExample();
+ example.test();
+}
+```
+
+```java
+A
+B
+```
+
+### wait() notify() notifyAll()
+
+调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
+
+它们都属于 Object 的一部分,而不属于 Thread。
+
+只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateException。
+
+使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。
+
+```java
+public class WaitNotifyExample {
+
+ public synchronized void before() {
+ System.out.println("before");
+ notifyAll();
+ }
+
+ public synchronized void after() {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println("after");
+ }
+}
+```
+
+```java
+public static void main(String[] args) {
+ ExecutorService executorService = Executors.newCachedThreadPool();
+ WaitNotifyExample example = new WaitNotifyExample();
+ executorService.execute(() -> example.after());
+ executorService.execute(() -> example.before());
+}
+```
+
+```java
+before
+after
+```
+
+> **wait() 和 sleep() 的区别**
+
+基本差别:
+
+- wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
+- sleep() 方法可以在任何地方使用,而 wait() 方法只能在 synchronized 块或者同步方法中使用;
+
+本质区别:
+
+- Thread.sleep 只会让出 CPU,不会导致锁行为的变化
+- Object.wait 不仅让出 CPU ,还会释放已经占有的同步资源锁
+
+```java
+public class WaitSleepDemo {
+ public static void main(String[] args) {
+ final Object lock = new Object();
+
+ Thread t1 = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ System.out.println("thread A is waiting to get lock");
+ synchronized (lock){
+ try {
+ System.out.println("thread A get lock");
+ Thread.sleep(20);
+ //Thread.sleep 只会让出 CPU,不会导致锁行为的变化
+ System.out.println("thread A do wait method");
+
+ //==========位置 A ==========
+ //lock.wait(1000);
+ //Object.wait 不仅让出 CPU ,还会释放已经占有的同步资源锁
+
+ Thread.sleep(1000);
+ //Thread.sleep 只会让出 CPU,不会导致锁行为的变化
+ //===========================
+
+ System.out.println("thread A is done");
+ } catch (InterruptedException e){
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+ t1.start();
+
+ try{
+ Thread.sleep(10); //主线程等待 10 ms
+ } catch (InterruptedException e){
+ e.printStackTrace();
+ }
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ System.out.println("thread B is waiting to get lock");
+ synchronized (lock){
+ try {
+ System.out.println("thread B get lock");
+ System.out.println("thread B is sleeping 10 ms");
+ Thread.sleep(10);
+ //Thread.sleep 只会让出 CPU,不会导致锁行为的变化
+ System.out.println("thread B is done");
+ } catch (InterruptedException e){
+ e.printStackTrace();
+ }
+ }
+ }
+ }).start();
+ }
+}
+```
+
+位置 A 为 `lock.wait(1000);` 输出结果:
+
+```html
+thread A is waiting to get lock
+thread A get lock
+thread A do wait method
+thread B is waiting to get lock
+thread B get lock
+thread B is sleeping 10 ms
+thread B is done
+thread A is done
+```
+
+位置 A 为 `Thread.sleep(1000);` 输出结果:
+
+```html
+thread A is waiting to get lock
+thread A get lock
+thread B is waiting to get lock
+thread A do wait method
+thread A is done
+thread B get lock
+thread B is sleeping 10 ms
+thread B is done
+```
+
+> **notify() 和 notifyAll() 的区别**
+
+两个重要概念:
+
+- 锁池(EntryList)
+
+假设线程 A 已经拥有了**某个对象的锁**,而其他线程 B、C 想要调用这个对象的同步方法(或者同步代码块),由于 B 、C 线程在进入对象的同步方法(或者同步代码块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正在被线程 A 所占用,此时 B 、C 线程就会被阻塞,进入一个地方去**等待锁的释放**,这个地方便是该对象的锁池。
+
+- 等待池(WaitSet)
+
+假设线程 A 调用了某个对象的 wait() 方法,线程 A 就会释放该对象的锁,同时线程 A 就进入该对象的等待池,**进入到等待池的线程不会竞争该对象的锁**。
+
+区别:
+
+notifyAll() 会让所有处于**等待池的线程全部进入锁池**去竞争锁
+
+```java
+public class NotificationDemo {
+ private volatile boolean go = false;
+
+ public static void main(String args[]) throws InterruptedException {
+ final NotificationDemo test = new NotificationDemo();
+
+ Runnable waitTask = new Runnable(){
+ @Override
+ public void run(){
+ try {
+ test.shouldGo();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println(Thread.currentThread().getName() + " finished Execution");
+ }
+ };
+
+ Runnable notifyTask = new Runnable(){
+ @Override
+ public void run(){
+ test.go();
+ System.out.println(Thread.currentThread().getName() + " finished Execution");
+ }
+ };
+
+ //t1、t2、t3 等待
+ Thread t1 = new Thread(waitTask, "WT1");
+ Thread t2 = new Thread(waitTask, "WT2");
+ Thread t3 = new Thread(waitTask, "WT3");
+ //t4 唤醒
+ Thread t4 = new Thread(notifyTask,"NT1");
+ t1.start();
+ t2.start();
+ t3.start();
+
+ Thread.sleep(200);
+ t4.start();
+ }
+
+ //wait and notify can only be called from synchronized method or block
+ private synchronized void shouldGo() throws InterruptedException {
+ while(go != true){
+ System.out.println(Thread.currentThread()
+ + " is going to wait on this object");
+ wait(); //this.wait 不仅让出 CPU ,还会释放已经占有的同步资源锁
+ System.out.println(Thread.currentThread() + " is woken up");
+ }
+ go = false; //resetting condition
+ }
+
+ // both shouldGo() and go() are locked on current object referenced by "this" keyword
+ private synchronized void go() {
+ while (go == false){
+ System.out.println(Thread.currentThread()
+ + " is going to notify all or one thread waiting on this object");
+
+ go = true; //making condition true for waiting thread
+ //===================位置 A =====================
+ //notify(); //只会唤醒 WT1, WT2,WT3 中的一个线程
+ notifyAll(); //所有的等待线程 WT1, WT2,WT3 都会被唤醒
+ //==============================================
+ }
+ }
+}
+```
+
+位置 A 为 `notify();` 输出结果:
+
+```html
+Thread[WT1,5,main] is going to wait on this object
+Thread[WT2,5,main] is going to wait on this object
+Thread[WT3,5,main] is going to wait on this object
+Thread[NT1,5,main] is going to notify all or one thread waiting on this object
+Thread[WT1,5,main] is woken up
+WT1 finished Execution
+NT1 finished Execution
+```
+
+位置 B 为 `notifyAll();` 输出结果:
+
+```html
+Thread[WT1,5,main] is going to wait on this object
+Thread[WT3,5,main] is going to wait on this object
+Thread[WT2,5,main] is going to wait on this object
+Thread[NT1,5,main] is going to notify all or one thread waiting on this object
+Thread[WT2,5,main] is woken up
+NT1 finished Execution
+WT2 finished Execution
+Thread[WT3,5,main] is woken up
+Thread[WT3,5,main] is going to wait on this object
+Thread[WT1,5,main] is woken up
+Thread[WT1,5,main] is going to wait on this object
+```
+
+### await() signal() signalAll()
+
+java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。
+
+相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。
+
+使用 Lock 来获取一个 Condition 对象。
+
+```java
+public class AwaitSignalExample {
+
+ private Lock lock = new ReentrantLock();
+ private Condition condition = lock.newCondition();
+
+ public void before() {
+ lock.lock();
+ try {
+ System.out.println("before");
+ condition.signalAll();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public void after() {
+ lock.lock();
+ try {
+ condition.await();
+ System.out.println("after");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ lock.unlock();
+ }
+ }
+}
+```
+
+```java
+public static void main(String[] args) {
+ ExecutorService executorService = Executors.newCachedThreadPool();
+ AwaitSignalExample example = new AwaitSignalExample();
+ executorService.execute(() -> example.after());
+ executorService.execute(() -> example.before());
+}
+```
+
+```java
+before
+after
+```
+
+## 七、Java 原子操作类
+
+由于 synchronized 是采用的是**悲观锁策略**,并不是特别高效的一种解决方案。 实际上,在 J.U.C下 的 atomic 包提供了一系列的操作简单,性能高效,并能保证线程安全的类,去更新基本类型变量、数组元素、引用类型以及更新对象中的字段类型。 atomic 包下的这些类都是采用的是**乐观锁策略**去原子更新数据,在 Java 中则是**使用 CAS操作具体实现**。
+
+### 1、原子更新基本类
+
+使用原子的方式更新基本类型,atomic 包提供了以下 3 个类:
+
+| 类 | 说明 |
+| :-----------: | :-------------------: |
+| AtomicBoolean | 原子更新 boolean 类型 |
+| AtomicInteger | 原子更新整型 |
+| AtomicLong | 原子更新长整型 |
+
+以 AtomicInteger 为例总结常用的方法:
+
+| 方法 | 说明 |
+| :---------------------: | :----------------------------------------------------------: |
+| addAndGet(int delta) | 以原子方式将输入的数值与实例中原本的值相加,并返回最后的结果 |
+| incrementAndGet() | 以原子的方式将实例中的原值进行加1操作,并返回最终相加后的结果 |
+| getAndSet(int newValue) | 将实例中的值更新为新值,并返回旧值 |
+| getAndIncrement() | 以原子的方式将实例中的原值 +1,返回的是自增前的旧值 |
+
+> **AtomicInteger **
+
+ AtomicInteger 测试:
+
+```java
+public class AtomicIntegerDemo {
+ // 请求总数
+ public static int clientTotal = 5000;
+
+ // 同时并发执行的线程数
+ public static int threadTotal = 200;
+
+ //java.util.concurrent.atomic.AtomicInteger;
+ public static AtomicInteger count = new AtomicInteger(0);
+
+ public static void main(String[] args) throws InterruptedException {
+ ExecutorService executorService = Executors.newCachedThreadPool();
+ //Semaphore和CountDownLatch模拟并发
+ final Semaphore semaphore = new Semaphore(threadTotal);
+ final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
+ for (int i = 0; i < clientTotal ; i++) { + executorService.execute(() -> {
+ try {
+ semaphore.acquire();
+ add();
+ semaphore.release();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ countDownLatch.countDown();
+ });
+ }
+ countDownLatch.await();
+ executorService.shutdown();
+ System.out.println("count:{"+count.get()+"}");
+ }
+
+ public static void add() {
+ count.incrementAndGet();
+ }
+}
+```
+
+```html
+count:{5000}
+```
+
+ AtomicInteger 的 getAndIncrement() 方法源码:
+
+```java
+private static final Unsafe unsafe = Unsafe.getUnsafe();
+
+public final int incrementAndGet() {
+ return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
+}
+```
+
+实际上是调用了 unsafe 实例的 getAndAddInt 方法。Unsafe 类在 sun.misc 包下,Unsafe 类提供了一些底层操作,atomic 包下的原子操作类的也主要是通过 Unsafe 类提供的 compareAndSwapObject、compareAndSwapInt 和 compareAndSwapLong 等一系列提供 CAS 操作的方法来进行实现。
+
+> **AtomicLong**
+
+AtomicLong 的实现原理和 AtomicInteger 一致,只不过一个针对的是 long 变量,一个针对的是 int 变量。
+
+> **AtomicBoolean**
+
+AtomicBoolean 的核心方法是 compareAndSet() 方法:
+
+```java
+public final boolean compareAndSet(boolean expect, boolean update) {
+ int e = expect ? 1 : 0;
+ int u = update ? 1 : 0;
+ return unsafe.compareAndSwapInt(this, valueOffset, e, u);
+}
+```
+
+可以看出,compareAndSet 方法的实际上也是先转换成 0、1 的整型变量,然后是通过针对 int 型变量的原子更新方法 compareAndSwapInt 来实现的。atomic 包中只提供了对 boolean ,int ,long 这三种基本类型的原子更新的方法,参考对 boolean 更新的方式,原子更新 char,double,float 也可以采用类似的思路进行实现。
+
+### 2、原子更新数组
+
+通过原子的方式更新数组里的某个元素,atomic 包提供了以下 3 个类:
+
+| 类 | 说明 |
+| :------------------: | :--------------------------: |
+| AtomicIntegerArray | 原子更新整型数组中的元素 |
+| AtomicLongArray | 原子更新长整型数组中的元素 |
+| AtomicReferenceArray | 原子更新引用类型数组中的元素 |
+
+这几个类的用法一致,就以AtomicIntegerArray来总结下常用的方法:
+
+| 方法 | 说明 |
+| :------------------------------------------: | :-----------------------------------------------: |
+| addAndGet(int i, int delta) | 以原子更新的方式将数组中索引为i的元素与输入值相加 |
+| getAndIncrement(int i) | 以原子更新的方式将数组中索引为i的元素自增 +1 |
+| compareAndSet(int i, int expect, int update) | 将数组中索引为 i 的位置的元素进行更新 |
+
+AtomicIntegerArray 与 AtomicInteger 的方法基本一致,只不过在 AtomicIntegerArray 的方法中会多一个指定数组索引位 i。
+
+```java
+public class AtomicIntegerArrayDemo {
+ // private static AtomicInteger atomicInteger = new AtomicInteger(1);
+ private static int[] value = new int[]{1, 2, 3};
+ private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);
+
+ public static void main(String[] args) {
+ //对数组中索引为1的位置的元素加5
+ int result = integerArray.getAndAdd(1, 5);
+ System.out.println(integerArray.get(1));
+ System.out.println(result);
+ }
+}
+```
+
+```html
+7
+2
+```
+
+getAndAdd 方法将位置为 1 的元素 +5,从结果可以看出索引为 1 的元素变成了 7 ,该方法返回的也是相加之前的数为 2。
+
+### 3、原子更新引用类型
+
+如果需要原子更新引用类型变量的话,为了保证线程安全,atomic 也提供了相关的类:
+
+| 类 | 说明 |
+| :-------------------------: | :--------------------------: |
+| AtomicReference | 原子更新引用类型 |
+| AtomicReferenceFieldUpdater | 原子更新引用类型里的字段 |
+| AtomicMarkableReference | 原子更新带有标记位的引用类型 |
+
+这几个类的使用方法也是基本一样的,以AtomicReference为例,来说明这些类的基本用法:
+
+```java
+public class AtomicDemo {
+
+ private static AtomicReference reference = new AtomicReference();
+
+ public static void main(String[] args) {
+ User user1 = new User("a",1);
+ reference.set(user1);
+ User user2 = new User("b",2);
+ User user = reference.getAndSet(user2);
+ System.out.println(user);
+ System.out.println(reference.get());
+ }
+
+ static class User {
+ private String userName;
+ private int age;
+
+ public User(String userName, int age) {
+ this.userName = userName;
+ this.age = age;
+ }
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "userName='" + userName + '\'' +
+ ", age=" + age +
+ '}';
+ }
+ }
+}
+```
+
+```html
+User{userName='a', age=1}
+User{userName='b', age=2}
+```
+
+首先构建一个 user1 对象,然会把 user1 对象设置进 AtomicReference 中,然后调用 getAndSet 方法。从结果可以看出,该方法会原子更新引用的 user 对象,变为`User{userName='b', age=2}`,返回的是原来的 user 对象User`{userName='a', age=1}`。
+
+### 4、原子更新字段类型
+
+如果需要更新对象的某个字段,并在多线程的情况下,能够保证线程安全,atomic 同样也提供了相应的原子操作类:
+
+| 类 | 说明 |
+| :----------------------: | :----------------------------------------------------------: |
+| AtomicIntegeFieldUpdater | 原子更新整型字段类 |
+| AtomicLongFieldUpdater | 原子更新长整型字段类 |
+| AtomicStampedReference | 原子更新带版本号引用类型。
为什么在更新的时候会带有版本号,是为了解决 CAS 的 ABA 问题 |
+
+要想使用原子更新字段需要两步操作:
+
+- 原子更新字段类都是**抽象类**,只能通过静态方法 newUpdater 来创建一个更新器,并且需要设置想要更新的类和属性
+- 更新类的属性必须使用 public volatile 进行修饰
+
+```java
+public class AtomicIntegerFieldUpdaterDemo {
+ private static AtomicIntegerFieldUpdater updater =
+ AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
+
+ public static void main(String[] args) {
+ User user = new User("a", 1);
+ System.out.println(user);
+ int oldValue = updater.getAndAdd(user, 5);
+ System.out.println(user);
+ }
+
+ static class User {
+ private String userName;
+ public volatile int age;
+
+ public User(String userName, int age) {
+ this.userName = userName;
+ this.age = age;
+ }
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "userName='" + userName + '\'' +
+ ", age=" + age +
+ '}';
+ }
+ }
+}
+```
+
+```html
+User{userName='a', age=1}
+User{userName='a', age=6}
+```
+
+创建 AtomicIntegerFieldUpdater 是通过它提供的静态方法进行创建, getAndAdd 方法会将指定的字段加上输入的值,并且返回相加之前的值。 user 对象中 age 字段原值为 1,+5 之后,可以看出 user 对象中的 age 字段的值已经变成了 6。
+
+## 八、J.U.C - AQS
+
+java.util.concurrent(J.U.C)大大提高了并发性能,AQS 被认为是 J.U.C 的核心。
+
+### CountDownLatch
+
+用来控制一个线程等待多个线程。
+
+维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒。
+
+
+
+
+
+> **场景1:程序执行需要等待某个条件完成后,才能进行后面的操作**
+
+比如父任务等待所有子任务都完成的时候, 再继续往下进行。
+
+```java
+public class CountDownLatchExample {
+ //线程数量
+ private static int threadCount=10;
+
+ public static void main(String[] args) throws InterruptedException {
+ ExecutorService executorService= Executors.newCachedThreadPool();
+
+ final CountDownLatch countDownLatch=new CountDownLatch(threadCount);
+
+
+ for (int i = 1; i <= threadCount; i++) { + final int threadNum=i; + executorService.execute(()->{
+ try {
+ test(threadNum);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }finally {
+ countDownLatch.countDown();
+ }
+
+ });
+ }
+ countDownLatch.await();
+ //上面的所有线程都执行完了,再执行主线程
+ System.out.println("Finished!");
+ executorService.shutdown();
+ }
+
+ private static void test(int threadNum) throws InterruptedException {
+ Thread.sleep(100);
+ System.out.println("run: Thread "+threadNum);
+ Thread.sleep(100);
+ }
+}
+```
+
+```html
+run: Thread 8
+run: Thread 7
+run: Thread 6
+run: Thread 5
+run: Thread 4
+run: Thread 3
+run: Thread 2
+run: Thread 1
+run: Thread 10
+run: Thread 9
+Finished!
+```
+
+
+
+> **场景2:指定执行时间的情况,超过这个任务就不继续等待了,完成多少算多少。**
+
+```java
+public class CountDownLatchExample2 {
+ //线程数量
+ private static int threadCount=10;
+
+ public static void main(String[] args) throws InterruptedException {
+ ExecutorService executorService= Executors.newCachedThreadPool();
+
+ final CountDownLatch countDownLatch=new CountDownLatch(threadCount);
+
+
+ for (int i = 1; i <= threadCount; i++) { + final int threadNum=i; + executorService.execute(()->{
+ try {
+ test(threadNum);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }finally {
+ countDownLatch.countDown();
+ }
+
+ });
+ }
+ countDownLatch.await(10, TimeUnit.MILLISECONDS);
+ //上面线程如果在10 毫秒内未完成,则有可能会执行主线程
+ System.out.println("Finished!");
+ executorService.shutdown();
+ }
+
+ private static void test(int threadNum) throws InterruptedException {
+ Thread.sleep(10);
+ System.out.println("run: Thread "+threadNum);
+ }
+}
+```
+
+```html
+run: Thread 10
+run: Thread 4
+run: Thread 3
+run: Thread 2
+Finished!
+run: Thread 9
+run: Thread 5
+run: Thread 8
+run: Thread 6
+run: Thread 7
+run: Thread 1
+```
+
+### CyclicBarrier
+
+用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被拦截的线程才会继续执行。
+
+和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行。
+
+CyclicBarrier 和 CountdownLatch 的一个区别是,CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用,所以它才叫做循环屏障。
+
+CyclicBarrier 有两个构造函数,其中 parties 指示计数器的初始值,barrierAction 在所有线程都到达屏障的时候会执行一次。
+
+```java
+public CyclicBarrier(int parties, Runnable barrierAction) {
+ if (parties <= 0) throw new IllegalArgumentException(); + this.parties = parties; + this.count = parties; + this.barrierCommand = barrierAction; +} + +public CyclicBarrier(int parties) { + this(parties, null); +} +``` + +
+
+```java
+public class CyclicBarrierExample {
+ //指定必须有6个运动员到达才行
+ private static CyclicBarrier barrier = new CyclicBarrier(6, () -> {
+ System.out.println("所有运动员入场,裁判员一声令下!!!!!");
+ });
+ public static void main(String[] args) {
+ System.out.println("运动员准备进场,全场欢呼............");
+
+ ExecutorService service = Executors.newFixedThreadPool(6);
+ for (int i = 0; i < 6; i++) { + service.execute(() -> {
+ try {
+ System.out.println(Thread.currentThread().getName() + " 运动员,进场");
+ barrier.await();
+ System.out.println(Thread.currentThread().getName() + " 运动员出发");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (BrokenBarrierException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+ service.shutdown();
+ }
+}
+```
+
+```html
+运动员准备进场,全场欢呼............
+pool-1-thread-1 运动员,进场
+pool-1-thread-2 运动员,进场
+pool-1-thread-3 运动员,进场
+pool-1-thread-4 运动员,进场
+pool-1-thread-5 运动员,进场
+pool-1-thread-6 运动员,进场
+所有运动员入场,裁判员一声令下!!!!!
+pool-1-thread-6 运动员出发
+pool-1-thread-1 运动员出发
+pool-1-thread-4 运动员出发
+pool-1-thread-3 运动员出发
+pool-1-thread-2 运动员出发
+pool-1-thread-5 运动员出发
+```
+
+> **场景:多线程计算数据,最后合并计算结果。**
+
+```java
+public class CyclicBarrierExample2 {
+ private static int threadCount = 10;
+
+ public static void main(String[] args) throws InterruptedException {
+ CyclicBarrier cyclicBarrier=new CyclicBarrier(5);
+
+ ExecutorService executorService= Executors.newCachedThreadPool();
+
+
+ for (int i = 0; i < threadCount; i++) { + final int threadNum=i; + executorService.execute(()->{
+ try {
+ System.out.println("before..."+threadNum);
+ cyclicBarrier.await();
+ System.out.println("after..."+threadNum);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ }
+ executorService.shutdown();
+ }
+}
+```
+
+```html
+before...1
+before...4
+before...3
+before...2
+before...6
+before...5
+before...7
+before...8
+after...6
+before...9
+after...1
+after...2
+before...10
+after...3
+after...7
+after...8
+after...4
+after...9
+after...5
+after...10
+```
+
+> **CountDownLatch 与 CyclicBarrier 比较**
+
+- CountDownLatch 一般用于某个线程 A 等待若干个其他线程执行完后再执行。CountDownLatch 强调一个线程等多个线程完成某件事情;
+
+ CyclicBarrier 一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行。CyclicBarrier是多个线程互等,等大家都完成,再携手共进。
+
+- 调用 CountDownLatch 的 countDown 方法后,当前线程并不会阻塞,会继续往下执行;
+
+ 调用 CyclicBarrier 的 await 方法,会阻塞当前线程,直到 CyclicBarrier 指定的线程全部都到达了指定点时,才能继续往下执行。
+
+- CountDownLatch 方法比较少,操作比较简单;
+
+ CyclicBarrier 提供的方法更多,比如能够通过 getNumberWaiting(),isBroken() 等方法获取当前多个线程的状态,并且 **CyclicBarrier 的构造方法可以传入 barrierAction**,指定当所有线程都到达时执行的业务功能。
+
+- CountDownLatch 是不能复用的;
+
+ CyclicLatch 是可以复用的。
+
+
+
+### Semaphore
+
+Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。
+
+其中`acquire()`方法,用来获取资源,`release()`方法用来释放资源。Semaphore维护了当前访问的个数,通过提供**同步机制**来控制同时访问的个数。
+
+> **场景:特殊资源的并发访问控制**
+
+比如数据库的连接数最大只有 20,而上层的并发数远远大于 20,这时候如果不作限制, 可能会由于无法获取连接而导致并发异常,这时候可以使用 Semaphore 来进行控制。当信号量设置为1的时候,就和单线程很相似了。
+
+```java
+//每次获取一个许可
+public class SemaphoreExample {
+ private static int clientCount = 3;
+ private static int totalRequestCount = 10;
+
+ public static void main(String[] args) {
+ ExecutorService executorService= Executors.newCachedThreadPool();
+
+ //Semaphore允许的最大许可数为 clientCount ,
+ //也就是允许的最大并发执行的线程个数为 clientCount
+ final Semaphore semaphore=new Semaphore(clientCount);
+
+ for(int i=1;i<=totalrequestcount;i++){ + final int threadNum=i; + executorService.execute(() -> {
+ try{
+ semaphore.acquire();//每次获取一个许可
+ test(threadNum);
+ }catch (InterruptedException e) {
+ e.printStackTrace();
+ }finally {
+ semaphore.release(); //释放一个许可
+ }
+ });
+ }
+ executorService.shutdown();
+ }
+
+ private static void test(int threadNum) throws InterruptedException {
+ System.out.println("run: "+threadNum);
+ Thread.sleep(1000); // 线程睡眠 1 s
+ }
+}
+```
+
+```html
+run: 1
+run: 2
+run: 3
+//---- 1 s -----
+run: 4
+run: 5
+run: 6
+//---- 1 s -----
+run: 7
+run: 8
+run: 9
+//---- 1 s -----
+run: 10
+```
+
+
+
+```java
+//每次获取多个许可
+public class SemaphoreExample2 {
+ private static int clientCount = 3;
+ private static int totalRequestCount = 10;
+
+ public static void main(String[] args) {
+ ExecutorService executorService= Executors.newCachedThreadPool();
+
+ final Semaphore semaphore=new Semaphore(clientCount);
+
+ for(int i=1;i<=totalrequestcount;i++){ + final int threadNum=i; + executorService.execute(() -> {
+ try{
+ semaphore.acquire(3);//每次获取3个许可
+ //并发数是 3 ,一次性获取 3 个许可,同 1s 内无其他许可释放,相当于单线程
+ test(threadNum);
+ }catch (InterruptedException e) {
+ e.printStackTrace();
+ }finally {
+ semaphore.release(3); //释放 3 个许可
+ }
+ });
+ }
+ executorService.shutdown();
+ }
+
+ private static void test(int threadNum) throws InterruptedException {
+ System.out.println("run: "+threadNum);
+ Thread.sleep(1000); // 线程睡眠 1 s
+ }
+}
+```
+
+```html
+run: 1
+//---- 1 s -----
+run: 2
+//---- 1 s -----
+run: 3
+//---- 1 s -----
+run: 4
+//---- 1 s -----
+run: 5
+//---- 1 s -----
+run: 6
+//---- 1 s -----
+run: 7
+//---- 1 s -----
+run: 8
+//---- 1 s -----
+run: 10
+//---- 1 s -----
+run: 9
+```
+
+
+
+```java
+public class SemaphoreExample3 {
+ private static int clientCount = 3;
+ private static int totalRequestCount = 10;
+
+ public static void main(String[] args) {
+ ExecutorService executorService= Executors.newCachedThreadPool();
+
+ final Semaphore semaphore=new Semaphore(clientCount);
+
+ for(int i=1;i<=totalrequestcount;i++){ + final int threadNum=i; + executorService.execute(() -> {
+ try{
+ if(semaphore.tryAcquire()){
+ //尝试获取一个许可
+ test(threadNum);
+ semaphore.release();
+ }
+ }catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+ executorService.shutdown();
+ }
+
+ private static void test(int threadNum) throws InterruptedException {
+ System.out.println("run: "+threadNum);
+ Thread.sleep(1000); // 线程睡眠 1 s
+ }
+}
+```
+
+```html
+run: 1
+run: 2
+run: 3
+```
+
+## 九、J.U.C - 其它组件
+
+### FutureTask
+
+在介绍 Callable 时我们知道它可以有返回值,返回值通过 Future 进行封装。FutureTask 实现了 RunnableFuture 接口,该接口继承自 Runnable 和 Future 接口,这使得 FutureTask 既可以当做一个任务执行,也可以有返回值。
+
+```java
+public class FutureTask implements RunnableFuture
+```
+
+```java
+public interface RunnableFuture extends Runnable, Future
+```
+
+FutureTask 可用于异步获取执行结果或取消执行任务的场景。当一个计算任务需要执行很长时间,那么就可以用 FutureTask 来封装这个任务,主线程在完成自己的任务之后再去获取结果。
+
+```java
+public class FutureTaskExample {
+
+ public static void main(String[] args) throws ExecutionException, InterruptedException {
+ FutureTask futureTask = new FutureTask(new Callable() {
+ @Override
+ public Integer call() throws Exception {
+ int result = 0;
+ for (int i = 0; i < 100; i++) { + Thread.sleep(10); + result += i; + } + return result; + } + }); + + Thread computeThread = new Thread(futureTask); + computeThread.start(); + + Thread otherThread = new Thread(() -> {
+ System.out.println("other task is running...");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ });
+ otherThread.start();
+ System.out.println(futureTask.get());
+ }
+}
+```
+
+```java
+other task is running...
+4950
+```
+
+### BlockingQueue
+
+java.util.concurrent.BlockingQueue 接口有以下阻塞队列的实现:
+
+- **FIFO 队列** :LinkedBlockingQueue、ArrayBlockingQueue(固定长度)
+- **优先级队列** :PriorityBlockingQueue
+
+提供了阻塞的 take() 和 put() 方法:如果队列为空 take() 将阻塞,直到队列中有内容;如果队列为满 put() 将阻塞,直到队列有空闲位置。
+
+**使用 BlockingQueue 实现生产者消费者问题**
+
+```java
+public class ProducerConsumer {
+
+ private static BlockingQueue queue = new ArrayBlockingQueue(5);
+
+ private static class Producer extends Thread {
+ @Override
+ public void run() {
+ try {
+ queue.put("product");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.print("produce..");
+ }
+ }
+
+ private static class Consumer extends Thread {
+
+ @Override
+ public void run() {
+ try {
+ String product = queue.take();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.print("consume..");
+ }
+ }
+}
+```
+
+```java
+public static void main(String[] args) {
+ for (int i = 0; i < 2; i++) { + Producer producer = new Producer(); + producer.start(); + } + for (int i = 0; i < 5; i++) { + Consumer consumer = new Consumer(); + consumer.start(); + } + for (int i = 0; i < 3; i++) { + Producer producer = new Producer(); + producer.start(); + } +} +``` + +```html +produce..produce..consume..consume..produce..consume..produce..consume..produce..consume.. +``` + +### ForkJoin + +主要用于并行计算中,和 MapReduce 原理类似,都是把大的计算任务拆分成多个小任务并行计算。 + +```java +public class ForkJoinExample extends RecursiveTask {
+
+ private final int threshold = 5;
+ private int first;
+ private int last;
+
+ public ForkJoinExample(int first, int last) {
+ this.first = first;
+ this.last = last;
+ }
+
+ @Override
+ protected Integer compute() {
+ int result = 0;
+ if (last - first <= threshold) { + // 任务足够小则直接计算 + for (int i = first; i <= last; i++) { + result += i; + } + } else { + // 拆分成小任务 + int middle = first + (last - first) / 2; + ForkJoinExample leftTask = new ForkJoinExample(first, middle); + ForkJoinExample rightTask = new ForkJoinExample(middle + 1, last); + leftTask.fork(); + rightTask.fork(); + result = leftTask.join() + rightTask.join(); + } + return result; + } +} +``` + +```java +public static void main(String[] args) throws ExecutionException, InterruptedException { + ForkJoinExample example = new ForkJoinExample(1, 10000); + ForkJoinPool forkJoinPool = new ForkJoinPool(); + Future result = forkJoinPool.submit(example); + System.out.println(result.get()); +} +``` + +ForkJoin 使用 ForkJoinPool 来启动,它是一个特殊的线程池,线程数量取决于 CPU 核数。 + +```java +public class ForkJoinPool extends AbstractExecutorService +``` + +ForkJoinPool 实现了工作窃取算法来提高 CPU 的利用率。每个线程都维护了一个双端队列,用来存储需要执行的任务。工作窃取算法允许空闲的线程从其它线程的双端队列中窃取一个任务来执行。窃取的任务必须是最晚的任务,避免和队列所属线程发生竞争。例如下图中,Thread2 从 Thread1 的队列中拿出最晚的 Task1 任务,Thread1 会拿出 Task2 来执行,这样就避免发生竞争。但是如果队列中只有一个任务时还是会发生竞争。 + +
+
+## 十、生产者与消费者模式
+
+生产者-消费者模式是一个十分经典的多线程并发协作的模式,弄懂生产者-消费者问题能够让我们对并发编程的理解加深。
+
+所谓生产者-消费者问题,实际上主要是包含了两类线程,一种是**生产者线程用于生产数据**,另一种是**消费者线程用于消费数据**,为了解耦生产者和消费者的关系,通常会采用**共享数据区域**。
+
+共享数据区域就像是一个仓库,生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为;消费者只需要从共享数据区中去获取数据,就不再需要关心生产者的行为。但是,这个共享数据区域中应该具备这样的线程间并发协作的功能:
+
+- 如果共享数据区已满的话,阻塞生产者继续生产数据放置入内
+- 如果共享数据区为空的话,阻塞消费者继续消费数据
+
+
+
+### wait() / notify() 潜在的一些问题
+
+> **notify() 早期通知**
+
+线程 A 还没开始 wait 的时候,线程 B 已经 notify 了。线程 B 通知是没有任何响应的,当线程 B 退出同步代码块后,线程 A 再开始 wait,便会一直阻塞等待,直到被别的线程打断。
+
+```java
+public class EarlyNotify {
+ public static void main(String[] args) {
+ final Object lock = new Object();
+
+ Thread notifyThread = new Thread(new NotifyThread(lock),"notifyThread");
+ Thread waitThread = new Thread(new WaitThread(lock),"waitThread");
+ notifyThread.start();
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ waitThread.start();
+ }
+
+ private static class WaitThread implements Runnable{
+ private Object lock;
+
+ public WaitThread(Object lock){
+ this.lock = lock;
+ }
+
+ @Override
+ public void run() {
+ synchronized (lock) {
+ try {
+ System.out.println(Thread.currentThread().getName() + " 进去代码块");
+ System.out.println(Thread.currentThread().getName() + " 开始 wait");
+ lock.wait();
+ System.out.println(Thread.currentThread().getName() + " 结束 wait");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private static class NotifyThread implements Runnable{
+ private Object lock;
+
+ public NotifyThread(Object lock){
+ this.lock = lock;
+ }
+
+ @Override
+ public void run() {
+ synchronized (lock) {
+ System.out.println(Thread.currentThread().getName() + " 进去代码块");
+ System.out.println(Thread.currentThread().getName() + " 开始 notify");
+ lock.notify();
+ System.out.println(Thread.currentThread().getName() + " 结束开始 notify");
+ }
+ }
+ }
+}
+```
+
+```html
+notifyThread 进去代码块
+notifyThread 开始 notify
+notifyThread 结束开始 notify
+waitThread 进去代码块
+waitThread 开始 wait
+```
+
+示例中开启了两个线程,一个是 WaitThread,另一个是 NotifyThread。NotifyThread 先启动,先调用 notify() 方法。然后 WaitThread 线程才启动,调用 wait() 方法,但是由于已经通知过了,wait() 方法就无法再获取到相应的通知,因此 WaitThread 会一直在 wait() 方法处阻塞,这种现象就是**通知过早**的现象。
+
+这种现象的解决方法:添加一个状态标志,让 WaitThread 调用 wait() 方法前先判断状态是否已经改变,如果通知早已发出的话,WaitThread 就不再调用 wait() 方法。对上面的代码进行更正:
+
+```java
+public class EarlyNotify2 {
+ private static boolean isWait = true; //isWait 判断线程是否需要等待
+
+ public static void main(String[] args) {
+ final Object lock = new Object();
+
+ Thread notifyThread = new Thread(new NotifyThread(lock),"notifyThread");
+ Thread waitThread = new Thread(new WaitThread(lock),"waitThread");
+ notifyThread.start();
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ waitThread.start();
+ }
+
+ private static class WaitThread implements Runnable{
+ private Object lock;
+
+ public WaitThread(Object lock){
+ this.lock = lock;
+ }
+
+ @Override
+ public void run() {
+ synchronized (lock) {
+ try {
+ System.out.println(Thread.currentThread().getName() + " 进去代码块");
+ while (isWait){
+ System.out.println(Thread.currentThread().getName() + " 开始 wait");
+ lock.wait();
+ System.out.println(Thread.currentThread().getName() + " 结束 wait");
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private static class NotifyThread implements Runnable{
+ private Object lock;
+
+ public NotifyThread(Object lock){
+ this.lock = lock;
+ }
+
+ @Override
+ public void run() {
+ synchronized (lock) {
+ System.out.println(Thread.currentThread().getName() + " 进去代码块");
+ System.out.println(Thread.currentThread().getName() + " 开始 notify");
+ lock.notify();
+ isWait = false; //已经唤醒了
+ System.out.println(Thread.currentThread().getName() + " 结束开始 notify");
+ }
+ }
+ }
+}
+```
+
+```html
+notifyThread 进去代码块
+notifyThread 开始 notify
+notifyThread 结束开始 notify
+waitThread 进去代码块
+```
+
+增加了一个 isWait 状态变量,NotifyThread 调用 notify() 方法后会对状态变量进行更新,在 WaitThread 中调用wait() 方法之前会先对状态变量进行判断,在该示例中,调用 notify() 后将状态变量 isWait 改变为 false ,因此,在 WaitThread 中 while 对 isWait 判断后就不会执行 wait 方法,从而**避免了Notify过早通知造成遗漏的情况。**
+
+小结:在使用线程的等待 / 通知机制时,一般都要配合一个 boolean 变量值(或者其他能够判断真假的条件),在 notify 之前改变该 boolean 变量的值,让 wait 返回后能够退出 while 循环(一般都要在 wait 方法外围加一层 while 循环,以防止早期通知),或在通知被遗漏后,不会被阻塞在 wait() 方法处。这样便保证了程序的正确性。
+
+> **等待 wait 的条件发生变化**
+
+如果线程在等待时接受到了通知,但是之后**等待的条件**发生了变化,并没有再次对等待条件进行判断,也会导致程序出现错误。
+
+```java
+public class ConditionChange {
+ public static void main(String[] args) {
+ final List lock = new ArrayList();
+ Thread consumer1 = new Thread(new Consumer(lock),"consume1");
+ Thread consumer2 = new Thread(new Consumer(lock),"consume1");
+ Thread producer = new Thread(new Producer(lock),"producer");
+ consumer1.start();
+ consumer2.start();
+ producer.start();
+ }
+
+ static class Consumer implements Runnable{
+ private List lock;
+
+ public Consumer(List lock) {
+ this.lock = lock;
+ }
+
+ @Override
+ public void run() {
+ synchronized (lock) {
+ try {
+ //这里使用if的话,就会存在 wait 条件变化造成程序错误的问题
+ if(lock.isEmpty()) {
+ System.out.println(Thread.currentThread().getName() + " list 为空");
+ System.out.println(Thread.currentThread().getName() + " 调用 wait 方法");
+ lock.wait();
+ System.out.println(Thread.currentThread().getName() + " wait 方法结束");
+ }
+ String element = lock.remove(0);
+ System.out.println(Thread.currentThread().getName() + " 取出第一个元素为:" + element);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ static class Producer implements Runnable{
+ private List lock;
+
+ public Producer(List lock) {
+ this.lock = lock;
+ }
+
+ @Override
+ public void run() {
+ synchronized (lock) {
+ System.out.println(Thread.currentThread().getName() + " 开始添加元素");
+ lock.add(Thread.currentThread().getName());
+ lock.notifyAll();
+ }
+ }
+ }
+}
+```
+
+```html
+consume1 list 为空
+consume1 调用 wait 方法
+consume2 list 为空
+consume2 调用 wait 方法
+producer 开始添加元素
+consume2 wait 方法结束
+Exception in thread "consume1" consume2 取出第一个元素为:producer
+java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
+consume1 wait 方法结束
+```
+
+异常原因分析:例子中一共开启了 3 个线程:consumer1,consumer2 以及 producer。
+
+首先 consumer1调用了 wait 方法后,线程处于了 WAITTING 状态,并且将对象锁释放出来。consumer2 能够获取对象锁,从而进入到同步代块中,当执行到 wait 方法时,同样的也会释放对象锁。productor 能够获取到对象锁,进入到同步代码块中,向 list 中插入数据后,通过 notifyAll 方法通知处于 WAITING 状态的 consumer1 和consumer2 。consumer2 得到对象锁后,从 wait 方法出退出,退出同步块,释放掉对象锁,然后删除了一个元素,list 为空。这个时候 consumer1 获取到对象锁后,从 wait 方法退出,继续往下执行,这个时候consumer1 再执行
+
+```java
+lock.remove(0)
+```
+
+就会出错,因为 list 由于 consumer2 删除一个元素之后已经为空了。
+
+解决方案:通过上面的分析,可以看出 consumer1 报异常是因为线程从 wait 方法退出之后没有再次对 wait 条件进行判断,因此,此时的 wait 条件已经发生了变化。解决办法就是,在 wait 退出之后再对条件进行判断即可。
+
+```java
+public class ConditionChange2 {
+ public static void main(String[] args) {
+ final List lock = new ArrayList();
+ Thread consumer1 = new Thread(new Consumer(lock),"consume1");
+ Thread consumer2 = new Thread(new Consumer(lock),"consume2");
+ Thread producer = new Thread(new Producer(lock),"producer");
+ consumer1.start();
+ consumer2.start();
+ producer.start();
+ }
+
+ static class Consumer implements Runnable{
+ private List lock;
+
+ public Consumer(List lock) {
+ this.lock = lock;
+ }
+
+ @Override
+ public void run() {
+ synchronized (lock) {
+ try {
+ //这里使用if的话,就会存在 wait 条件变化造成程序错误的问题
+ while(lock.isEmpty()) {
+ System.out.println(Thread.currentThread().getName() + " list 为空");
+ System.out.println(Thread.currentThread().getName() + " 调用 wait 方法");
+ lock.wait();
+ System.out.println(Thread.currentThread().getName() + " wait 方法结束");
+ }
+ String element = lock.remove(0);
+ System.out.println(Thread.currentThread().getName() + " 取出第一个元素为:" + element);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ static class Producer implements Runnable{
+ private List lock;
+
+ public Producer(List lock) {
+ this.lock = lock;
+ }
+
+ @Override
+ public void run() {
+ synchronized (lock) {
+ System.out.println(Thread.currentThread().getName() + " 开始添加元素");
+ lock.add(Thread.currentThread().getName());
+ lock.notifyAll();
+ }
+ }
+ }
+}
+```
+
+```html
+consume1 list 为空
+consume1 调用 wait 方法
+consume2 list 为空
+consume2 调用 wait 方法
+producer 开始添加元素
+consume2 wait 方法结束
+consume2 取出第一个元素为:producer
+consume1 wait 方法结束
+consume1 list 为空
+consume1 调用 wait 方法
+```
+
+上面的代码与之前的代码仅仅只是将 wait() 外围的 if 语句改为 while 循环即可,这样当 list 为空时,线程便会继续等待,而不会继续去执行删除 list 中元素的代码。
+
+小结:在使用线程的等待/通知机制时,一般都要在 while 循环中调用 wait()方法,配合使用一个 boolean 变量(或其他能判断真假的条件,如本文中的 list.isEmpty())。在满足 while 循环的条件时,进入 while 循环,执行 wait()方法,不满足 while 循环的条件时,跳出循环,执行后面的代码。
+
+> **"假死"状态**
+
+如果是多消费者和多生产者情况,如果使用 notify() 方法可能会出现"假死"的情况,即唤醒的是同类线程。
+
+原因分析:假设当前多个生产者线程会调用 wait 方法阻塞等待,当其中的生产者线程获取到对象锁之后使用notify 通知处于 WAITTING 状态的线程,如果唤醒的仍然是生产者线程,就会造成所有的生产者线程都处于等待状态。
+
+解决办法:将 notify() 方法替换成 notifyAll() 方法,如果使用的是 lock 的话,就将 signal() 方法替换成 signalAll()方法。
+
+> **Object提供的消息通知机制总结**
+
+- 永远在 while 循环中对条件进行判断而不是 if 语句中进行 wait 条件的判断
+- 使用 notifyAll() 而不是使用 notify()
+
+基本的使用范式如下:
+
+```java
+// The standard idiom for calling the wait method in Java
+synchronized (sharedObject) {
+ while (condition) {
+ sharedObject.wait();
+ // (Releases lock, and reacquires on wakeup)
+ }
+ // do action based upon condition e.g. take or put into queue
+}
+```
+
+### 1、wait() / notifyAll() 实现生产者-消费者
+
+```java
+public class ProducerConsumer {
+ public static void main(String[] args) {
+ final LinkedList linkedList = new LinkedList();
+ final int capacity = 5;
+ Thread producer = new Thread(new Producer(linkedList,capacity),"producer");
+ Thread consumer = new Thread(new Consumer(linkedList),"consumer");
+
+ producer.start();
+ consumer.start();
+ }
+
+ static class Producer implements Runnable {
+ private List list;
+ private int capacity;
+
+ public Producer(List list, int capacity) {
+ this.list = list;
+ this.capacity = capacity;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ synchronized (list) {
+ try {
+ while (list.size() == capacity) {
+ System.out.println("生产者 " + Thread.currentThread().getName() + " list 已达到最大容量,进行 wait");
+ list.wait();
+ System.out.println("生产者 " + Thread.currentThread().getName() + " 退出 wait");
+ }
+ Random random = new Random();
+ int i = random.nextInt();
+ System.out.println("生产者 " + Thread.currentThread().getName() + " 生产数据" + i);
+ list.add(i);
+ list.notifyAll();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+ }
+ }
+
+ static class Consumer implements Runnable {
+ private List list;
+
+ public Consumer(List list) {
+ this.list = list;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ synchronized (list) {
+ try {
+ while (list.isEmpty()) {
+ System.out.println("消费者 " + Thread.currentThread().getName() + " list 为空,进行 wait");
+ list.wait();
+ System.out.println("消费者 " + Thread.currentThread().getName() + " 退出wait");
+ }
+ Integer element = list.remove(0);
+ System.out.println("消费者 " + Thread.currentThread().getName() + " 消费数据:" + element);
+ list.notifyAll();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+输出结果:
+
+```html
+生产者 producer 生产数据-1652445373
+生产者 producer 生产数据1234295578
+生产者 producer 生产数据-1885445180
+生产者 producer 生产数据864400496
+生产者 producer 生产数据621858426
+生产者 producer list 已达到最大容量,进行 wait
+消费者 consumer 消费数据:-1652445373
+消费者 consumer 消费数据:1234295578
+消费者 consumer 消费数据:-1885445180
+消费者 consumer 消费数据:864400496
+消费者 consumer 消费数据:621858426
+消费者 consumer list 为空,进行 wait
+生产者 producer 退出 wait
+```
+
+### 2、await() / signalAll() 实现生产者-消费者
+
+```java
+public class ProducerConsumer {
+ private static ReentrantLock lock = new ReentrantLock();
+ private static Condition full = lock.newCondition();
+ private static Condition empty = lock.newCondition();
+
+ public static void main(String[] args) {
+ final LinkedList linkedList = new LinkedList();
+ final int capacity = 5;
+ Thread producer = new Thread(new Producer(linkedList,capacity,lock),"producer");
+ Thread consumer = new Thread(new Consumer(linkedList,lock),"consumer");
+
+ producer.start();
+ consumer.start();
+ }
+
+ static class Producer implements Runnable {
+ private List list;
+ private int capacity;
+ private Lock lock;
+
+ public Producer(List list, int capacity,Lock lock) {
+ this.list = list;
+ this.capacity = capacity;
+ this.lock = lock;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ lock.lock();
+ try {
+ while (list.size() == capacity) {
+ System.out.println("生产者 " + Thread.currentThread().getName() + " list 已达到最大容量,进行 wait");
+ full.await();
+ System.out.println("生产者 " + Thread.currentThread().getName() + " 退出 wait");
+ }
+ Random random = new Random();
+ int i = random.nextInt();
+ System.out.println("生产者 " + Thread.currentThread().getName() + " 生产数据" + i);
+ list.add(i);
+ empty.signalAll();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }finally {
+ lock.unlock();
+ }
+
+ }
+ }
+ }
+
+ static class Consumer implements Runnable {
+ private List list;
+ private Lock lock;
+
+ public Consumer(List list,Lock lock) {
+ this.list = list;
+ this.lock = lock;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ lock.lock();
+ try {
+ while (list.isEmpty()) {
+ System.out.println("消费者 " + Thread.currentThread().getName() + " list 为空,进行 wait");
+ empty.await();
+ System.out.println("消费者 " + Thread.currentThread().getName() + " 退出wait");
+ }
+ Integer element = list.remove(0);
+ System.out.println("消费者 " + Thread.currentThread().getName() + " 消费数据:" + element);
+ full.signalAll();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }finally {
+ lock.unlock();
+ }
+ }
+ }
+ }
+}
+```
+
+```html
+生产者 producer 生产数据-1748993481
+生产者 producer 生产数据-131075825
+生产者 producer 生产数据-683676621
+生产者 producer 生产数据1543722525
+生产者 producer 生产数据804266076
+生产者 producer list 已达到最大容量,进行 wait
+消费者 consumer 消费数据:-1748993481
+消费者 consumer 消费数据:-131075825
+消费者 consumer 消费数据:-683676621
+消费者 consumer 消费数据:1543722525
+消费者 consumer 消费数据:804266076
+消费者 consumer list 为空,进行 wait
+生产者 producer 退出 wait
+```
+
+### 3、BlockingQueue 实现生产者-消费者
+
+由于 BlockingQueue 内部实现就附加了两个阻塞操作。即
+
+- 当队列已满时,阻塞向队列中插入数据的线程,直至队列中未满
+- 当队列为空时,阻塞从队列中获取数据的线程,直至队列非空时为止
+
+可以利用 BlockingQueue 实现生产者-消费者,**阻塞队列完全可以充当共享数据区域**,就可以很好的完成生产者和消费者线程之间的协作。
+
+```java
+public class ProducerConsumer {
+ private static LinkedBlockingQueue queue = new LinkedBlockingQueue();
+
+ public static void main(String[] args) {
+ Thread producer = new Thread(new Producer(queue),"producer");
+ Thread consumer = new Thread(new Consumer(queue),"consumer");
+
+ producer.start();
+ consumer.start();
+ }
+
+ static class Producer implements Runnable {
+ private BlockingQueue queue;
+
+ public Producer(BlockingQueue queue) {
+ this.queue = queue;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ Random random = new Random();
+ int i = random.nextInt();
+ System.out.println("生产者" + Thread.currentThread().getName() + "生产数据" + i);
+ try {
+ queue.put(i);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ static class Consumer implements Runnable {
+ private BlockingQueue queue;
+
+ public Consumer(BlockingQueue queue) {
+ this.queue = queue;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ Integer element = (Integer) queue.take();
+ System.out.println("消费者" + Thread.currentThread().getName() + "正在消费数据" + element);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+}
+```
+
+```html
+生产者producer生产数据-222876564
+消费者consumer正在消费数据-906876105
+生产者producer生产数据-9385856
+消费者consumer正在消费数据1302744938
+生产者producer生产数据-177925219
+生产者producer生产数据-881052378
+生产者producer生产数据-841780757
+生产者producer生产数据-1256703008
+消费者consumer正在消费数据1900668223
+消费者consumer正在消费数据2070540191
+消费者consumer正在消费数据1093187
+消费者consumer正在消费数据6614703
+消费者consumer正在消费数据-1171326759
+```
+
+使用 BlockingQueue 来实现生产者-消费者很简洁,这正是利用了 BlockingQueue 插入和获取数据附加阻塞操作的特性。
+
+## 参考资料
+
+- [Java 并发知识总结](https://github.com/CL0610/Java-concurrency)
+- [CS-Notes Java并发](https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/Java%20%E5%B9%B6%E5%8F%91.md)
+- 《 Java 并发编程的艺术》
+- [剑指Java面试-Offer直通车](https://coding.imooc.com/class/303.html)
From bfb1546554dd935f00d1ec1cf4f655af2681fe80 Mon Sep 17 00:00:00 2001
From: IvanLu1024 <501275190@qq.com>
Date: Tue, 9 Jul 2019 17:10:35 +0800
Subject: [PATCH 13/17] Update 11HTTPs.md
---
docs/NetWork/11HTTPs.md | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/docs/NetWork/11HTTPs.md b/docs/NetWork/11HTTPs.md
index 51ca883..ea03eb4 100644
--- a/docs/NetWork/11HTTPs.md
+++ b/docs/NetWork/11HTTPs.md
@@ -72,6 +72,14 @@ HTTP 也提供了 MD5 报文摘要功能,但不是安全的。例如报文内
HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认证这两个操作。试想一下,加密之后的报文,遭到篡改之后,也很难重新计算报文摘要,因为无法轻易获取明文。
+## 连接建立过程
+
+HTTPs的连接建立过程比较复杂,大致情况如下图所示:
+
+
+
+
+
## HTTPs 的缺点
- 因为需要进行加密解密等过程,因此速度会更慢;
From 464df35a48e5b03e2a0cab50ebad75fd618c0300 Mon Sep 17 00:00:00 2001
From: IvanLu1024 <501275190@qq.com>
Date: 2019年7月11日 15:50:08 +0800
Subject: [PATCH 14/17] =?UTF-8?q?=E6=9B=B4=E6=96=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...03347円275円221円347円273円234円345円261円202円.md" | 8 +++---
.../09HTTP351円246円226円351円203円250円.md" | 0
...11345円244円247円347円211円271円346円200円247円.md" | 27 ++++++++++++++-----
...1347円273円234円-347円233円256円345円275円225円.md" | 2 +-
4 files changed, 25 insertions(+), 12 deletions(-)
rename "docs/NetWork/09HTTP 351円246円226円351円203円250円.md" => "docs/NetWork/09HTTP351円246円226円351円203円250円.md" (100%)
diff --git "a/docs/NetWork/03347円275円221円347円273円234円345円261円202円.md" "b/docs/NetWork/03347円275円221円347円273円234円345円261円202円.md"
index 8293df4..a5a9166 100644
--- "a/docs/NetWork/03347円275円221円347円273円234円345円261円202円.md"
+++ "b/docs/NetWork/03347円275円221円347円273円234円345円261円202円.md"
@@ -112,7 +112,7 @@ ARP 实现由 IP 地址得到 MAC 地址。
## 网际控制报文协议 ICMP
-ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。
+ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。由于IP协议并不是一个可靠的协议,它不保证数据被送达,那么,自然地,保证数据送达的工作应该由其他的模块来完成。其中一个重要的模块就是ICMP(网络控制报文)协议。它封装在 IP 数据报中,但是不属于高层协议。
@@ -124,17 +124,17 @@ ICMP 报文分为差错报告报文和询问报文。
Ping 是 ICMP 的一个重要应用,主要用来测试两台主机之间的连通性。
-Ping 的原理是通过向目的主机发送 ICMP Echo 请求报文,目的主机收到之后会发送 Echo 回答报文。Ping 会根据时间和成功响应的次数估算出数据包往返时间以及丢包率。
+**Ping 的原理:**通过向目的主机发送 ICMP Echo 请求报文,目的主机收到之后会发送 Echo 回答报文。Ping 会根据时间和成功响应的次数估算出数据包往返时间以及丢包率。
### 2. Traceroute
-Traceroute 是 ICMP 的另一个应用,用来跟踪一个分组从源点到终点的路径。
+Traceroute 是 ICMP 的另一个应用,用来**跟踪一个分组从源点到终点的路径**。
Traceroute 发送的 IP 数据报封装的是无法交付的 UDP 用户数据报,并由目的主机发送终点不可达差错报告报文。
- 源主机向目的主机发送一连串的 IP 数据报。第一个数据报 P1 的生存时间 TTL 设置为 1,当 P1 到达路径上的第一个路由器 R1 时,R1 收下它并把 TTL 减 1,此时 TTL 等于 0,R1 就把 P1 丢弃,并向源主机发送一个 ICMP 时间超过差错报告报文;
- 源主机接着发送第二个数据报 P2,并把 TTL 设置为 2。P2 先到达 R1,R1 收下后把 TTL 减 1 再转发给 R2,R2 收下后也把 TTL 减 1,由于此时 TTL 等于 0,R2 就丢弃 P2,并向源主机发送一个 ICMP 时间超过差错报文。
-- 不断执行这样的步骤,直到最后一个数据报刚刚到达目的主机,主机不转发数据报,也不把 TTL 值减 1。但是因为数据报封装的是无法交付的 UDP,因此目的主机要向源主机发送 ICMP 终点不可达差错报告报文。
+- 不断执行这样的步骤,直到最后一个数据报刚刚到达目的主机,主机不转发数据报,也不把 TTL 值减 1。这样,**Traceroute 就拿到了所有的路由器IP,从而避开了IP头只能记录有限路由IP的问题。**但是因为数据报封装的是无法交付的 UDP,因此目的主机要向源主机发送 ICMP 终点不可达差错报告报文。
- 之后源主机知道了到达目的主机所经过的路由器 IP 地址以及到达每个路由器的往返时间。
## 虚拟专用网 VPN
diff --git "a/docs/NetWork/09HTTP 351円246円226円351円203円250円.md" "b/docs/NetWork/09HTTP351円246円226円351円203円250円.md"
similarity index 100%
rename from "docs/NetWork/09HTTP 351円246円226円351円203円250円.md"
rename to "docs/NetWork/09HTTP351円246円226円351円203円250円.md"
diff --git "a/docs/OO/04351円235円242円345円220円221円345円257円271円350円261円241円344円270円211円345円244円247円347円211円271円346円200円247円.md" "b/docs/OO/04351円235円242円345円220円221円345円257円271円350円261円241円344円270円211円345円244円247円347円211円271円346円200円247円.md"
index d379467..05f839f 100644
--- "a/docs/OO/04351円235円242円345円220円221円345円257円271円350円261円241円344円270円211円345円244円247円347円211円271円346円200円247円.md"
+++ "b/docs/OO/04351円235円242円345円220円221円345円257円271円350円261円241円344円270円211円345円244円247円347円211円271円346円200円247円.md"
@@ -26,9 +26,9 @@
### 3. 成员变量和局部变量的区别
| 区别 | 成员变量 | 局部变量 |
-| :--: | :--: | :--: |
+| :--: | :--: | :--: |
| 在类中位置不同 | 类中方法外 | 方法内或者方法声明上 |
-| 在内存中位置不同 | 堆内存 | 栈内存 |
+| 在内存中位置不同 | 堆内存 | 栈内存 |
| 声明周期不同 | 随着**对象**存在而存在,随着对象消失而消失 | 随着**方法**调用而存在,随着方法调用完毕而消失 |
| 初始化值不同 | 有默认的初始值 | 没有默认的初始值,必须先定义,赋值,才能使用 |
@@ -189,7 +189,7 @@ public class SmartPhoneDemo2 {
| 区别 | 静态变量 | 成员变量 |
| :--: | :--: | :--: |
-| 所属不同 | 属于类,所以也称为**类变量** | 属于对象,所以也称为**实例变量**(对象变量) |
+| 所属不同 | 属于类,所以也称为**类变量** | 属于对象,所以也称为**实例变量**(对象变量) |
| 内存中位置不同 | 存储于**方法区**的静态区 | 存储于堆内存 |
| 内存中出现时间不同 | 随着**类**的加载而加载,随着类的消失而消失 | 随着**对象**的创建而存在,随着对象的消失而消失 |
| 调用不同 | 可以通过类名调用,也可以通过对象调用 | 只能通过的对象来调用 |
@@ -267,7 +267,8 @@ Animal animal = new Cat();
### 5. 继承中成员变量的关系
- 子类中成员变量和父类中成员变量名称不同。
- 子类中成员变量和父类中成员变量名称相同。
-
+
+
在子类方法中的查找顺序:
在子类方法的局部范围找,有就使用
@@ -275,7 +276,7 @@ Animal animal = new Cat();
在子类的成员范围找,有就使用
在**父类的成员范围**找,有就使用
-
+
如果还找不到,就报错
### 6. 继承中构造方法的关系
@@ -290,7 +291,7 @@ Animal animal = new Cat();
3. 如果父类中没有无参构造方法,该怎么办呢?
方式一:子类通过super去显示调用父类其他的带参的构造方法
-
+
方式二:子类通过this去调用本类的其他构造方法
(子类一定要有一个去访问父类的构造方法,否则父类数据就没有初始化)
@@ -746,4 +747,16 @@ public class PolymorphismDemo2 {
100
show Zi
function Fu
-```
\ No newline at end of file
+```
+
+### 6. Java中多态实现的原理*
+
+多态允许具体访问时实现方法的动态绑定。Java对于动态绑定的实现主要依赖于**方法表**,通过继承和接口的多态实现有所不同。
+
+- 继承:在执行某个方法时,在方法区中找到该类的方法表,再确认该方法在方法表中的**偏移量**,找到该方法后如果被重写则直接调用,否则认为没有重写父类该方法,这时会按照继承关系搜索父类的方法表中该偏移量对应的方法。 因为方法表的偏移量总是**固定的**,所有继承父类的子类的方法表中,其父类所定义的方法的偏移量也总是一个**定值**。
+
+- 接口:Java 允许一个类实现多个接口,从某种意义上来说相当于多继承,这样同一个接口的的方法在不同类方法表中的**位置就可能不一样了**。所以不能通过偏移量的方法,而是通过**搜索完整的方法表**。
+
+# 参考资料
+
+- [Java技术——多态的实现原理]()
\ No newline at end of file
diff --git "a/docs/347円275円221円347円273円234円-347円233円256円345円275円225円.md" "b/docs/347円275円221円347円273円234円-347円233円256円345円275円225円.md"
index 4aa3ab3..24c2c4e 100644
--- "a/docs/347円275円221円347円273円234円-347円233円256円345円275円225円.md"
+++ "b/docs/347円275円221円347円273円234円-347円233円256円345円275円225円.md"
@@ -14,7 +14,7 @@
- [基础概念](./NetWork/06基础概念.md)
- [HTTP 方法](./NetWork/07HTTP%%方法.md)
- [HTTP 状态码](./NetWork/08HTTP%%状态码.md)
-- [HTTP 首部](./NetWork/09HTTP%%首部.md)
+- [HTTP 首部](./NetWork/09HTTP首部.md)
- [具体应用](./NetWork/10具体应用.md)
- [HTTPs](./NetWork/11HTTPs.md)
- [HTTP20](./NetWork/12HTTP20.md)
From e40eb28e959f9c40da3be92bc37170c412d94cae Mon Sep 17 00:00:00 2001
From: IvanLu1024 <501275190@qq.com>
Date: 2019年7月12日 08:54:45 +0800
Subject: [PATCH 15/17] =?UTF-8?q?Update=20Java=20=E5=A4=9A=E7=BA=BF?=
=?UTF-8?q?=E7=A8=8B=E4=B8=8E=E5=B9=B6=E5=8F=91-=E5=8E=9F=E7=90=86.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
...1266円345円217円221円-345円216円237円347円220円206円.md" | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git "a/docs/Java/Java 345円244円232円347円272円277円347円250円213円344円270円216円345円271円266円345円217円221円-345円216円237円347円220円206円.md" "b/docs/Java/Java 345円244円232円347円272円277円347円250円213円344円270円216円345円271円266円345円217円221円-345円216円237円347円220円206円.md"
index 3b999f7..d9c18df 100644
--- "a/docs/Java/Java 345円244円232円347円272円277円347円250円213円344円270円216円345円271円266円345円217円221円-345円216円237円347円220円206円.md"
+++ "b/docs/Java/Java 345円244円232円347円272円277円347円250円213円344円270円216円345円271円266円345円217円221円-345円216円237円347円220円206円.md"
@@ -360,6 +360,20 @@ public class SyncBlockAndMethod {
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的是 **ACC_SYNCHRONIZED 标识**,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
+### synchronized的可重入性
+
+如果一个获取锁的线程调用其它的synchronized修饰的方法,会发生什么?
+
+从设计上讲,当一个线程请求一个由其他线程持有的对象锁时,该线程会阻塞。当线程请求自己持有的对象锁时,如果该线程是重入锁,请求就会成功,否则阻塞。
+
+我们回来看synchronized,synchronized拥有强制原子性的内部锁机制,是一个可重入锁。因此,在一个线程使用synchronized方法时调用该对象另一个synchronized方法,即一个线程得到一个对象锁后再次请求该对象锁,是**永远可以拿到锁的**。
+
+在 Java 内部,同一个线程调用自己类中其他synchronized方法/块时不会阻碍该线程的执行,**同一个线程对同一个对象锁是可重入的,同一个线程可以获取同一把锁多次,也就是可以多次重入**。原因是 Java 中线程获得对象锁的操作是以**线程为单位**的,而不是以调用为单位的。
+
+### synchronized可重入锁的实现
+
+每个锁关联一个线程持有者和一个计数器。当计数器为0时表示该锁没有被任何线程持有,那么任何线程都都可能获得该锁而调用相应方法。当一个线程请求成功后,JVM会记下持有锁的线程,并将计数器计为1。此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁。
+
### JDK1.6 之后的锁优化
JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等**技术**来减少锁操作的开销。
@@ -3677,3 +3691,4 @@ public static void main(String[] args) {
- [Java 并发知识总结](https://github.com/CL0610/Java-concurrency)
- [CS-Notes Java并发](https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/Java%20%E5%B9%B6%E5%8F%91.md)
- [《 Java 并发编程的艺术》]()
+- [Java多线程:synchronized的可重入性](https://www.cnblogs.com/cielosun/p/6684775.html)
From 01803905ff40880c8a14285912ef8ca81587eb69 Mon Sep 17 00:00:00 2001
From: IvanLu1024 <501275190@qq.com>
Date: 2019年7月31日 09:49:27 +0800
Subject: [PATCH 16/17] =?UTF-8?q?=E7=AC=94=E8=AE=B0=E8=A1=A5=E5=85=85?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/BigData/Zookeeper.md | 13 +++++++++++++
docs/DataBase/Redis.md | 2 ++
...271266円345円217円221円-345円216円237円347円220円206円.md" | 6 ++++++
docs/Spring/02SpringAOP.md | 2 +-
...270円270円350円247円201円346円263円250円350円247円243円.md" | 13 ++++++++++++-
...275221円347円273円234円-347円233円256円345円275円225円.md" | 1 +
6 files changed, 35 insertions(+), 2 deletions(-)
diff --git a/docs/BigData/Zookeeper.md b/docs/BigData/Zookeeper.md
index b05f72c..a7266a3 100644
--- a/docs/BigData/Zookeeper.md
+++ b/docs/BigData/Zookeeper.md
@@ -199,6 +199,19 @@ ZAB 协议两种基本的模式:崩溃恢复和消息广播。
当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进人**消息广播模式**了。当一台同样遵守 ZAB 协议的服务器启动后加人到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播,那么新加入的服务器就会自觉地进入数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。正如上文介绍中所说的,**Zookeeper 设计成只允许唯一的一个 Leader 服务器来进行事务请求的处理**。Leader 服务器在接收到客户端的事务请求后,会生成对应的事务提案并发起一轮广播协议;如果集群中的其他机器接收到客户端的事务请求,那么这些非 Leader 服务器会首先将这个事务请求转发给 Leader 服务器。
+# ZooKeeper的选举机制
+
+当leader崩溃或者leader失去大多数的follower,这时zk进入恢复模式,恢复模式需要重新选举出一个新leader,让所有的Server都恢复到一个正确的状态。Zk的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。
+
+1. Zookeeper选主流程(basic paxos)
+ (1)选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐Server;
+ (2)选举线程首先向所有Server发起一次询问(包括自己);
+ (3)选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
+ (4)收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server;
+ (5)线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2 + 1的Server票数,设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。 通过流程分析我们可以得出:要使Leader获得多数Server的支持,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于n+1. 每个Server启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的server还会从磁盘快照中恢复数据和会话信息,zk会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。
+2. Zookeeper选主流程(fast paxos)
+ fast paxos流程是在选举过程中,某Server首先向所有Server提议自己要成为leader,当其它Server收到提议以后,解决epoch和 zxid的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息,重复这个流程,最后一定能选举出Leader。
+
# Zookeeper 的应用场景
## 分布式协调/通知
diff --git a/docs/DataBase/Redis.md b/docs/DataBase/Redis.md
index 057ce36..0a0c7ba 100644
--- a/docs/DataBase/Redis.md
+++ b/docs/DataBase/Redis.md
@@ -1,5 +1,7 @@
## 一、概述
+[Redis脑图](http://naotu.baidu.com/file/b2cc07d1dabb080eac294567c31bbe04?token=6dcf94de0168d614)
+
Redis 是速度非常快的非关系型(NoSQL)内存键值数据库,可以存储键和五种不同类型的值之间的映射。
键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。
diff --git "a/docs/Java/Java 345円244円232円347円272円277円347円250円213円344円270円216円345円271円266円345円217円221円-345円216円237円347円220円206円.md" "b/docs/Java/Java 345円244円232円347円272円277円347円250円213円344円270円216円345円271円266円345円217円221円-345円216円237円347円220円206円.md"
index d9c18df..cb71d12 100644
--- "a/docs/Java/Java 345円244円232円347円272円277円347円250円213円344円270円216円345円271円266円345円217円221円-345円216円237円347円220円206円.md"
+++ "b/docs/Java/Java 345円244円232円347円272円277円347円250円213円344円270円216円345円271円266円345円217円221円-345円216円237円347円220円206円.md"
@@ -323,6 +323,12 @@ public class Singleton {
考虑下面的实现,也就是只使用了一个 if 语句。 在 `uniqueInstance == null` 的情况下,如果两个线程都执行了 if 语句,那么两个线程都会进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 `uniqueInstance = new Singleton();` 这条语句,只是先后的问题,那么就会进行两次实例化。
+### 锁的内存语义
+
+- 锁的释放-获取遵循Happens-before
+- 当线程释放锁的时候,JMM会把该线程对应的工作内存中的共享变量刷新到主内存中
+- 当线程获取锁的时候,JMM会把该线程对应的工作内存中的共享变量置为无效
+
### synchronized 原理
```java
diff --git a/docs/Spring/02SpringAOP.md b/docs/Spring/02SpringAOP.md
index c1d87bd..d58e67a 100644
--- a/docs/Spring/02SpringAOP.md
+++ b/docs/Spring/02SpringAOP.md
@@ -600,7 +600,7 @@ public class SpringTest {
}
```
-### 区分基于ProxyFattoryBean的代理与自动代理区别?
+### 区分基于ProxyFactoryBean的代理与自动代理区别?
- ProxyFactoryBean:先有被代理对象,将被代理对象传入到代理类中生成代理。
diff --git "a/docs/Spring/Spring 345円270円270円350円247円201円346円263円250円350円247円243円.md" "b/docs/Spring/Spring 345円270円270円350円247円201円346円263円250円350円247円243円.md"
index ab2a945..d84ae93 100644
--- "a/docs/Spring/Spring 345円270円270円350円247円201円346円263円250円350円247円243円.md"
+++ "b/docs/Spring/Spring 345円270円270円350円247円201円346円263円250円350円247円243円.md"
@@ -47,4 +47,15 @@ http://host:port/path/参数值
## @ResponseBody
-该注解用于将 Controller 中方法返回的对象,通过适当的 HttpMessageConverter 转换为指定的格式后,写入到 Response 对象的 body s数据区。
\ No newline at end of file
+该注解用于将 Controller 中方法返回的对象,通过适当的 HttpMessageConverter 转换为指定的格式后,写入到 Response 对象的 body s数据区。
+
+## @Autowired和@Resource的区别
+
+1. @Autowired与@Resource都可以用来装配bean,也都可以写在字段上,或写在setter方法上。
+2. @Autowired默认按**类型**装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false。
+3. @Resource(这个注解属于J2EE的),默认按照**名称**进行装配,名称可以通过name属性进行指定, 如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。这个注解是属于J2EE的,减少了与spring的耦合。
+
+# 参考资料
+
+- https://blog.csdn.net/u011067360/article/details/38873755
+
diff --git "a/docs/347円275円221円347円273円234円-347円233円256円345円275円225円.md" "b/docs/347円275円221円347円273円234円-347円233円256円345円275円225円.md"
index 24c2c4e..9649764 100644
--- "a/docs/347円275円221円347円273円234円-347円233円256円345円275円225円.md"
+++ "b/docs/347円275円221円347円273円234円-347円233円256円345円275円225円.md"
@@ -19,6 +19,7 @@
- [HTTPs](./NetWork/11HTTPs.md)
- [HTTP20](./NetWork/12HTTP20.md)
- [get和post比较](./NetWork/13get和post比较.md)
+- [HTTP脑图](http://naotu.baidu.com/file/3f21f7d32eec65e66408e6c60849c5cc?token=723248a938dcd301)
### Socket
From 925fc29ac3eb0eab36e22c824ff7ea9a2fae716b Mon Sep 17 00:00:00 2001
From: IvanLu1024 <501275190@qq.com>
Date: 2019年7月31日 11:12:05 +0800
Subject: [PATCH 17/17] =?UTF-8?q?Update=2010=E7=B4=A2=E5=BC=95.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
"docs/DataBase/10347円264円242円345円274円225円.md" | 36 ++++++++++++++++---
1 file changed, 31 insertions(+), 5 deletions(-)
diff --git "a/docs/DataBase/10347円264円242円345円274円225円.md" "b/docs/DataBase/10347円264円242円345円274円225円.md"
index 986f490..d07acad 100644
--- "a/docs/DataBase/10347円264円242円345円274円225円.md"
+++ "b/docs/DataBase/10347円264円242円345円274円225円.md"
@@ -128,6 +128,28 @@ B+ 树是 B 树的变体,其定义基本与 B 树相同,除了:
**InnoDB 存储引擎有一个特殊的功能叫"自适应哈希索引"**,当某个索引值被使用的非常频繁时,会在 B+ 树索引之上再创建一个哈希索引,这样就让 B+ 树索引具有哈希索引的一些优点,比如快速的哈希查找。
+## 聚集(密集)索引和非聚集索引
+
+### 聚集索引
+
+定义:数据行的**物理顺序与列值(一般是主键的那一列)的逻辑顺序相同**,一个表中只能拥有一个聚集索引。
+
+> 索引文件中的每个搜索码对应一个索引值。
+
+打个比方,一个表就像是我们以前用的新华字典,聚集索引就像是**拼音目录**,而每个字存放的页码就是我们的数据物理地址,我们如果要查询一个"哇"字,我们只需要查询"哇"字对应在新华字典拼音目录对应的页码,就可以查询到对应的"哇"字所在的位置,而拼音目录对应的A-Z的字顺序,和新华字典实际存储的字的顺序A-Z也是一样的。
+
+由于物理排列方式与聚集索引的顺序相同,所以一张表只能建立一个聚集索引。
+
+### 非聚集索引
+
+定义:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引。
+
+其实按照定义,除了聚集索引以外的索引都是非聚集索引,只是人们想细分一下非聚集索引,分成普通索引,唯一索引,全文索引。如果非要把非聚集索引类比成现实生活中的东西,那么非聚集索引就像新华字典的偏旁字典,其结构顺序与实际存放顺序不一定一致。
+
+> 索引文件只为索引码的某些值建立索引项。
+
+
+
## 索引的物理存储
索引是在**存储引擎层实现**的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。
@@ -136,7 +158,7 @@ MySQL主要的存储引擎是 MyISAM 和 InnoDB。
### MyISAM 索引存储机制
-MyISAM 引擎使用 B+ 树作索引结构,**叶子节点的 data 域存放的是数据记录的地址**。
+MyISAM 引擎使用 B+ 树作索引结构,**叶子节点的 data 域存放的是数据记录的地址**,所有索引均是非聚集索引。
@@ -155,7 +177,7 @@ MyISAM 的索引方式也叫做**非聚集索引(稀疏索引)**(索引和
### InnoDB 索引存储机制
-InnoDB 也使用 B+ 树作为索引结构。
+InnoDB 也使用 B+ 树作为索引结构。有且仅有一个聚集索引,和多个非聚集索引。
InnoDB 的数据文件本身就是索引文件。MyISAM 索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在 InnoDB 中,表数据文件本身就是按 B+ 树组织的一个索引结构,这棵树的**叶子节点 data 域保存了完整的数据记录**。这个索引的 key 是数据表的主键,因此 **InnoDB 表数据文件本身就是主索引**。
@@ -165,7 +187,11 @@ InnoDB 的数据文件本身就是索引文件。MyISAM 索引文件和数据文
上图是 InnoDB 主索引(同时也是数据文件)的示意图。可以看到叶子节点包含了完整的数据记录。
-这种索引叫做**聚集索引(密集索引)**(索引和数据保存在同一文件中)。因为 InnoDB 的数据文件本身要按主键聚集,所以 InnoDB 要求表必须有主键( MyISAM 可以没有),如果没有显式指定,则 MySQL 系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则 MySQL 自动为 InnoDB 表生成一个隐含字段作为主键,这个字段长度为 6 个字节,类型为长整形。
+这种索引叫做**聚集索引(密集索引)**(索引和数据保存在同一文件中):
+
+- 若一个主键被定义,该主键作为聚集索引;
+- 若没有主键定义,该表的第一个唯一非空索引作为聚集索引;
+- 若均不满足,则会生成一个隐藏的主键( MySQL 自动为 InnoDB 表生成一个隐含字段作为主键,这个字段是递增的,长度为 6 个字节)。
与 MyISAM 索引的不同是 **InnoDB 的辅助索引 data 域存储相应记录主键的值**而不是地址。例如,定义在 Col3 上的一个辅助索引:
@@ -356,5 +382,5 @@ customer_id_selectivity: 0.0373
- [干货:mysql索引的数据结构](https://www.jianshu.com/p/1775b4ff123a)
- [MySQL优化系列(三)--索引的使用、原理和设计优化](https://blog.csdn.net/Jack__Frost/article/details/72571540)
- [数据库两大神器【索引和锁】](https://juejin.im/post/5b55b842f265da0f9e589e79#comment)
-
-- [Mysql索引整理总结](https://blog.csdn.net/u010648555/article/details/81102957)
\ No newline at end of file
+- [Mysql索引整理总结](https://blog.csdn.net/u010648555/article/details/81102957)
+- https://www.imooc.com/article/22915
\ No newline at end of file