I am having trouble preventing a race condition in a J2EE app that accesses an Oracle database. The sequence of events goes like this:
1) My servlet receives a request.
2) The servlet checks the value of a flag stored in the database.
3) If the flag is set to 'N' then it sets it to 'Y', and performs some other actions that must only be done once.
The problem occurs when I have multiple simultaneous requests (which is common for this particular service). For example:
Thread 1 checks the flag and sees that it is N.
Thread 2 checks the flag and sees that it is N.
Thread 1 updates the flag to Y and does the stuff that should only be done once.
Thread 2 also updates the flag to Y and proceeds to duplicate the work that should only be done once.
What I want is to prevent Thread 2 from reading the flag until after Thread 1 has had a chance to update it. What is the best way to do this in JDBC? I thought about using transactions, but I wasn't sure if that would provide the intended functionality. Any and all help is greatly appreciated.
Generally you use transactions to avoid this type of behavior. The first process to commit will finish without issue, whereas the second process will likely get a stale data exception and be required to start over. Alternatively, you could go with a Java Singleton pattern to allow only one thread into a process although its not really a J2EE application if only one person can use it at a time. Granted, single-threading may be what you want in this case.
Oracle apps uses object version number on many tables. Also I have seen it on some ORM software. Each update on a row increases OVN by one -either with trigger or programmatically. So once you get the row for update you have an OVN then when you are updating you also provide OVN+1 and expect that to be equal to OVN+1 on the row. If not then the row data has been changed by some one since your last fetch.
Since this kind of solution can be implemented most easily by triggers, triggers also bring a burden on execution time but I guess the least among others. But also increases vendor dependencies.
Just a thought to be considered.
Joined: May 15, 2009
One thing I am not clear on is how to use a transaction to prevent this race condition. My understanding is that a transaction will lock the row for writing, but not for reading. So, how do I prevent Thread 2 from reading the value before Thread 1 updates it? Any code samples of how to prevent this with a transaction would be greatly appreciated. I also tried using SELECT FOR UPDATE but I still get the race condition. Here is some code to illustrate my current approach, which is not working. I'm not sure if I need to roll back on line 30, but I wanted to make sure that nothing would stay locked.
I am surprised that select for update is not working for you. Select for update acquires the exclusive lock on the rows selected. Thus the read will not happen if the lock is not acquired. Thus at any point of time only one session/transaction can hold the lock. The other SELECT FOR UPDATE should simply wait for the lock to be released (either by commit or rollback). If you specify SELECT FOR UPDATE NOWAIT then the second thread should simply throw an ORA error(ORA-00054) and exit.