Java多线程数据库访问

时间:2011-02-02 16:25:25

标签: java database multithreading synchronization

我想问一下多线程Java应用程序的最佳解决方案是什么,以确保所有线程同步访问db。例如,每个线程代表单独的事务,并首先检查db的值,然后根据答案必须插入或更新数据库中的某些字段(注意,插入和提交应用程序之间的注意事项正在进行其他处理)。但问题是另一个线程可能在同一个表上做同样的事情。更具体的例子。线程T1启动事务,然后检查表ENTITY_TABLE是否输入代码为'111'如果发现更新其日期,如果未找到则插入新条目,然后提交事务。现在假设线程T2完全相同。现在几乎没有问题: 1. T1和T2检查db并找不到任何内容,并且都插入相同的条目。 2. T1检查db,查找具有旧日期的条目,但在提交T2时已经更新了最近日期的条目。 3.如果我们使用缓存并同步访问缓存,则会出现问题:T1获取锁定检查db和缓存,如果未找到则添加到缓存,释放锁定,提交。 T2做同样的事情,发现缓存中的条目将提交。但T1事务失败并被支持。现在T2形状不好,因为它应该插入ENTITY_TABLE但不知道。 还有更多?

我正在创建具有同步功能和解决问题3的简单自定义缓存。但是我感兴趣的是可能还有一些更简单的解决方案?有没有人必须解决类似的问题?你是怎么做到的?

6 个答案:

答案 0 :(得分:8)

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

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

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

答案 1 :(得分:2)

如果要SQL从数据库中选择一行,然后再更新同一行,那么作为Java开发人员有两种选择。

  1. 使用ROWLOCK或其他任何选项进行选择 行锁语法适用于您的 特定数据库。

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

答案 2 :(得分:2)

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

我基本上最终使用了synchronized。当ConnectionFactory返回数据库连接时,它还会返回一个锁定对象,在使用该连接时应锁定该对象。因此,您可以手动执行同步锁定,或者在下面创建类的子类:

 /**
  * 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;
     }

 }

答案 3 :(得分:1)

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

答案 4 :(得分:1)

您遇到的问题是 transaction isolation

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

答案 5 :(得分:1)

你为什么要重新发明轮子?我建议使用OR映射器框架进行事务的数据库访问(例如,像Hibernate或Eclipselink这样的JPA规范实现者)。您还可以添加Spring DAO来处理您的交易。然后,您可以专注于业务逻辑,而不必担心这些低级别的东西。