简体   繁体   中英

Java method synchronization and database read/write

I have following method that reads and writes to a database. It checks if the user count is less than MAX_USERS and then adds a new user.

addUser(User user){
    //count number of users in the DB, SELECT count(*) from user_table
    count = ...

    if(count<MAX_USERS){
        //add the new user to the DB, INSERT INTO users_table
    }
}     

The issue here is that if above code is called by multiple threads then they may get the same count value. This will result in more than MAX_USERS entries in the users_table.

One solution would be to synchronize the whole method but it will impact performance. It there a better way to handle this?

All of the other answers (except the synchronized method) are subject to a race condition:

If two such INSERT statements are running concurrently, the subqueries checking the number of rows will both find the count not exceeding the maximum, and both INSERT statements will insert their row, potentially pushing the user count beyond the maximum.

A solution using an AFTER INSERT ON user_table trigger would have the same problem, since the effects of a transaction are not visible to concurrent transactions before the transaction is committed.

The only bullet-proof solution is to use SERIALIZABLE transactions in the database:

import java.sql.Connection;
import java.sql.SQLException;

Connection conn;

...

conn.setAutoCommit(false);

boolean transaction_done = false;

while (! transaction_done) {
    try {
        conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

        // run the SELECT statement that counts the rows

        if (count < MAX_USERS) {
            //add the new user to the DB, INSERT INTO users_table
        }

        conn.commit();

        transaction_done = true;
    } catch (SQLException e) {
        // only retry the transaction if it is a serialization error
        if (! "40001".equals(e.getSQLState()))
            throw e;
    }
}

This will only work if all transactions that insert users into the table use this piece of code (or at least use serializable transactions).

If you want to avoid synchronization and transaction management magic in your Java code (which IMO you should) you have to handle it in database. Assuming you're using PostgreSQL, the best way to do it is Trigger Functions. See this for a similar scenario:

How to write a constraint concerning a max number of rows in postgresql?

I think you can use insert SQL like this:

INSERT INTO test1(name) 
SELECT 'tony'
 WHERE
   EXISTS (
    (select count(*) from test1) <= MAX_USERS
   );  

when the insert return 0 mean then count more than MAX_USERS, then you can use a local variable save the result, Not needed select database next time only check the local variable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM