Java Synchronization
Should a Java method that creates a new record in a database table and returns the primary key of the newly created record be declared as synchronized? The method doesn't access any instance variables and the class is a singleton.
This is not a homework question, I've read some articles on Java multithreaded programming and it's not at all clear to me what the correct answer is.
Thanks in anticipation.
Java Newbie
Monday, October 6, 2003
If you have designed transaction and acidity into your database you don't really have to worry about it again in the Java code, but you should shape your code to DEPEND on the decision of the db. You only use sychronization operations (in java threading) for exclusive write/read access to objects without such protection--like something unprotected by transactions.
Li-fan Chen
Monday, October 6, 2003
Thanks, that's most helpful. The bit I'm concerned about is returning the primary key to the caller.
What happens if thread A executes the method which in turn calls the stored procedure that creates the record in the table, but before the primary key can be assigned as the method return value it's pre-empted by thread B which calls the method and creates another new record with a different primary key. In this scenario there could be a mis-match between the primary keys returned by the method and their corresponding records.
Does this sound plausible?
Java Newbie
Monday, October 6, 2003
The thing to do is to look at the atomic operations in your method and consider what happens if the same operation is run at the same time.
For example, you may have two atomic operations here:
Create new record.
Return primary key.
What if two objects on seperate threads called the same method. The two threads could look like this
o1.createRecord
o2.createRecord
o1.getPrimaryKey
o2.getPrimaryKey
Now, does the code executed between 01.createRecord and 01.getPrimaryKey cause any problems?
The following scenario may or may not be realistic, but here goes.
Imagine you are using a database that stores the primary key of the last inserted recorded in a variable LATEST_RECORD, and you are using this variable to get the primary key after your insert. This is what happens:
o1.createRecord Record '101' is created, LATEST_RECORD = 101
o2.createRecord Record '102' is created, LATEST_RECORD = 102
o1.getPrimaryKey o1 retrieves 102 from the database
o2.getPrimaryKey o2 retrieves 102 from the database
Now both objects o1 and o2 hold a reference to the same record 102, and 101 has been lost.
Ged Byrne
Monday, October 6, 2003
JN,
I see you had already answered your own question :)
Yes it is plausable. Don't forget that your code should be DB independant. It may be that your current DB will not allow this scenario, but what if they switch in 2 years time to one that does?
Ged Byrne
Monday, October 6, 2003
Thanks Ged, you've helped to clarify my thinking.
Java Newbie
Monday, October 6, 2003
>In this scenario there could be a mis-match between the primary keys returned by the method and their corresponding records.
If your database auto-generated the primary key (say in the case of SQL Server).. on behalf of an autoincrementing field.. the primary key generation and the record insertion should and can be considered atomic. And if thread A was running this, thread B should have to stand in line at least logically. I think you can depend on the two separate db operations to return the right db-generated primary key everytime. But if you (as in your client-side code) generated the primary key, it's up to you to ensure only one user can call "getGenUniqueKey()" at a time, requiring you to write a sync wrapper around the primary key incrementation variable.
Li-fan Chen
Monday, October 6, 2003
It's an Oracle database. A SELECT statement is executed to get the next value from an Oracle sequence. This is then stored in a local variable. The record is created (and the value from the sequence passed in as a parameter) and then the local variable is assigned to the method result.
Java Newbie
Monday, October 6, 2003
But, the situation GED carefully laid out will actually happen. Sometimes you are trying to read a special variable normally assigned the primary key of the last insert operation, you can't expect these variables to hold the right value for long on a busy server. So the minute you make your operation you must grab that variable out of that magic variable and store it somewhere else. Or it'll be clobbered and sent off to the bit bucket..
-- stored procedure pattern
1. call insert
2. save last primary key
3. continue other stuff that may use saved primary key later
-- wrong pattern
1. call insert
1.1 do some stuff
2. save last primary key
3. continue other stuff that may use saved primary key later
Li-fan Chen
Monday, October 6, 2003
Well I don't think PL/SQL jobs run into variable sharing problems normally--especially a local variable. And I don't think Oracle sequences will ever give out the same value twice. And inserting two records with the sequence reversed in the wrong order is not a bad thing, acidity is still preserved I think.
Li-fan Chen
Monday, October 6, 2003
but if your threads share the same local variable.. I would make sure they aren't sharing the same local variable. Each thread should get their own variable.
Li-fan Chen
Monday, October 6, 2003
For Oracle, you have a few options. Either use "sequence_name.nextval" in your insert query and then "select sequence_name.currval" *in the same transaction*, or else grab the sequence value first and then do the insert with the actual value. The second is slightly more elegant, imho, and may generalize to other databases with fewer headarches. Of course, you can also write a storedproc to do the insert and return the pkey, in which case you have the same problem, but your java code is more database-independent.
Personally, I prefer the second approach, with no storedprocs, but many people will prefer the storedproc approach.
Either way, you won't need to use java synchronization - since your code could conceivably run on several different machines at the same time, hitting the same database, java synchronization won't help you. The database's built-in synchronization/isolation facilities (i.e. transactions and atomicity) are the only thing that will help.
schmoe
Monday, October 6, 2003
How will that help with the scenario I outlined earlier?
Java Newbie
Monday, October 6, 2003
Storedprocs in Oracle do the right thing with respect to threading and local variables, so if the variable is local to the storedproc, the two threads effectively each have their own variable. When one selects the next value from the sequence into its local variable, the other thread's storedproc can't see this value, and gets a different value from the sequence, so everything works ok.
If the variable is a package-scoped variable, instead of a local variable, there's only one version of it across many threads, so it's entirely possible to have a collision. (I'm not sure of the concurrency guarantees around package-scoped variables; anyone?)
schmoe
Monday, October 6, 2003
So the code within the stored procedure will be thread safe but my method still needs to get the sequence value out of the stored procedure because that's what the method returns.
Java Newbie
Monday, October 6, 2003
Correct. The storedproc should return the primary key in the same way (and for the same reason) that your method does.
schmoe
Monday, October 6, 2003
I don't think you understand the point that I'm trying to make. I think that the primary key value from the stored procedure being assigned to the method return value is potentially not thread-safe.
Java Newbie
Monday, October 6, 2003
Java Newbie:
You have nothing to worry about.
Synchronization is only a worry when the code in your method does one of two things:
1) works with some outside resource that isn't thread safe.
2) Reads or changes a persistent object or class field
This means that variables defined WITHIN your method are always threadsafe. Every call to a method has its own version of these variables. If the method is called by another thread, or by the same thread, or even if the method calls itself (recursion), the values of these variables are not shared.
As you assumed at first, your outside resource (the database and your stored procedure) is already threadsafe. Don't worry it.
As for the second concern, you'll want to make sure that your method either doesn't use class or object fields at all, or these fields can't be changed, or that you only access them once and you don't care if they are perfectly up-to-date. If you think carefully about your class and object fields you should be able to figure out if it's a problem.
I think that you are just worried about your method variables, also known as local variables. Mr. Li-fan Chen's answer to the contrary, these are always threadsafe.
Ethan Herdrick
Monday, October 6, 2003
Yeah, local are local. You don't have to worry about them. objects and static variables that have a longer life span beyond the routines that call them are what you have to worry about generally.
Li-fan Chen
Monday, October 6, 2003
Thanks everyone, that's great. Over and out!
Java Newbie
Tuesday, October 7, 2003
The real question is, whatever you are trying to accomplish does it really have to be multithreaded?
T. Norman
Tuesday, October 7, 2003
It's a class that will be used as part of a web application, so it does need to be thread safe.
Java Newbie
Tuesday, October 7, 2003
Recent Topics
Fog Creek Home
|