【问题标题】:Java multiple threads database access [closed]Java多线程数据库访问[关闭]
【发布时间】:2011-06-20 02:30:52
【问题描述】:

对于多线程 Java 应用程序来说,确保所有线程同步访问数据库的最佳解决方案是什么?例如,每个线程代表单独的事务,首先检查 db 的值,然后根据答案插入或更新数据库中的某些字段(注意在检查、插入和提交之间的应用程序正在执行其他处理)。但问题是另一个线程可能在同一张桌子上做同样的事情。

更具体的例子。线程 T1 启动事务,然后检查表 ENTITY_TABLE 是否有代码为“111”的条目。如果找到更新它的日期,如果没有找到插入新条目,然后提交事务。现在想象线程 T2 做同样的事情。现在有几个问题:

  1. T1 和 T2 检查 db 并没有发现任何内容,并且都插入了相同的条目。
  2. T1 检查 db,找到具有旧日期的条目,但在提交时,T2 已经将条目更新为更近的日期。
  3. 如果我们使用缓存并同步访问缓存,我​​们会遇到问题:T1 获取锁检查数据库,如果没有找到缓存,则添加到缓存,释放锁,提交。 T2 做同样的事情,在缓存中找到要提交的条目。但是 T1 事务失败并被回滚。现在 T2 状态不佳,因为它应该插入到 ENTITY_TABLE 但不知道。
  4. 更多?

我正在创建具有同步功能的简单自定义缓存并解决问题 3。但也许有一些更简单的解决方案?

【问题讨论】:

    标签: java database multithreading synchronization


    【解决方案1】:

    这应该主要在数据库中通过配置所需的transaction isolation 级别来处理。然后最重要的是,您需要选择您的锁定策略(optimistic 或悲观)。

    如果没有事务隔离,您将很难尝试仅在 Java 域中确保事务完整性。尤其是考虑到即使当前仅从您的 Java 应用程序访问数据库,这也可能在未来发生变化。

    现在关于选择哪个隔离级别,从您的描述看来,您似乎需要最高的隔离级别,serializable。然而,在实践中,这往往 由于广泛的锁定,成为真正的性能猪。因此,您可能需要重新评估您的要求,以针对您的具体情况找到隔离和性能的最佳平衡。

    【讨论】:

    • 感谢您的回答。问题是应用程序必须非常高效,它可能运行在 16 个甚至 32 个线程上。每个开始事务,进行大量处理,然后在提交步骤中获取结果并进行插入和更新 - 所有这些都在 jdbc 批处理中(为了性能)。然后事务被提交。无论如何,在我的情况下,可序列化级别会太多,尤其是对于 32 个线程。我希望我能够通过 Java 应用程序中的缓存来解决这个问题。
    【解决方案2】:

    如果您想 SQL SELECT 数据库中的一行,然后更新同一行,作为 Java 开发人员,您有 2 个选择。

    1. SELECT 使用 ROWLOCK 或任何 行锁语法适用于您的 特定的数据库。

    2. 选择行,进行处理, 就在你准备好之前 更新,使用 ROWLOCK 再次选择行以查看 如果任何其他线程进行了更改。 如果两个 SELECTS 返回相同 值,更新。如果没有,抛出一个 错误或重新处理。

    【讨论】:

    • 我一定是对 2 有误解,因为这听起来不对。在我的解释中,这种方法根本不能防止竞争条件。仅仅因为您在两次读取之间没有遇到变化并不意味着在您现在进行写入之前不会发生变化。
    • @Dreamspace 总裁:你说得部分正确。您在选项 2 中所做的是最小化在您进行写入之前发生更改的可能性。我应该在答案中提到第二个选择锁定行。
    • 哦。这就是你的意思。然后我就知道你的想法是什么工作流程了。
    【解决方案3】:

    您面临的问题是transaction isolation

    似乎您需要让每个线程锁定 where 子句中相关的行,这需要可序列化的隔离。

    【讨论】:

      【解决方案4】:

      我在处理使用 Sqllite 数据库的多线程 Java 程序时遇到了这个问题。它使用文件锁定,所以我必须确保只有一个线程同时在工作。

      我基本上最终使用了同步。当 ConnectionFactory 返回一个 db 连接时,它还返回一个锁定对象,在使用该连接时应该锁定该对象。所以你可以手动做同步锁,或者创建一个为你做的下面的类的子类:

       /**
        * Subclass this class and implement the persistInTransaction method to perform
        * an update to the database.
        */
       public abstract class DBOperationInTransaction {
      
           protected Logger logger = Logger.getLogger(DBOperationInTransaction.class.getName());
      
           public DBOperationInTransaction(ConnectionFactory connectionFactory) {
               DBConnection con = null;
      
               try {
                   con = connectionFactory.getConnection();
      
                   if(con == null) {
                       logger.log(Level.SEVERE, "Could not get db connection");
                       throw new RuntimException("Could not get db connection");
                   }
      
                   synchronized (con.activityLock) {
                       con.connection.setAutoCommit(false);
                       persistInTransaction(con.connection);
                       con.connection.commit();
                   }
      
               } catch (Exception e) {
                   logger.log(Level.SEVERE, "Failed to persist data:", e);
                   throw new RuntimeException(e);
               } finally {
                   if(con != null) {
                       //Close con.connection silently.
                   }
               }
           }
      
           /**
            * Method for persisting data within a transaction. If any SQLExceptions
            * occur they are logged and the transaction is rolled back.
            * 
            * In the scope of the method there is a logger object available that any
            * errors/warnings besides sqlException that you want to log.
            * 
            * @param con
            *            Connection ready for use, do not do any transaction handling
            *            on this object.
            * @throws SQLException
            *             Any SQL exception that your code might throw. These errors
            *             are logged. Any exception will rollback the transaction.
            * 
            */
           abstract protected void persistInTransaction(Connection con) throws SQLException;
      
       }
      

      还有 DBConnection 结构体:

       final public class DBConnection {
           public final Connection connection;
           public final String activityLock;
      
           public DBConnection(Connection connection, String activityLock) {
               this.connection = connection;
               this.activityLock = activityLock;
           }
      
       }
      

      【讨论】:

        【解决方案5】:

        顺便说一句,我认为您必须在查询之前锁定表。这将强制线程的顺序操作。然后,您的线程应该准备好等待锁定,当然,锁定获取可能会超时。这可能会给您的应用程序带来相当大的瓶颈,并且您的线程都必须排队等待数据库资源。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-02-23
          • 2011-10-25
          • 1970-01-01
          • 1970-01-01
          • 2013-05-25
          相关资源
          最近更新 更多