我想问一下多线程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的简单自定义缓存。但是我感兴趣的是可能还有一些更简单的解决方案?有没有人必须解决类似的问题?你是怎么做到的?
答案 0 :(得分:8)
这应该通过配置所需的transaction isolation级别主要在数据库中处理。然后,您需要选择锁定策略(optimistic或悲观)。
如果没有事务隔离,您将很难尝试仅在Java域中确保事务完整性。特别是考虑到即使数据库当前仅从您的Java应用程序访问,这可能在将来发生变化。
现在关于选择哪个隔离级别,从您的描述中可能看起来您需要最高的隔离级别 serializable 。然而,在实践中,这趋于 由于广泛的锁定,成为一个真正的性能生猪。因此,您可能需要重新评估您的要求,以便针对您的具体情况找到隔离和性能的最佳平衡。
答案 1 :(得分:2)
如果要SQL从数据库中选择一行,然后再更新同一行,那么作为Java开发人员有两种选择。
使用ROWLOCK或其他任何选项进行选择 行锁语法适用于您的 特定数据库。
选择行,进行处理, 就在你准备好之前 更新,再次选择行以查看 如果任何其他线程发生了变化。 如果两个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来处理您的交易。然后,您可以专注于业务逻辑,而不必担心这些低级别的东西。