Merge branch 'other_master'

# Conflicts:
#	README.md
pull/38/head
huifer 5 years ago
commit 8e03ee8260

@ -7,10 +7,10 @@
[![issues](https://badgen.net/github/open-issues/doocs/source-code-hunter)](https://github.com/doocs/source-code-hunter/issues)
[![PRs Welcome](https://badgen.net/badge/PRs/welcome/green)](http://makeapullrequest.com)
有被“读过哪些知名的开源项目源码?”这种问题所困扰过吗?加入我们,一起通读互联网公司主流框架及中间件源码,成为强大的“源码猎人”,目前开放的有 Spring 系列框架、Mybatis 框架、Netty 框架及Redis中间件等让我们一起开拓新的领地揭开这些源码的神秘面纱。本项目主要用于记录框架及中间件源码的阅读经验、个人理解及解析希望能够使阅读源码变成一件更简单有趣且有价值的事情抽空更新中...(如果本项目对您有帮助请watch、star、fork 素质三连一波,鼓励一下作者,谢谢)
有被“读过哪些知名的开源项目源码?”这种问题所困扰过吗?加入我们,一起通读互联网公司主流框架及中间件源码,成为强大的“源码猎人”,目前开放的有 Spring 系列框架、Mybatis 框架、Netty 框架,及 Redis、Tomcat 中间件等,让我们一起开拓新的领地,揭开这些源码的神秘面纱。本项目主要用于记录框架及中间件源码的阅读经验、个人理解及解析,希望能够使阅读源码变成一件更简单有趣,且有价值的事情,抽空更新中...(如果本项目对您有帮助请watch、star、fork 素质三连一波,鼓励一下作者,谢谢)
## Spring系列
### IoC容器
## Spring 系列
### IoC 容器
- [BeanDefinition 的资源定位过程](/docs/Spring/IoC/1、BeanDefinition的资源定位过程.md)
- [将 bean 解析封装成 BeanDefinition](/docs/Spring/IoC/2、将bean解析封装成BeanDefinition.md)
- [将 BeanDefinition 注册进 IoC 容器](/docs/Spring/IoC/3、将BeanDefinition注册进IoC容器.md)
@ -26,16 +26,17 @@
- [IoC容器 在 Web环境 中的启动](/docs/Spring/SpringMVC/IoC容器在Web环境中的启动.md)
- [SpringMVC 的设计与实现](/docs/Spring/SpringMVC/SpringMVC的设计与实现.md)
- [SpringMVC 跨域解析](/docs/Spring/SpringMVC/SpringMVC-CROS.md)
### SpringJDBC
- 努力编写中...
### Spring事务
### Spring 事务
- [Spring 与事务处理](/docs/Spring/SpringTransaction/Spring与事务处理.md)
- [Spring 声明式事务处理](/docs/Spring/SpringTransaction/Spring声明式事务处理.md)
- [Spring 事务处理的设计与实现](/docs/Spring/SpringTransaction/Spring事务处理的设计与实现.md)
- [Spring 事务管理器的设计与实现](/docs/Spring/SpringTransaction/Spring事务管理器的设计与实现.md)
### Spring源码故事瞎编版
### Spring 源码故事(瞎编版)
- [面筋哥 IoC 容器的一天(上)](/docs/Spring/Spring源码故事瞎编版/面筋哥IoC容器的一天(上).md)
### Spring 类解析
@ -48,6 +49,7 @@
- [Spring messageSource](/docs/Spring/clazz/Spring-MessageSource.md)
- [Spring 自定义属性解析器](/docs/Spring/clazz/Spring-Custom-attribute-resolver.md)
- [Spring 排序工具](/docs/Spring/clazz/Spring-OrderUtils.md)
- [Spring-import注解](/docs/Spring/clazz/Spring-Import.md)
- [Spring-定时任务](/docs/Spring/clazz/Spring-Scheduling.md)
### Spring5 新特性
@ -66,6 +68,7 @@
- [DataSource 及 Transaction 模块](docs/Mybatis/基础支持层/2、DataSource及Transaction模块.md)
- [binding 模块](docs/Mybatis/基础支持层/3、binding模块.md)
- [缓存模块](docs/Mybatis/基础支持层/4、缓存模块.md)
### 核心处理层
- [MyBatis 初始化](docs/Mybatis/核心处理层/1、MyBatis初始化.md)
- [SqlNode 和 SqlSource](docs/Mybatis/核心处理层/2、SqlNode和SqlSource.md)
@ -73,6 +76,7 @@
- [StatementHandler](docs/Mybatis/核心处理层/4、StatementHandler.md)
- [Executor 组件](docs/Mybatis/核心处理层/5、Executor组件.md)
- [SqlSession 组件](docs/Mybatis/核心处理层/6、SqlSession组件.md)
### 类解析
- [Mybatis-Cache](/docs/Mybatis/基础支持层/Mybatis-Cache.md)
- [Mybatis-log](/docs/Mybatis/基础支持层/Mybatis-log.md)
@ -108,7 +112,7 @@
- [基于WebSocket协议的Netty开发](docs/Netty/Netty多协议开发/基于WebSocket协议的Netty开发.md)
- [基于自定义协议的Netty开发](docs/Netty/Netty多协议开发/基于自定义协议的Netty开发.md)
### 基于Netty开发服务端及客户端
### 基于 Netty 开发服务端及客户端
- [基于Netty的服务端开发](docs/Netty/基于Netty开发服务端及客户端/基于Netty的服务端开发.md)
- [基于Netty的客户端开发](docs/Netty/基于Netty开发服务端及客户端/基于Netty的客户端开发.md)
@ -149,7 +153,7 @@
- [从框架源码中学习设计模式的感悟](docs/LearningExperience/DesignPattern/从框架源码中学习设计模式的感悟.md)
### 多线程
- [Java多线程编程在各主流框架中的应用]()
- [Java并发编程在各主流框架中的应用](docs/LearningExperience/ConcurrentProgramming/Java并发编程在各主流框架中的应用.md)
## 贡献者
感谢以下所有朋友对 [GitHub 技术社区 Doocs](https://github.com/doocs) 所做出的贡献,[参与项目维护请戳这儿](https://doocs.github.io/#/?id=how-to-join)。
@ -158,4 +162,4 @@
<a href="https://opencollective.com/doocs/contributors.svg?width=890&button=true"><img src="https://opencollective.com/doocs/contributors.svg?width=890&button=false" /></a>
<!-- ALL-CONTRIBUTORS-LIST:END -->
<!-- ALL-CONTRIBUTORS-LIST:END -->

@ -0,0 +1 @@
努力编写中...

@ -0,0 +1 @@
努力编写中...

@ -0,0 +1 @@
努力编写中...

@ -0,0 +1,654 @@
Spring、Netty、Mybatis 等框架的代码中大量运用了 Java 多线程编程技巧。并发编程处理的恰当与否,将直接影响架构的性能。本章通过对 这些框架源码 的分析,结合并发编程的常用技巧,来讲解多线程编程在这些主流框架中的应用。
## Java内存模型
JVM规范 定义了 Java内存模型 来屏蔽掉各种操作系统、虚拟机实现厂商和硬件的内存访问差异,以确保 Java 程序 在所有操作系统和平台上能够达到一致的内存访问效果。
### 工作内存和主内存
Java内存模型 规定所有的变量都存储在主内存中,每个线程都有自己独立的工作内存,工作内存保存了 对应该线程使用的变量的主内存副本拷贝。线程对这些变量的操作都在自己的工作内存中进行,不能直接操作主内存 和 其他工作内存中存储的变量或者变量副本。线程间的变量传递需通过主内存来完成,三者的关系如下图所示。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200221000348294.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70)
### Java内存操作协议
Java内存模型定义了8种操作来完成主内存和工作内存的变量访问具体如下。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200221001115193.png)
- read把一个变量的值从主内存传输到线程的工作内存中以便随后的 load 动作使用。
- load把从主内存中读取的变量值载入工作内存的变量副本中。
- use把工作内存中一个变量的值传递给 Java 虚拟机执行引擎。
- assign把从执行引擎接收到的变量的值赋值给工作内存中的变量。
- store把工作内存中一个变量的值传送到主内存中以便随后的write操作。
- write工作内存传递过来的变量值放入主内存中。
- lock把主内存的一个变量标识为某个线程独占的状态。
- unlock把主内存中 一个处于锁定状态的变量释放出来,被释放后的变量才可以被其他线程锁定。
### 内存模型三大特性
#### 1、原子性
这个概念与事务中的原子性大概一致,表明此操作是不可分割,不可中断的,要么全部执行,要么全部不执行。 Java内存模型直接保证的原子性操作包括read、load、use、assign、store、write、lock、unlock这八个。
#### 2、可见性
可见性是指当一个线程修改了共享变量的值其他线程能够立即得知这个修改。Java内存模型 是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是 volatile变量 都是如此,普通变量与 volatile变量 的区别是volatile 的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此,可以说 volatile 保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。除了 volatile 外synchronized 也提供了可见性synchronized 的可见性是由 “对一个变量执行 unlock操作 之前,必须先把此变量同步回主内存中(执行 store、write 操作)” 这条规则获得。
#### 3、有序性
单线程环境下,程序会 “有序的”执行,即:线程内表现为串行语义。但是在多线程环境下,由于指令重排,并发执行的正确性会受到影响。在 Java 中使用 volatile 和 synchronized 关键字可以保证多线程执行的有序性。volatile 通过加入内存屏障指令来禁止内存的重排序。synchronized 通过加锁,保证同一时刻只有一个线程来执行同步代码。
## volatile 的应用
打开 NioEventLoop 的代码中,有一个控制 IO操作 和 其他任务运行比例的,用 volatile 修饰的 int类型字段 ioRatio代码如下。
```java
private volatile int ioRatio = 50;
```
这里为什么要用 volatile 修饰呢?我们首先对 volatile 关键字进行说明,然后再结合 Netty 的代码进行分析。
关键字 volatile 是 Java 提供的最轻量级的同步机制Java 内存模型对 volatile 专门定义了一些特殊的访问规则。下面我们就看它的规则。当一个变量被 volatile 修饰后,它将具备以下两种特性。
- 线程可见性:当一个线程修改了被 volatile 修饰的变量后,无论是否加锁,其他线程都可以立即看到最新的修改(什么叫立即看到最新的修改?感觉这句话太口语化且模糊,搞不太懂!),而普通变量却做不到这点。
- 禁止指令重排序优化:普通的变量仅仅保证在该方法的执行过程中所有依赖赋值结果的地方都能获取正确的结果,而不能保证变量赋值操作的顺序与程序代码的执行顺序一致。举个简单的例子说明下指令重排序优化问题,代码如下。
```java
public class ThreadStopExample {
private static boolean stop;
public static void main(String[] args) throws InterruptedException {
Thread workThread = new Thread(new Runnable() {
public void run() {
int i= 0;
while (!stop) {
i++;
try{
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
workThread.start();
TimeUnit.SECONDS.sleep(3);
stop = true;
}
}
```
我们预期程序会在 3s 后停止,但是实际上它会一直执行下去,原因就是虚拟机对代码进行了指令重排序和优化,优化后的指令如下。
```java
if (!stop)
While(true)
......
```
workThread线程 在执行重排序后的代码时,是无法发现 变量stop 被其它线程修改的,因此无法停止运行。要解决这个问题,只要将 stop 前增加 volatile 修饰符即可。volatile 解决了如下两个问题。第一,主线程对 stop 的修改在 workThread线程 中可见,也就是说 workThread线程 立即看到了其他线程对于 stop变量 的修改。第二,禁止指令重排序,防止因为重排序导致的并发访问逻辑混乱。
一些人认为使用 volatile 可以代替传统锁提升并发性能这个认识是错误的。volatile 仅仅解决了可见性的问题,但是它并不能保证互斥性,也就是说多个线程并发修改某个变量时,依旧会产生多线程问题。因此,不能靠 volatile 来完全替代传统的锁。根据经验总结volatile 最适用的场景是 “ 一个线程写,其他线程读 ”,如果有多个线程并发写操作,仍然需要使用锁或者线程安全的容器或者原子变量来代替。下面我们继续对 Netty 的源码做分析。上面讲到了 ioRatio 被定义成 volatile下面看看代码为什么要这样定义。
```java
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
```
通过代码分析我们发现,在 NioEventLoop线程 中ioRatio 并没有被修改,它是只读操作。既然没有修改,为什么要定义成 volatile 呢?继续看代码,我们发现 NioEventLoop 提供了重新设置 IO 执行时间比例的公共方法。
```java
public void setIoRatio(int ioRatio) {
if (ioRatio <= 0 || ioRatio > 100) {
throw new IllegalArgumentException("ioRatio: " + ioRatio + " (expected: 0 < ioRatio <= 100)");
}
this.ioRatio = ioRatio;
}
```
首先NioEventLoop线程 没有调用该 set方法说明调整 IO执行时间比例 是外部发起的操作,通常是由业务的线程调用该方法,重新设置该参数。这样就形成了一个线程写、一个线程读。根据前面针对 volatile 的应用总结,此时可以使用 volatile 来代替传统的 synchronized关键字以提升并发访问的性能。
## ThreadLocal 的应用及源码解析
ThreadLocal 又称为线程本地存储区Thread Local Storage简称为TLS每个线程都有自己的私有的本地存储区域不同线程之间彼此不能访问对方的 TLS区域。使用 ThreadLocal变量 的 set(T value)方法 可以将数据存入 该线程本地存储区,使用 get() 方法 可以获取到之前存入的值。
### ThreadLocal的常见应用
不使用 ThreadLocal。
```java
public class SessionBean {
public static class Session {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
public Session createSession() {
return new Session();
}
public void setId(Session session, String id) {
session.setId(id);
}
public String getId(Session session) {
return session.getId();
}
public static void main(String[] args) {
//没有使用ThreadLocal在方法间共享session需要进行session在方法间的传递
new Thread(() -> {
SessionBean bean = new SessionBean();
Session session = bean.createSession();
bean.setId(session, "susan");
System.out.println(bean.getId(session));
}).start();
}
}
```
上述代码中session需要在方法间传递才可以修改和读取保证线程中各方法操作的是一个。下面看一下使用 ThreadLocal 的代码。
```java
public class SessionBean {
//定义一个静态ThreadLocal变量session就能够保证各个线程有自己的一份并且方法可以方便获取不用传递
private static ThreadLocal<Session> session = new ThreadLocal<>();
public static class Session {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
public void createSession() {
session.set(new Session());
}
public void setId(String id) {
session.get().setId(id);
}
public String getId() {
return session.get().getId();
}
public static void main(String[] args) {
new Thread(() -> {
SessionBean bean = new SessionBean();
bean.createSession();
bean.setId("susan");
System.out.println(bean.getId());
}).start();
}
}
```
在方法的内部实现中,直接可以通过 session.get() 获取到当前线程的 session省掉了参数在方法间传递的环节。
### ThreadLocal的实现原理
一般,类属性中的数据是多个线程共享的,但 ThreadLocal类型的数据 声明为类属性却可以为每一个使用它通过set(T value)方法)的线程存储 线程私有的数据,通过其源码我们可以发现其中的原理。
```java
public class ThreadLocal<T> {
/**
* 下面的 getMap()方法 传入当前线程获得一个ThreadLocalMap对象说明每一个线程维护了
* 自己的一个 map保证读取出来的value是自己线程的。
*
* ThreadLocalMap 是ThreadLocal静态内部类存储value的键值就是ThreadLocal本身。
*
* 因此可以断定每个线程维护一个ThreadLocalMap的键值对映射Map。不同线程的Map的 key值 是一样的,
* 都是ThreadLocal但 value 是不同的。
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
}
```
### ThreadLocal 在 Spring 中的使用
Spring事务处理的设计与实现中大量使用了 ThreadLocal类比如TransactionSynchronizationManager 维护了一系列的 ThreadLocal变量用于存储线程私有的 事务属性及资源。源码如下。
```java
/**
* 管理每个线程的资源和事务同步的中心帮助程序。供资源管理代码使用,但不供典型应用程序代码使用。
*
* 资源管理代码应该检查线程绑定的资源JDBC连接 或 Hibernate Sessions。
* 此类代码通常不应该将资源绑定到线程,因为这是事务管理器的职责。另一个选项是,
* 如果事务同步处于活动状态,则在首次使用时延迟绑定,以执行跨任意数量资源的事务。
*/
public abstract class TransactionSynchronizationManager {
/**
* 一般是一个线程持有一个 独立的事务,以相互隔离地处理各自的事务。
* 所以这里使用了很多 ThreadLocal对象为每个线程绑定 对应的事务属性及资源,
* 以便后续使用时能直接获取。
*/
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<String>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<Boolean>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<Integer>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<Boolean>("Actual transaction active");
/**
* 为当前线程 绑定 对应的resource资源
*/
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map<Object, Object> map = resources.get();
// 如果当前线程的 resources中绑定的数据map为空则为 resources 绑定 map
if (map == null) {
map = new HashMap<Object, Object>();
resources.set(map);
}
Object oldValue = map.put(actualKey, value);
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
if (logger.isTraceEnabled()) {
logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
Thread.currentThread().getName() + "]");
}
}
/**
* 返回当前线程绑定的所有资源
*/
public static Map<Object, Object> getResourceMap() {
Map<Object, Object> map = resources.get();
return (map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap());
}
}
```
### ThreadLocal 在 Mybatis中的使用
Mybatis 的 SqlSession对象 也是各线程私有的资源,所以对其的管理也使用到了 ThreadLocal类。源码如下。
```java
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();
public void startManagedSession() {
this.localSqlSession.set(openSession());
}
public void startManagedSession(boolean autoCommit) {
this.localSqlSession.set(openSession(autoCommit));
}
public void startManagedSession(Connection connection) {
this.localSqlSession.set(openSession(connection));
}
public void startManagedSession(TransactionIsolationLevel level) {
this.localSqlSession.set(openSession(level));
}
public void startManagedSession(ExecutorType execType) {
this.localSqlSession.set(openSession(execType));
}
public void startManagedSession(ExecutorType execType, boolean autoCommit) {
this.localSqlSession.set(openSession(execType, autoCommit));
}
public void startManagedSession(ExecutorType execType, TransactionIsolationLevel level) {
this.localSqlSession.set(openSession(execType, level));
}
public void startManagedSession(ExecutorType execType, Connection connection) {
this.localSqlSession.set(openSession(execType, connection));
}
public boolean isManagedSessionStarted() {
return this.localSqlSession.get() != null;
}
@Override
public Connection getConnection() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot get connection. No managed session is started.");
}
return sqlSession.getConnection();
}
@Override
public void clearCache() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot clear the cache. No managed session is started.");
}
sqlSession.clearCache();
}
@Override
public void commit() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot commit. No managed session is started.");
}
sqlSession.commit();
}
@Override
public void commit(boolean force) {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot commit. No managed session is started.");
}
sqlSession.commit(force);
}
@Override
public void rollback() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot rollback. No managed session is started.");
}
sqlSession.rollback();
}
@Override
public void rollback(boolean force) {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot rollback. No managed session is started.");
}
sqlSession.rollback(force);
}
@Override
public List<BatchResult> flushStatements() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot rollback. No managed session is started.");
}
return sqlSession.flushStatements();
}
@Override
public void close() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot close. No managed session is started.");
}
try {
sqlSession.close();
} finally {
localSqlSession.set(null);
}
}
}
```
## J.U.C包的实际应用
### 线程池 ThreadPoolExecutor
首先通过 ThreadPoolExecutor 的源码 看一下线程池的主要参数及方法。
```java
public class ThreadPoolExecutor extends AbstractExecutorService {
/**
* 核心线程数
* 当向线程池提交一个任务时若线程池已创建的线程数小于corePoolSize即便此时存在空闲线程
* 也会通过创建一个新线程来执行该任务直到已创建的线程数大于或等于corePoolSize
*/
private volatile int corePoolSize;
/**
* 最大线程数
* 当队列满了且已创建的线程数小于maximumPoolSize则线程池会创建新的线程来执行任务。
* 另外,对于无界队列,可忽略该参数
*/
private volatile int maximumPoolSize;
/**
* 线程存活保持时间
* 当线程池中线程数 超出核心线程数,且线程的空闲时间也超过 keepAliveTime时
* 那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数
*/
private volatile long keepAliveTime;
/**
* 任务队列
* 用于传输和保存等待执行任务的阻塞队列
*/
private final BlockingQueue<Runnable> workQueue;
/**
* 线程工厂
* 用于创建新线程。threadFactory 创建的线程也是采用 new Thread() 方式threadFactory
* 创建的线程名都具有统一的风格pool-m-thread-nm为线程池的编号n为线程池中线程的编号
*/
private volatile ThreadFactory threadFactory;
/**
* 线程饱和策略
* 当线程池和队列都满了,再加入的线程会执行此策略
*/
private volatile RejectedExecutionHandler handler;
/**
* 构造方法提供了多种重载,但实际上都使用了最后一个重载 完成了实例化
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
/**
* 执行一个任务,但没有返回值
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
/**
* 提交一个线程任务,有返回值。该方法继承自其父类 AbstractExecutorService有多种重载这是最常用的一个。
* 通过future.get()获取返回值(阻塞直到任务执行完)
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
/**
* 关闭线程池,不再接收新的任务,但会把已有的任务执行完
*/
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
/**
* 立即关闭线程池,已有的任务也会被抛弃
*/
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
public boolean isShutdown() {
return ! isRunning(ctl.get());
}
}
```
线程池执行流程,如下图所示。
![avatar](images/ConcurrentProgramming/线程池流程.png)
#### Executors 提供的4种线程池
Executors类 通过 ThreadPoolExecutor 封装了 4 种常用的线程池CachedThreadPoolFixedThreadPoolScheduledThreadPool 和 SingleThreadExecutor。其功能如下。
1. CachedThreadPool用来创建一个几乎可以无限扩大的线程池最大线程数为 Integer.MAX_VALUE适用于执行大量短生命周期的异步任务。
2. FixedThreadPool创建一个固定大小的线程池保证线程数可控不会造成线程过多导致系统负载更为严重。
3. SingleThreadExecutor创建一个单线程的线程池可以保证任务按调用顺序执行。
4. ScheduledThreadPool适用于执行 延时 或者 周期性 任务。
#### 如何配置线程池
- **CPU密集型任务**
尽量使用较小的线程池,一般为 CPU核心数+1。 因为 CPU密集型任务 使得 CPU使用率 很高,若开过多的线程数,会造成 CPU 过度切换。
- **IO密集型任务**
可以使用稍大的线程池,一般为 2*CPU核心数。 IO密集型任务 CPU使用率 并不高,因此可以让 CPU 在等待 IO 的时候有其他线程去处理别的任务,充分利用 CPU时间。
#### 线程池的实际应用
Tomcat 在分发 web请求 时使用了线程池来处理。
### BlockingQueue
#### 核心方法
```java
public interface BlockingQueue<E> extends Queue<E> {
// 将给定元素设置到队列中如果设置成功返回true, 否则返回false。如果是往限定了长度的队列中设置值推荐使用offer()方法。
boolean add(E e);
// 将给定的元素设置到队列中如果设置成功返回true, 否则返回false. e的值不能为空否则抛出空指针异常。
boolean offer(E e);
// 将元素设置到队列中,如果队列中没有多余的空间,该方法会一直阻塞,直到队列中有多余的空间。
void put(E e) throws InterruptedException;
// 将给定元素在给定的时间内设置到队列中如果设置成功返回true, 否则返回false.
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
// 从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有值,并且该方法取得了该值。
E take() throws InterruptedException;
// 在给定的时间里,从队列中获取值,时间到了直接调用普通的 poll()方法为null则直接返回null。
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
// 获取队列中剩余的空间。
int remainingCapacity();
// 从队列中移除指定的值。
boolean remove(Object o);
// 判断队列中是否拥有该值。
public boolean contains(Object o);
// 将队列中值,全部移除,并发设置到给定的集合中。
int drainTo(Collection<? super E> c);
// 指定最多数量限制将队列中值,全部移除,并发设置到给定的集合中。
int drainTo(Collection<? super E> c, int maxElements);
}
```
#### 主要实现类
- **ArrayBlockingQueue**
基于数组的阻塞队列实现,在 ArrayBlockingQueue 内部维护了一个定长数组以便缓存队列中的数据对象这是一个常用的阻塞队列除了一个定长数组外ArrayBlockingQueue 内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
ArrayBlockingQueue 在生产者放入数据 和 消费者获取数据时,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于 LinkedBlockingQueue。ArrayBlockingQueue 和 LinkedBlockingQueue 间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的 Node对象。这在长时间内需要高效并发地处理大批量数据的系统中其对于 GC 的影响还是存在一定的区别。而在创建 ArrayBlockingQueue 时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。
- **LinkedBlockingQueue**
基于链表的阻塞队列,同 ArrayListBlockingQueue 类似其内部也维持着一个数据缓冲队列该队列由一个链表构成当生产者往队列中放入一个数据时队列会从生产者手中获取数据并缓存在队列内部而生产者立即返回只有当队列缓冲区达到最大值缓存容量时LinkedBlockingQueue可以通过构造函数指定该值才会阻塞生产者队列直到消费者从队列中消费掉一份数据生产者线程会被唤醒反之对于消费者这端的处理也基于同样的原理。而 LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
需要注意的是,如果构造一个 LinkedBlockingQueue对象而没有指定其容量大小LinkedBlockingQueue 会默认一个类似无限大小的容量Integer.MAX_VALUE这样的话如果生产者的速度一旦大于消费者的速度也许还没有等到队列满阻塞产生系统内存就有可能已被消耗殆尽了。
- **PriorityBlockingQueue**
基于优先级的阻塞队列优先级的判断通过构造函数传入的Compator对象来决定但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者而只会在没有可消费的数据时阻塞数据的消费者。因此使用的时候要特别注意生产者生产数据的速度绝对不能快于消费者消费数据的速度否则时间一长会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时内部控制线程同步的锁采用的是公平锁。
### CAS指令和原子类应用比较多的就是计数器
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能的额外损耗,因此这种同步被称为**阻塞同步**,它属于一种**悲观的并发策略,我们称之为悲观锁**。随着硬件和操作系统指令集的发展和优化,产生了**非阻塞同步**,被称为**乐观锁**。简单地说,就是**先进行操作,操作完成之后再判断操作是否成功,是否有并发问题,如果有则进行失败补偿,如果没有就算操作成功**,这样就从根本上避免了同步锁的弊端。
目前在Java中应用最广泛的非阻塞同步就是 CAS。从 JDK1.5 以后,可以使用 CAS 操作,该操作由 sun.misc.Unsafe 类里的 compareAndSwapInt() 和 compareAndSwapLong() 等方法实现。通常情况下 sun.misc.Unsafe类 对于开发者是不可见的因此JDK 提供了很多 CAS包装类 简化开发者的使用,如 AtomicInteger。使用 Java 自带的 Atomic原子类可以避免同步锁带来的并发访问性能降低的问题减少犯错的机会。

@ -8,13 +8,15 @@ Java中将输入输出抽象称为流就好像水管将两个容器连接
每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。
#### 2、IO模型
五种IO模型包括阻塞IO、非阻塞IO、信号驱动IO、IO多路转接、异步IO。其中前四个被称为同步IO。在网络环境下可以将IO分为两步
1.等;
五种IO模型包括阻塞IO、非阻塞IO、信号驱动IO、IO多路复用、异步IO。其中前四个被称为同步IO。在网络环境下可以将IO分为两步
1.等待数据到来
2.数据搬迁。
所以如果要想提高IO效率需要降低等待的时间。
在互联网应用中IO线程大多被阻塞在等待数据的过程中所以如果要想提高IO效率需要降低等待的时间。
##### 2.1 阻塞IOBlocking I/O
在内核将数据准备好之前系统调用会一直等待所有的套接字Socket默认的是阻塞方式。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191121192630209.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70)
![avatar](/images/Netty/阻塞IO模型.png)
Java中的socket.read()会调用native read()而Java中的native方法会调用操作系统底层的dll而dll是C/C++编写的图中的recvfrom其实是C语言socket编程中的一个方法。所以其实我们在Java中调用socket.read()最后也会调用到图中的recvfrom方法。
应用程序(也就是我们的代码)想要读取数据就会调用recvfrom而recvfrom会通知OS来执行OS就会判断数据报是否准备好(比如判断是否收到了一个完整的UDP报文如果收到UDP报文不完整那么就继续等待)。当数据包准备好了之后OS就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后socket.read()就会解除阻塞并得到read的结果。
@ -25,7 +27,9 @@ BIO中的阻塞就是阻塞在2个地方
在这2个时候我们的BIO程序就是占着茅坑不拉屎啥事情都不干。
##### 2.2 非阻塞IONoblocking I/O
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191121193031873.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70)
![avatar](/images/Netty/非阻塞IO模型.png)
每次应用进程询问内核是否有数据报准备好当有数据报准备好时就进行拷贝数据报的操作从内核拷贝到用户空间和拷贝完成返回的这段时间应用进程是阻塞的。但在没有数据报准备好时并不会阻塞程序内核直接返回未准备就绪的信号等待应用进程的下一个轮寻。但是轮寻对于CPU来说是较大的浪费一般只有在特定的场景下才使用。
Java的NIO就是采用这种方式当我们new了一个socket后我们可以设置它是非阻塞的。比如
@ -43,20 +47,26 @@ serverSocketChannel.configureBlocking(false);
**BIO 不会在recvfrom询问数据是否准备好时阻塞但还是会在将数据从内核空间拷贝到用户空间时阻塞。一定要注意这个地方Non-Blocking还是会阻塞的。**
##### 2.3 IO多路复用I/O Multiplexing
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191121194503981.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70)
![avatar](/images/Netty/IO复用模型.png)
传统情况下client与server通信需要一个3个socket(客户端的socket服务端的serversocket服务端中用来和客户端通信的socket)而在IO多路复用中客户端与服务端通信需要的不是socket而是3个channel通过channel可以完成与socket同样的操作channel的底层还是使用的socket进行通信但是多个channel只对应一个socket(可能不只是一个但是socket的数量一定少于channel数量)这样仅仅通过少量的socket就可以完成更多的连接提高了client容量。
其中,不同的操作系统,对此有不同的实现:
Windowsselector
Linuxepoll
Mackqueue
其中epollkqueue比selector更为高效这是因为他们监听方式的不同。selector的监听是通过轮询FD_SETSIZE来问每一个socket“你改变了吗假若监听到时间那么selector就会调用相应的时间处理器进行处理。但是epoll与kqueue不同他们把socket与事件绑定在一起当监听到socket变化时立即可以调用相应的处理。
其中epollkqueue比selector更为高效这是因为他们监听方式的不同。selector的监听是通过轮询FD_SETSIZE来问每一个socket“你改变了吗假若监听到事件那么selector就会调用相应的事件处理器进行处理。但是epoll与kqueue不同他们把socket与事件绑定在一起当监听到socket变化时立即可以调用相应的处理。
**selectorepollkqueue都属于Reactor IO设计。**
##### 2.4 信号驱动Signal driven IO
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191121195059827.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70)
![avatar](/images/Netty/信号驱动IO模型.png)
信号驱动IO模型应用进程告诉内核当数据报准备好的时候给我发送一个信号对SIGIO信号进行捕捉并且调用我的信号处理函数来获取数据报。
##### 2.5 异步IOAsynchronous I/O
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191121195315354.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70)
![avatar](/images/Netty/异步IO模型.png)
Asynchronous IO调用中是真正的无阻塞其他IO model中多少会有点阻塞。程序发起read操作之后立刻就可以开始去做其它的事。而在内核角度当它受到一个asynchronous read之后首先它会立刻返回所以不会对用户进程产生任何block。然后kernel会等待数据准备完成然后将数据拷贝到用户内存当这一切都完成之后kernel会给用户进程发送一个signal告诉它read操作完成了。
可以看出阻塞程度阻塞IO>非阻塞IO>多路转接IO>信号驱动IO>异步IO效率是由低到高的。
@ -75,10 +85,14 @@ acceptable
readable
writable
我们为每一种事件都编写一个处理器然后设置每个socket要监听哪种情况随后就可以调用对应的处理器。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191121200143647.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70)
图中的input就可以当作socket中间的Service Hanlder&event dispatch的作用就是监听每一个socket(需要实现把socket注册进来并指定要监听哪种情况)然后给socket派发不同的事件。
##### 3.2 Proactor
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019112120035031.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70)
Proactor与Reactor较为类似以读取数据为例
**Reactor模式**
@ -210,9 +224,13 @@ Selector选择器用于监听多个通道的事件比如连接打开
但是现代的操作系统和CPU在多任务方面表现的越来越好所以多线程的开销随着时间的推移变得越来越小了。实际上如果一个CPU有多个内核不使用多任务可能是在浪费CPU能力。
传统的IO处理方式一个线程处理一个网络连接
![在这里插入图片描述](https://img-blog.csdnimg.cn/2019112120352588.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70)
NIO处理方式一个线程可以管理过个网络连接
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191121203602279.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70)
#### 2、NIO服务器端如何实现非阻塞
服务器上所有Channel需要向Selector注册而Selector则负责监视这些Socket的IO状态(观察者)当其中任意一个或者多个Channel具有可用的IO操作时该Selector的select()方法将会返回大于0的整数该整数值就表示该Selector上有多少个Channel具有可用的IO操作并提供了selectedKeys方法来返回这些Channel对应的SelectionKey集合(一个SelectionKey对应一个就绪的通道)。正是通过Selector使得服务器端只需要不断地调用Selector实例的select()方法即可知道当前所有Channel是否有需要处理的IO操作。注java NIO就是多路复用IOjdk7之后底层是epoll模型。
#### 3、Java NIO的简单实现

@ -1390,7 +1390,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
## lazy-init 属性触发的依赖注入
最后看一下 lazy-init 触发的预实例化和依赖注入,发生在 IoC 容器完成对 BeanDefinition 的定位、载入、解析和注册之后。通过牺牲 IoC 容器初始化的性能,来有效提升应用第一次获取该 bean 的效率。
lazy-init 实现的入口方法在我们前面解读过的 AbstractApplicationContext 的 refresh() 中,它是 IoC 容器正式启动的标志。
lazy-init 实现的入口方法在我们前面解读过的 AbstractApplicationContext 的 refresh() 中,它是 IoC 容器正式启动的标志。
```java
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext, DisposableBean {
@ -1443,7 +1443,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/**
*
* 对配置了 lazy-init属性为true的bean 进行预实例化
* 对配置了 lazy-init属性 为 false 的 bean 进行预实例化
*
*/
finishBeanFactoryInitialization(beanFactory);
@ -1465,7 +1465,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
}
/**
* 对配置了 lazy-init属性为true的bean 进行预实例化
* 对配置了 lazy-init属性 为 false 的 bean 进行预实例化
*/
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// 这是 Spring3 以后新加的代码,为容器指定一个转换服务 (ConversionService)
@ -1492,7 +1492,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// 缓存容器中所有注册的 BeanDefinition 元数据,以防被修改
beanFactory.freezeConfiguration();
// 对配置了 lazy-init属性 的 单例bean 进行预实例化处理
// 对配置了 lazy-init属性 为 false 的 单例bean 进行预实例化处理
beanFactory.preInstantiateSingletons();
}
}

File diff suppressed because it is too large Load Diff

@ -15,4 +15,4 @@ Spring事务处理模块 的类层次结构如下图所示。
## 2 Spring事务处理 的应用场景
Spring 作为应用平台或框架的设计出发点是支持 POJO的开发这点在实现事务处理的时候也不例外。在 Spring 中,它既支持编程式事务管理方式,又支持声明式事务处理方式,在使用 Spring 处理事务的时候,声明式事务处理通常比编程式事务管理更方便些。
Spring 对应用的支持,一方面,通过声明式事务处理,将事务处理的过程和业务代码分离出来。这种声明方式实际上是通过 AOP 的方式来完成的。显然Spring 已经把那些通用的事务处理过程抽象出来,并通过 AOP 的方式进行封装,然后用声明式的使用方式交付给客户使用。这样,应用程序可以更简单地管理事务,并且只需要关注事务的处理策略。另一方面,应用在选择数据源时可能会采取不同的方案,当以 Spring 作为平台时Spring 在应用和具体的数据源之间搭建一个中间平台通过这个中间平台解耦应用和具体数据源之间的绑定并且Spring 为常用的数据源的事务处理支持提供了一系列的 TransactionManager。这些 Spring 封装好的 TransactionManager 为应用提供了很大的方便,因为在这些具体事务处理过程中,已经根据底层的实现,封装好了事务处理的设置以及与特定数据源相关的特定事务处理过程,这样应用在使用不同的数据源时,可以做到事务处理的即开即用。这样的另一个好处是,如果应用有其他的数据源事务处理需要, Spring 也提供了一种一致的方式。这种 有机的事务过程抽象 和 具体的事务处理 相结合的设计,是我们在日常的开发中非常需要模仿学习的。事务处理将事务处理的过程和业务代码分离出来。这种声明方式实际上是通过AOP的方式来完成的。显然Spring已经把那些通用的事务处理过程抽象出来并通过AOP的方式进行封装然后用声明式的使用方式交付给客户使用。这样应用程序可以更简单地管理事务并且只需要关注事务的处理策略。另一方面应用在选择数据源时可能会采取不同的方案当以Spring作为平台时Spring在应用和具体的数据源之间搭建一个中间平台通过这个中间平台解耦应用和具体数据源之间的绑定并且Spring为常用的数据源的事务处理支持提供了一系列的TransactionManager。这些Spring封装好的TransactionManager为应用提供了很大的方便因为在这些具体事务处理过程中已经根据底层的实现封装好了事务处理的设置以及与特定数据源相关的特定事务处理过程这样应用在使用不同的数据源时可以做到事务处理的即开即用。这样的另一个好处是如果应用有其他的数据源事务处理需要Spring也提供了一种一致的方式。这种有机的事务过程抽象和具体的事务处理相结合的设计是我们在日常的开发中非常需要模仿学习的。
Spring 对应用的支持,一方面,通过声明式事务处理,将事务处理的过程和业务代码分离出来。这种声明方式实际上是通过 AOP 的方式来完成的。显然Spring 已经把那些通用的事务处理过程抽象出来,并通过 AOP 的方式进行封装,然后用声明式的使用方式交付给客户使用。这样,应用程序可以更简单地管理事务,并且只需要关注事务的处理策略。另一方面,应用在选择数据源时可能会采取不同的方案,当以 Spring 作为平台时Spring 在应用和具体的数据源之间搭建一个中间平台通过这个中间平台解耦应用和具体数据源之间的绑定并且Spring 为常用的数据源的事务处理支持提供了一系列的 TransactionManager。这些 Spring 封装好的 TransactionManager 为应用提供了很大的方便,因为在这些具体事务处理过程中,已经根据底层的实现,封装好了事务处理的设置以及与特定数据源相关的特定事务处理过程,这样应用在使用不同的数据源时,可以做到事务处理的即开即用。这样的另一个好处是,如果应用有其他的数据源事务处理需要, Spring 也提供了一种一致的方式。这种 有机的事务过程抽象 和 具体的事务处理 相结合的设计,是我们在日常的开发中非常需要模仿学习的。

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Loading…
Cancel
Save