(spring-第20回【AOP基础篇】)Spring与事务

要想了解Spring的事务,首先要了解数据库事务的基本知识,数据库并发会产生很多问题,Spring使用ThreadLocal技术来处理这些问题,那么我们必须了解Java的ThreadLocal技术。下面我们逐一了解。

第一回合:数据库事务的基本知识

什么是数据库事务?

一次执行多个SQL语句,全部执行成功则成功,有一个执行失败则全部失败。即“一荣俱荣,一损俱损”。

数据库的事务必须同时满足下列四个条件:

原子性(Atomic:比如数据库一次执行四个SQL语句,那么这四个SQL就是宏观的一个不可分割单元,“一荣俱荣,一损俱损”。全部执行成功则成功,有一个执行失败则全部失败。三分归元气。

一致性(Consistency:整个事务不管成功了还是失败了,整个数据库的状态和规则不能变化。即:A账户转账100元到B账户,这个事务过程结束后前后,数据库中总的账户金额是不变的。

隔离性(Isolation:不同的事务并发执行时,各自拥有不同的数据空间,你走你的阳关道,我走我的独木桥,互不干扰。

但并非完全不干扰,数据库规定了事务隔离级别,隔离级别越高,数据的一致性越好,并发性越弱。

持久性(Durability:一旦事务提交成功,事务中的所有数据操作都必须被持久化到数据库中。

这样一来,即使刚提交完,数据库就崩溃,当重启数据库之后,也可以根据已经保存(持久化)的操作来恢复数据。

一致性是结果,其他三个是手段。

  • 数据库管理一般采用重执行日志保证原子性、一致性和持久性。
  • 重执行日志记录了数据库变化的每一个动作。这样,即使数据库事务在执行了一部分操作后发生错误退出,可以根据重执行日志来撤销已经执行的操作。
  • 对于已经提交的事务,即使数据库崩溃,再重启数据库时也能够根据日志对尚未持久化的数据进行相应的重执行操作。
  • 数据库管理系统采用数据库锁机制来保证事务的隔离性(正如Java采用对象锁机制进行线程同步。)

数据并发的问题

多个客户端同时操作一个数据库,该并发过程就可能引起并发问题:

脏读(dirty read):A事务读取B事务尚未提交的数据并进行一系列操作,结果B事务执行了回滚,那么这时,A事务读到的数据就是不被认可的,是脏数据。

不可重复读(unrepeatable read):比如:A开始了查询事务,B开始了提款事务。A第一次查询余额为1000元,这时B提取100元,A第二次查询余额时,变成了900元,与第一次查询的余额不同。

幻象读(phantom read:一般发生在计算统计数据的事务中。比如;银行正在统计所有账户的存款总额,统计出来为10000元。这时正好新增了一个账户,存款1000元。再次统计发现总额为11000元,与前一次统计不同。

不可重复读是指读到了更改的数据(一般情况下需要添加行级锁,阻止操作中的数据变化),而幻象读是指读到了新增的数据(往往需要添加表级锁,将整个表锁定)。

第一类丢失更新:目前账户余额1000元,A开始事务-->B开始事务-->B汇入100元,余额改为1100元-->B提交事务àA取出100元,把余额改为900元-->A撤销事务-->余额恢复为1000元(丢失更新)。

A事务撤销时,把B提交的更新数据给覆盖了。

第二类丢失更新:B开始事务-->A开始事务-->B查询余额为1000元-->A查询余额为1000元-->B取出100元,把余额改为900元-->B提交事务-->A汇入100元-->A提交事务-->A把余额改为1100元(丢失更新)。

  A在提交事务时,把B所做的操作丢失。

JDBC对事务的支持

(spring-第20回【AOP基础篇】)Spring与事务

Connection默认情况下是自动提交的。

为了把多个事务当成一个事务执行,就必须强制阻止自动提交(第五行)。

第二回合:ThreadLocal

l  Spring通过各种模板类降低了开发者使用各种持久技术的难度。

l  这些模板类都是线程安全的。

l  模板类需要绑定数据连接或者会话的资源。

l  这些资源本身是非线程安全的。

l  虽然模板类通过资源池获取连接或者会话,

l  但是资源池解决的是数据连接或者资源的缓存问题,

l  而不是线程安全问题。

l  按照惯例,采用synchronized进行线程同步。

l  但是该线程同步机制解决具体问题时,开发难度大、降低并发性、影响系统性能。

l  所以,模板类并未采用线程同步机制。

l  那么,模板类究竟采用什么方式保证线程安全的呢?

l  答案:ThreadLocal!

ThreadLocal是什么?

ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。多线程程序使用ThreadLocal维护变量时,每一个线程将拿到该变量的一个副本,从而,每个线程对各自变量的副本的更改都不会影响到其他线程。

一个ThreadLocal实例

(spring-第20回【AOP基础篇】)Spring与事务

(spring-第20回【AOP基础篇】)Spring与事务

上例很简单,三个线程都拿到Integer对象的副本,该Integer对象的初始化值设置为0,然后各自修改,互不影响。

除了set、get、initialValue之外,ThreadLocal还有一个方法:remove(),该方法将当前变量副本从该线程中删除,减少内存的占用。

与Thread同步机制的比较

  • 在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量,该变量是多个线程共享的,那么,每个线程在什么时候可以对变量读写,什么时候要对该对象加锁,什么时候释放对象锁等,都要准确判断,逻辑复杂,编写难度大。
  • ThreadLoacl为每一个线程提供一个变量的副本,隔离了多线程访问数据的冲突。ThreadLocal提供了线程安全的对象封装,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

  总之,对多线程共享的问题,同步机制采用了”以时间换空间,访问串行化,对象共享化”。而ThreadLocal则是“以空间换时间,访问并行化,对象独享化。前者只提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

Spring与ThreadLocal

有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。

(spring-第20回【AOP基础篇】)Spring与事务

(spring-第20回【AOP基础篇】)Spring与事务

无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象.不能保存数据,是不变类,是线程安全的。

(spring-第20回【AOP基础篇】)Spring与事务

一般情况下,只有无状态bean才可以在多线程环境下共享(既然没有状态,不能保存数据,随便共享啦)

在spring中,绝大部分Bean都可以声明为singleton作用域。(如果在<bean>中指定Bean的作用范围是scopt="prototype",那么系统将bean返回给调用者,spring就不管了(如果两个实例调用的话,每一次调用都要重新初始化,一个实例的修改不会影响另一个实例的值。如果指定Bean的作用范围是scope="singleton",则把bean放到缓冲池中,并将bean的引用返回给调用者。这个时候,如果两个实例调用的话,因为它们用的是同一个引用,任何一方的修改都会影响到另一方。))

正因为Spring对一些Bean(RequestContextholder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全的”状态性对象”采用ThreadLocal封装,让它们成为线程安全的”状态性对象”,因此有状态的bean就能够以singleton方式在多线程中正常工作了。

Spring对有状态bean的改造思路

非线程安全:

(spring-第20回【AOP基础篇】)Spring与事务

由于第8行的conn是非线程安全的成员变量,

因此addTopic()方法也是非线程安全的,

每次使用时都必须新创建一个TopicDao实例(非singleton)。

对非线程安全的conn进行改造:

(spring-第20回【AOP基础篇】)Spring与事务

上例仅为了简单说明原理,并不做深究,例子粗糙,并不能在实际环境中使用,还有很多要考虑的其他问题。

第三回合:Spring对事务管理的支持

  • 不管选择Spring JDBC,Hibernate,JPA还是iBatis,Spring都让我们可以用统一的编程模型进行事务管理。
  • Spring事务管理有几个主要的抽象父类,在事务管理的运作过程中各司其职,主要的功能:

     描述事务的隔离级别、超时时间、是否只读等。

    定义事务的属性,比如事务隔离(当前事务与其他事务的隔离程度)、事务传播、事务超时、只读状态等。

    描述事务的具体运行状态。

  • 对应不同的持久化技术,Spring事务管理封装了具体的实现类。每一种实现类对应的配置方式有所不同。
  • Spring使用ThreadLocal技术给不同线程提供各自的数据连接副本。
  • Spring通过事务传播行为来处理事务嵌套调用时的运作。
  • Spring声明式事务管理是通过AOP实现的,通过声明性信息,Spring负责将事务管理增强逻辑动态织入到业务方法的连接点中。这些逻辑包括:获取线程绑定资源、开始事务、提交/回滚事务、进行异常转换和处理等。
  • 基于tx/aop命名空间配置事务:在XML中配置目标类、事务管理器、增强类、定义切面,引入增强等。
  • 使用注解配置声明式事务:@Transactional。
上一篇:java线程之多个生产者消费者


下一篇:[LeetCode] 566. Reshape the Matrix_Easy