跨多线程解决方案的单一事务

时间:2017-10-27 05:29:34

标签: java multithreading concurrency transactions

据我了解,所有事务都是线程绑定的(即上下文存储在ThreadLocal中)。例如,如果:

  1. 我在事务性父方法中启动事务
  2. 在异步调用中使数据库插入#1
  3. 在另一个异步调用中使数据库插入#2
  4. 然后,即使它们共享相同的“事务性”父级,也会产生两个不同的事务(每个插入一个)。

    例如,假设我执行两次插入(并使用一个非常简单的示例,即为了简洁起见,不使用执行程序或可完成的未来等):

    @Transactional
    public void addInTransactionWithAnnotation() {
        addNewRow();
        addNewRow();
    }
    

    将根据需要执行两次插入,作为同一事务的一部分。

    但是,如果我想并行化这些插入的性能:

    @Transactional
    public void addInTransactionWithAnnotation() {
        new Thread(this::addNewRow).start();
        new Thread(this::addNewRow).start();
    }
    

    然后,每个生成的线程都不会参与事务,因为事务是线程绑定的。

    关键问题:有没有办法将事务安全地传播到子线程?

    我想到解决这个问题的唯一解决方案:

    1. 使用JTA或某些XA管理器,根据定义应该可以这样做 这个。但是,我理想情况下不希望将XA用于我的解决方案 因为它的开销
    2. 将我想要执行的所有事务性工作(在上面的示例中,addNewRow()函数)传递给单个线程,并以多线程方式执行所有先前的工作。
    3. 找出在Transaction状态上利用InheritableThreadLocal并将其传播到子线程的某种方法。我不知道该怎么做。
    4. 还有更多可行的解决方案吗?即使它的味道有点像解决方法(如我上面的解决方案)?

2 个答案:

答案 0 :(得分:2)

首先,澄清一下:如果你想加速几个相同类型的插入,正如你的例子所示,你可能会通过在同一个线程中发布插入并使用它来获得最佳性能某种类型的批量插入。根据您的DBMS,有几种技术可供选择,请查看:

至于你的实际问题,我个人会尝试将所有工作都传递给工作线程。这是最简单的选择,因为您不需要弄乱ThreadLocal或交易登记/退市。此外,一旦您将工作单元放在同一个线程中,如果您很聪明,您可以应用上面的批处理技术以获得更好的性能。

最后,对工作线程的管道工作并不意味着您必须拥有一个工作线程,如果它对您的应用程序真正有益,您可以拥有一个工作池并实现一些并行性。从生产者/消费者的角度思考。

答案 1 :(得分:2)

JTA API有几种隐式操作当前线程事务的方法,但它不会阻止您在线程之间移动或复制事务,或者对未绑定到当前(或任何其他)的事务执行某些操作)线程。这不会导致头痛,但这不是最糟糕的部分......

对于原始JDBC,您根本没有JTA事务。您有一个JDBC连接,它有自己的关于事务上下文的想法。在这种情况下,事务是连接绑定,而不是线程绑定。通过连接,tx随之而来。但是Connections不一定是线程安全的,无论如何都可能是性能瓶颈,所以在多个并发线程之间共享一个并不能真正帮助你。您可能需要多个连接,认为它们位于同一个事务中,这意味着您需要XA,因为这就是数据库识别此类情况的方式。此时你回到JTA,但现在图片中有一个JCA来正确处理Connection管理。简而言之,您已经彻底改造了JavaEE应用程序服务器。

对于在JDBC上分层的框架,例如像Hibernate这样的ORM,你有一个额外的复杂性:它们的抽象不一定是线程安全的。所以你不能同时拥有一个绑定到多个Threads的Session。但是,您可以拥有多个并发的会话,每个会话都参与同一个XA事务。

像往常一样,它归结为阿姆达尔定律。如果从每个tx使用多个Connections以允许多个并发线程共享db I / O工作所获得的加速比相对于从批处理得到的大,那么XA的开销是值得的。如果加速是本地计算并且db I / O是次要问题,则处理JDBC连接并将非IO计算工作卸载到线程池的单个线程是可行的。