I need to insert into database which has two columns-
ID PrimaryKey String
ACCOUNT String
So that means each thread should be using unique id always and I need to store the same ID in Account column also. So suppose if ID is 1 then in Database it should be stored as
ID Account
1 SomeString+1
2 SomeString+2
3 SomeString+3
....
..
100 SomeString+100
I am concatenating that userID with that String always in Account column.
Below is my multithreaded code which will spawn multiple threads- And each thread will be getting a new unique ID everytime as I am using AtomicInteger for that. And it will insert that ID to ID column and also append that ID to Account column
But somehow in my below program what I have seen in that database is-
ID Account
1 String+2
2 String+1
3 String+3
Which is not right. It should be something like this-
ID Account
1 String+1
2 String+2
3 String+3
Below is the code
public static void main(String[] args) {
final int noOfThreads = 4;
final int noOfTasks = 10;
final AtomicInteger id = new AtomicInteger(1);
ExecutorService service = Executors.newFixedThreadPool(noOfThreads);
for (int i = 0; i < noOfTasks * noOfThreads; i++) {
service.submit(new Task(id));
}
}
class Task implements Runnable {
private final AtomicInteger id;
private volatile int userId;
public Task(AtomicInteger id) {
this.id = id;
}
@Override
public void run() {
dbConnection = getDBConnection();
preparedStatement = dbConnection.prepareStatement(Constants.INSERT_ORACLE_SQL);
userId = id.getAndIncrement();
preparedStatement.setString(1, String.valueOf(userId));
preparedStatement.setString(2, Constants.getaAccount(userId));
preparedStatement.executeUpdate();
}
}
And below is my Constants class which I have made immutable.
public final class Constants {
public static String A_ACCOUNT;
public final static String INSERT_ORACLE_SQL = "INSERT INTO XMP_TEST"
+ "("
+ "ID, A_ACCOUNT) VALUES"
+ "(?, ?)";
public static String getaAccount(int userId) {
A_ACCOUNT = "{\"lv\":[{\"v\":{\"userId\":"+userId+"},\"cn\":1}]}";
return A_ACCOUNT;
}
}
Can anyone tell me what wrong I am doing here? I believe it’s happening because of thread safety issue. Multiple threads modifying the userID integer I guess and that’s why it is getting written wrongly to database.
How can I fix this problem?
The major problem I see is not with
Task.userId, but rather withConstants.A_ACCOUNT: if two separate threads callgetaAccountat the same time, then they’ll both setConstants.A_ACCOUNTand both read it, so they can end up both having the same value, or each having the other’s value, or whatnot. To fix this, you can use a local variable instead of a static field:or just dispense with the variable:
(You say that you’ve made
Constantsimmutable, but that’s not really true. Instances ofConstantswill be immutable, because they have no fields at all; butConstantsitself has a publically modifiable field, so it’s very mutable!)More generally, you shouldn’t be using fields for temporary values only needed within a specific method, and only during a single call to it. Even when it’s not a synchronization problem, it’s a maintenance problem. For example,
Taskdoes not need avolatile int userId;userIdshould just be a local variable inside itsrunmethod.Also, I’d recommend wrapping your
AtomicIntegerin its own class,IncrementingCounteror something, that offers just one method, called (say)getNewId. ThengetNewIdwill be the only class that has to deal with coordination between threads. All other classes can be made threadsafe by regular techniques (immutability, only existing within a single thread, etc.).