`

Spring的事务管理难点剖析(4):多线程的困惑

阅读更多
Spring通过单实例化Bean简化多线程问题


   由于Spring的事务管理器是通过线程相关的ThreadLocal来保存数据访问基础设施(也即Connection实例),再结合IoC和AOP实现高级声明式事务的功能,所以Spring的事务天然地和线程有着千丝万缕的联系。
   我们知道Web容器本身就是多线程的,Web容器为一个HTTP请求创建一个独立的线程(实际上大多数Web容器采用共享线程池),所以由此请求所牵涉到的Spring容器中的Bean也是运行于多线程的环境下。在绝大多数情况下,Spring的Bean都是单实例的(singleton),单实例Bean的最大好处是线程无关性,不存在多线程并发访问的问题,也就是线程安全的。
   一个类能够以单实例的方式运行的前提是“无状态”:即一个类不能拥有状态化的成员变量。我们知道,在传统的编程中,DAO必须持有一个Connection,而Connection即是状态化的对象。所以传统的DAO不能做成单实例的,每次要用时都必须创建一个新的实例。传统的Service由于内部包含了若干个有状态的DAO成员变量,所以其本身也是有状态的。
   但是在Spring中,DAO和Service都以单实例的方式存在。Spring是通过ThreadLocal将有状态的变量(如Connection等)本地线程化,达到另一个层面上的“线程无关”,从而实现线程安全。Spring不遗余力地将有状态的对象无状态化,就是要达到单实例化Bean的目的。
   由于Spring已经通过ThreadLocal的设施将Bean无状态化,所以Spring中单实例Bean对线程安全问题拥有了一种天生的免疫能力。不但单实例的Service可以成功运行于多线程环境中,Service本身还可以自由地启动独立线程以执行其他的Service。

启动独立线程调用事务方法

package com.baobaotao.multithread;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
import org.apache.commons.dbcp.BasicDataSource;

@Service("userService")
public class UserService extends BaseService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private ScoreService scoreService;

    public void logon(String userName) {
        System.out.println("before userService.updateLastLogonTime method...");
        updateLastLogonTime(userName);
        System.out.println("after userService.updateLastLogonTime method...");

        //scoreService.addScore(userName, 20);//①在同一线程中调用scoreService#addScore()
        
        //②在一个新线程中执行scoreService#addScore()
        Thread myThread = new MyThread(this.scoreService, userName, 20);//使用一个新线程运行
        myThread.start();
    }

    public void updateLastLogonTime(String userName) {
        String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";
        jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
    }
    
    //③负责执行scoreService#addScore()的线程类
    private class MyThread extends Thread {
        private ScoreService scoreService;
        private String userName;
        private int toAdd;
        private MyThread(ScoreService scoreService, String userName, int toAdd) {
            this.scoreService = scoreService;
            this.userName = userName;
            this.toAdd = toAdd;
        }

        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("before scoreService.addScor method...");
            scoreService.addScore(userName, toAdd);
            System.out.println("after scoreService.addScor method...");
        }
    }
}

   将日志级别设置为DEBUG,执行UserService#logon()方法,观察以下输出日志:
引用
before userService.logon method...

//①创建一个事务
Creating new transaction with name [com.baobaotao.multithread.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] for JDBC transaction

SQL update affected 1 rows
after userService.updateLastLogonTime method...
Initiating transaction commit

//②提交①处开启的事务
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver]
Releasing JDBC Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] after transaction

Returning JDBC Connection to DataSource
before scoreService.addScor method...

//③创建一个事务
Creating new transaction with name [com.baobaotao.multithread.ScoreService.addScore]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] for JDBC transaction

SQL update affected 0 rows
Initiating transaction commit

//④提交③处开启的事务
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver]
Releasing JDBC Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] after transaction
Returning JDBC Connection to DataSource
after scoreService.addScor method...


   在①处,在主线程(main)执行的UserService#logon()方法的事务启动,在②处,其对应的事务提交。而在子线程(Thread-2)执行的ScoreService#addScore()方法的事务在③处启动,在④处对应的事务提交。
   所以,我们可以得出这样的结论:在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果这些相互嵌套调用的方法工作在不同的线程中,则不同线程下的事务方法工作在独立的事务中。

  注:以上内容摘自《Spring 4.x企业应用开发实战》
分享到:
评论
8 楼 guojiew 2017-02-14  
“Spring的Bean都是单实例的(singleton),单实例Bean的最大好处是线程无关性,不存在多线程并发访问的问题,也就是线程安全的。”
......真不懂你怎么得出的这个结论。。
7 楼 gongrunlian 2016-05-16  
gufachongyang02 写道
“Spring的Bean都是单实例的(singleton),单实例Bean的最大好处是线程无关性,不存在多线程并发访问的问题,也就是线程安全的。”

扯淡吧!!完全误导人


嗯咯
6 楼 gufachongyang02 2016-02-02  
stamen 写道
gufachongyang02 写道
“Spring的Bean都是单实例的(singleton),单实例Bean的最大好处是线程无关性,不存在多线程并发访问的问题,也就是线程安全的。”

扯淡吧!!完全误导人


请问问题在什么地方,请指出来。

正因为spring的bean是单实例的所以才可能有线程安全的问题,如果在实例中定义了属性就得注意线程的安全了,和servlet所遇到的线程安全是一样的意思。springmvc之所以不需要开发人员解决安全问题是因为controller采用了restful风格的方法级别的请求映射处理,而如果在service实例中定义了属性也是会出现线程安全的问题。具体去看看java的内存模型,里面说的就是jvm底层的线程原理。
5 楼 stamen 2015-09-21  
gufachongyang02 写道
“Spring的Bean都是单实例的(singleton),单实例Bean的最大好处是线程无关性,不存在多线程并发访问的问题,也就是线程安全的。”

扯淡吧!!完全误导人


请问问题在什么地方,请指出来。
4 楼 gufachongyang02 2015-09-09  
“Spring的Bean都是单实例的(singleton),单实例Bean的最大好处是线程无关性,不存在多线程并发访问的问题,也就是线程安全的。”

扯淡吧!!完全误导人
3 楼 amoszhou 2012-11-23  
有两个问题想问作者。

1.如果我的系统,有一办的DAO是用hibernate,另一半是用spring JDBC,如果我的事务只配置Hibernate事务,那么对JDBC是否也有效?

2.如果上述情况无效,需要配置多个事务管理器,也就是说spring jdbc事务和hibernate各配置一个, 那么在同一个方法中,如果既用了hibernate,又用了spring jdbc 那么配置两个事务管理器,会不会有影响(如果只配置一个Hibernate是可以正常工作嘛)

3.  在一个事务方法中,如果开始另外一个线程去调用另外一个事务方法,是否构成嵌套事务??
2 楼 taya 2012-10-18  
原来是这样的,谢谢分享。
我一直以为PROPAGATION_REQUIRED,即使是多个线程,也是同一事务。
有点想当然了。
1 楼 huang_yong 2012-04-14  
引用
在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果这些相互嵌套调用的方法工作在不同的线程中,则不同线程下的事务方法工作在独立的事务中。


意思就是说:
在单线程环境下,A方法调用B方法,两个方法都在同一个事务中。
在多线程环境下,A方法调用B方法,两个方法各自具有各自的事务(两个事务)。

一句话:多线程肯定多事务,单线程一般都是单事务,也可以配置成多事务。

请问作者理解是否正确?

PS:
也可以使用 ThreadPoolTaskExecutor 开启一个线程

相关推荐

    spring boot注解事务+多线程

    在Spring Boot应用中,事务管理和多线程是两个非常关键的特性,特别是在处理复杂的业务逻辑时。本示例将深入探讨如何使用注解来实现事务控制以及如何在Spring Boot中运用多线程。 首先,让我们关注"注解事务"。在...

    Spring事务管理Demo

    Spring事务管理的目的是确保数据的一致性和完整性,尤其是在多操作、多资源的环境中。本Demo将深入探讨Spring如何实现事务的管理。 首先,Spring提供了两种主要的事务管理方式:编程式事务管理和声明式事务管理。 ...

    Spring事务管理开发必备jar包

    4. **Spring AOP**:AOP(面向切面编程)是Spring用来实现事务管理的关键技术之一。通过切面,我们可以定义横切关注点,如事务边界,这些关注点可以被编织到应用程序的多个点上,从而实现事务的声明式管理。 5. **...

    springboot多数据源即分布式事务解决方案,添加对多线程的支持

    本教程将深入探讨如何在Spring Boot环境下实现多数据源操作及分布式事务管理,并加入对多线程的支持。 首先,我们来理解多数据源的概念。在大型系统中,往往需要连接多个数据库,如主库、从库、测试库等。Spring ...

    Synchronized锁在Spring事务管理下线程不安全

    Synchronized锁在Spring事务管理下,导致线程不安全。

    spring事务案例分析.zip

    2. **Spring编程式事务管理**:虽然不如声明式事务方便,但在某些复杂场景下,如需要自定义事务处理逻辑,编程式事务管理提供了更多的灵活性。通过TransactionTemplate或PlatformTransactionManager接口,开发者可以...

    spring事务管理

    ### Spring事务管理详解 #### 一、Spring事务管理概述 Spring框架提供了强大的事务管理功能,使得开发者能够更方便地管理应用程序中的事务。Spring事务管理主要包括两种类型:编程式事务管理和声明式事务管理。 -...

    Spring事务流程图

    Spring事务管理是Spring框架的核心特性之一,主要用于处理应用程序中的数据一致性问题。...通过理解和使用Spring事务流程图,我们可以更好地设计和优化我们的应用程序,确保在多线程环境下的数据一致性。

    全面分析_Spring_的编程式事务管理及声明式事务管理

    Spring 框架的事务管理是其核心特性之一,它为开发者提供了强大的支持,确保了在多线程和并发环境中数据的一致性和完整性。本教程将深入探讨 Spring 的编程式事务管理和声明式事务管理,帮助你理解这两种方式的差异...

    spring 事务管理的理解

    Spring 提供了两种事务管理方式:编程式事务管理和声明式事务管理。 1. 编程式事务管理:这是通过编写代码来控制事务的开始、提交和回滚。Spring 提供了PlatformTransactionManager接口,如...

    实验 spring 声明事务

    实验 "Spring 声明事务" 是 Java 高级编程中的一个重要环节,旨在让学生掌握 Spring 框架中声明式事务管理的配置和使用。在实际应用中,事务管理是确保数据一致性、完整性和可靠性的关键组件。Spring 提供了声明式...

    spring事务管理5种方法

    在IT行业中,Spring框架是Java企业级应用开发的首选,其强大的事务管理功能是它的一大亮点。本篇文章将深入探讨Spring事务管理的五种方法,旨在帮助开发者更好地理解和运用这一核心特性。 首先,我们来了解什么是...

    Spring事务管理失效原因汇总

    标题“Spring事务管理失效原因汇总”指出了本文的核心内容是分析在使用Spring框架进行事务管理时可能遇到的问题及其原因。描述部分进一步说明了事务失效的后果往往不明显,容易在测试环节被忽略,但在生产环境中出现...

    spring事务与数据库操作

    Spring支持两种类型的事务管理:编程式事务管理和声明式事务管理。其中声明式事务管理因其易于使用和维护而被广泛采用。 ##### 1.1 Spring声明式事务介绍 Spring的声明式事务管理是通过配置文件或注解的方式来实现...

    Spring事务管理4种方式

    4. **TransactionTemplate**:它是Spring提供的一个工具类,简化了编程式事务管理。它封装了事务的开始、提交和回滚,使得事务处理更加简洁。示例代码如下: ```java TransactionTemplate transactionTemplate = ...

    Spring事务管理的jar包

    在Java企业级应用开发中,Spring框架以其强大的功能和灵活性被广泛应用,特别是在事务管理方面。Spring提供了全面的事务管理解决方案,使得开发者可以方便地控制事务的边界,保证数据的一致性和完整性。本篇将深入...

    spring3.0两种事务管理配置

    Spring 3.0 提供了两种事务管理配置方法:基于 XML 的事务管理和基于 @Transactional 的事务管理,这两种方法都是为了实现事务管理的目标,分别具有不同的配置方式和优缺点。 基于 XML 的事务管理 这种方法不需要...

    全面分析 Spring 的编程式事务管理及声明式事务管理

    本文将全面分析Spring中的编程式事务管理和声明式事务管理,旨在帮助开发者深入理解这两种事务管理方式,并在实际项目中合理选择。 **编程式事务管理** 编程式事务管理是通过代码直接控制事务的开始、提交、回滚等...

    Spring事务管理.pdf

    Spring事务管理.pdf 1.资料 2.本地事务与分布式事务 3.编程式模型 4.宣告式模型

Global site tag (gtag.js) - Google Analytics