Yesterday I got another view on the locking mechanism and I think I finally had a total solution of which I was 100% happy. Until this morning I found a 'little' problem of which I cannot find a decent solution. Let me first explain what I have now, since it is crucial to understand what I was trying to change, maybe it is also interesting for others out there:
My current implementation uses a queue for writing operations on the same record. So there is no chance that 2 threads can update the same record at the same time, since they have to wait there turn. There is no unfair treatment (no notifyAll()) and it does not consume cpu cycles, since there is only one
thread notified at a time using its lock object, it WILL receive the lock. Nice !
Besides the write, there is also the read. A read does not require a lock, meaning that reading should go everytime by every thread on every record number. If thread A is reading recordnumber one, thread B should also be able to read recordumber one at the same time.
Now, there is only one more issue left. Reading on a record number should not be allowed when there is currently a thread writing to it, and also vica versa. Allowing this will end up in reading unpredictable data. I solved this by placing "syncrhonized" on the readRecordFromDatabase() and writeRecordToDatabase() of my data class. These methods do the real databasefile access thing (using RandomAccessFile) . I will call them read() and write() from now on , to make it easier. These 2 methods are the only two syncrhonized methods I have in Data class, the rest of the syncrhonization occurs on objects in the 'DataManager' class which handles the logical locking.
When one thread gets the logical lock and it is able to write, no other thread can read since it is in the syncrhonized write method. So the read will wait behined scenes until the write finishes. Also the other way around: a thread is readin a record and in the mean time another thread gets the logical lock so it starts writing. Again, this thread has to wait until the other tread leaves the read method. nice !
So, this should cover all problems :
-A 'no cpu cycle consuming' queue mechanism when multiple threads want to write to the same record number, which promotes fair handling (first come first serve) .
-Reads do not require a lock, meaning that multiple reads on the same record number will not be queued and go at the same time.
-Safety on the lowest read/write method by using synchronized which prevents that writing and reading goes at the same time.
Ok, the problem in this lovely story is the synchronized database read/write which do the actual file access. Synchronizing them sure solves my problem, but it seems not right after I evauluated some things.
Supose you have 10 threads only doing reads. Then they are NOT doing that simultaneously, because of the synchronized read ! 9 threads are waiting until the first thread leaves the read. After that, 8 threads are waiting until thread 2 leaves the read, and so on. There is no reason why they should wait. Also, supose you have 10 threads writing each to a different record number, then each of them will receive a logical lock at the same time (because it are all different record numbers) . But, the actual write will go sequential , just because of the synchronized, while this SHOULD go simultaneously since they are all different record numbers ! *sigh*
Ok, I decided to drop the "synchronized" on the read() and write() and to build a mutex system based upon logical read/write locks. It takes a lot of thinking, but eventually it is possible. This system provides me:
- simultaneous reading of records
- simultaneously writing of different records
- simultaneously writing and reading if the record numbers are different
- queue based waiting when multiple writes occur on the same record number
- read waits on a record number that is currently locked for writing. After the unlock occurs, all the waiting reads on that record number will all be notified so they can continue reading simultaneously.
- writes to a certain record number wait when there is currently being read from that record number. They will be notified when the read ends.
Now my real question:
In the read() and write() methods I'm using RandomAccessFile. I use the seek() method to move the file pointer. But I have only once RandomAccessFile instance. So, if 10 threads are reading at the same time, they will be all moving the same pointer on the same RandomAccessFile, I suspect ? Earlier this was no issue because of the synchronized read/write...
Is this true ? And how can I solve this ? Sure, I can bring down the RandomAccessFile from class variable to local variable in the read and write method. But isn't it expensive to create a new RandomAccessFile instance everytime a record is read ? ...
Thanks.