【问题标题】:Can spring transactions unsynchronize a synchronized method?春天事务可以取消同步同步方法吗?
【发布时间】:2023-03-16 15:00:02
【问题描述】:

我的同事和我有一个在 MyEclipse 内的 Tomcat 上使用 Spring 3.0.0 和 JPA(休眠 3.5.0-Beta2)的 Web 应用程序。其中一种数据结构是树。只是为了好玩,我们尝试用 JMeter 对“插入节点”操作进行压力测试,发现了一个并发问题。 Hibernate 报告发现两个具有相同私钥的实体,就在这样的警告之后:

 WARN  [org.hibernate.engine.loading.LoadContexts] fail-safe cleanup (collections) : ...

如果多个线程同时调用 insert() 方法,很容易看出这些问题是如何发生的。

我的 servlet A 调用一个服务层对象 B.execute(),然后它调用一个较低层对象 C.insert()。 (实际代码太大,无法贴出,所以略作删减。)

小服务程序 A:

  public void doPost(Request request, Response response) {
    ...
    b.execute(parameters);
    ...
  }

服务 B:

  @Transactional //** Delete this line to fix the problem.
  public synchronized void execute(parameters) {
    log("b.execute() starting. This="+this);
    ...
    c.insert(params);
    ...
    log("b.execute() finishing. This="+this);
  }

子服务 C:

  @Transactional
  public void insert(params) {
    ...
    // data structure manipulation operations that should not be 
    // simultaneous with any other manipulation operations called by B.
    ...
  }

我所有的状态更改调用都通过 B,所以我决定让 B.execute() synchronized。已经是@Transactional了,但实际上需要同步的是业务逻辑,而不仅仅是持久化,这样看来是有道理的。

我的 C.insert() 方法也是 @Transactional。但由于 Spring 中的默认事务传播似乎是必需的,我认为没有为 C.insert() 创建任何新事务。

所有组件 A、B 和 C 都是 spring-bean,因此是单例。如果确实只有一个 B 对象,那么我得出的结论是,一次执行 b.execute() 的威胁不应该超过一个。当负载较轻时,只使用单个线程,就是这样。但是在负载下,会涉及到额外的线程,我看到几个线程在第一个打印“完成”之前打印“开始”。这似乎违反了该方法的synchronized 性质。

我决定在日志消息中打印this 以确认是否只有一个 B 对象。所有日志消息都显示相同的对象 ID。

经过多次令人沮丧的调查,我发现删除 B.execute() 的 @Transactional 可以解决问题。随着那条线的消失,我可以有很多线程,但我总是在下一个“开始”之前看到一个“开始”,然后是一个“结束”(并且我的数据结构保持不变)。不知何故,synchronized 似乎只在@Transactional 不存在时才起作用。但我不明白为什么。任何人都可以帮忙吗?有关如何进一步研究此问题的任何提示?

在堆栈跟踪中,我可以看到在 A.doPost() 和 B.execute() 之间以及 B.execute() 和 C.insert() 之间生成了一个 aop/cglib 代理。我想知道代理的构造是否会以某种方式破坏 synchronized 行为。

【问题讨论】:

    标签: hibernate spring jpa transactions synchronized


    【解决方案1】:

    问题在于@Transactional 封装了同步方法。 Spring 使用 AOP 执行此操作。 执行过程是这样的:

    1. 开始交易
    2. 调用带有@Transactional注解的方法
    3. 当方法返回时提交事务

    步骤 1. 和 3. 可以由多个线程同时执行。因此,您可以多次开始交易。

    您唯一的解决方案是将调用同步到方法本身。

    【讨论】:

    • 我相信这是正确且最有帮助的答案。 spring aop 的微妙之处经常被忽视,这会导致各种意想不到的问题,就像这样!
    • 正确答案 - 我们运行并发单元测试来确认这一点
    【解决方案2】:

    正如您所说,同步关键字要求所涉及的对象始终相同。我自己没有观察到上述行为,但你的嫌疑人可能是正确的。

    您是否尝试过从 doPost 方法中注销 b?如果每次都不同,那么 AOP/cglib 代理就会有一些春天的魔力。

    无论如何,我不会依赖 syncronized 关键字,而是使用 java.util.concurrent.locks 中的 ReentrantLock 之类的东西来确保同步行为,因为无论可能有多个 cglib 代理,您的 b 对象总是相同的。

    【讨论】:

    • 谢谢普劳。我不知道 ReentrantLock - 我会看看。
    【解决方案3】:

    选项 1:

    Delete synchronized of ServiceB and:
    
    public void doPost(Request request, Response response) {
        ...
        synchronized(this)
        {
            b.execute(parameters);
        }
        ...
      }
    

    选项 2:

    Delete synchronized of ServiceB and:
    
    public class ProxyServiceB (extends o implements) ServiceB
    {
        private ServiceB serviceB;
        public ProxyServiceB(ServiceB serviceB)
        {
            this.serviceB =serviceB;
        }
        public synchronized void execute(parameters) 
        {
             this.serviceB.execute(parameters);
        }
    } 
    
    public void doPost(Request request, Response response) 
    {
        ...
        ProxyServiceB proxyServiceB = new ProxyServiceB(b);
        proxyServiceB .execute(parameters);
        ...
    }
    

    【讨论】:

    • 感谢 Springfan。使用您的选项 B,难道不能有多个 ProxyServiceB 实例吗?如果每个人都只是在自己同步,我看不出这有什么好处。选项 A(将同步的上移一层)可能很有效,但必须同步所有可能调用此服务的不同 servlet 似乎很可惜。
    【解决方案4】:

    再次选择选项 2:

    删除ServiceB的同步并:

    public class ProxyServiceB (extends o implements) ServiceB
    {
        private ServiceB serviceB;
        public ProxyServiceB(ServiceB serviceB)
        {
            this.serviceB =serviceB;
        }
        public synchronized void execute(parameters) 
        {
             this.serviceB.execute(parameters);
        }
    } 
    
    public class TheServlet extends HttpServlet
    {
       private static ProxyServiceB proxyServiceB = null;
    
       private static ProxyServiceB getProxyServiceBInstance()
       {
            if(proxyServiceB == null)
            {
                return proxyServiceB = new ProxyServiceB(b);
            }
            return proxyServiceB;
       }
    
       public void doPost(Request request, Response response) 
       {
        ...
         ProxyServiceB proxyServiceB = getProxyServiceBInstance();
        proxyServiceB .execute(parameters);
        ...
       }    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多