An in-depth description of concurrent processing in JE is beyond the scope of this manual. However, there are a few things that you should be aware of as you explore JE. Note that many of these topics are described in greater detail in other parts of this book. This section is intended only to summarize JE concurrent processing.
This appendix first describes concurrency with multithreaded applications. It then goes on to describe Multiprocess Applications.
Note the following if you are writing an application that will use multiple threads for reading and writing JE databases:
JE database and environment handles are free-threaded (that is, are thread safe), so from a mechanical perspective you do not have to synchronize access to them when they are used by multiple threads of control.
It is dangerous to close environments, databases and cursors when other database operations are in progress. So if you are going to share handles for these objects across threads, you should architect your application such that there is no possibility of a thread closing a handle when another thread is using that handle.
If a transaction is shared across threads, it is safe to call transaction.abort() from any thread. However, be aware that any thread that attempts a database operation using an aborted transaction will throw a DatabaseException. You should architect your application such that your threads are able to gracefully deal with some other thread aborting the current transaction.
If a transaction is shared across threads, make sure that transaction.commit() can never be called until all threads participating in the transaction have completed their database operations.
JE always performs locking and deadlock detection. Locking is performed at the database record level. In the event that a deadlock is detected, DeadlockException is thrown.
A non-transactional operation that reads a record locks it for the duration of the read. While locked for read, a write lock can not be obtained on that record. However, another read lock can be obtained for that record. This means that for threaded applications, multiple threads can simultaneously read a record, but no thread can write to the record while a read is in progress.
Note that if you are performing dirty reads, then no locking is performed for that read. Instead, JE uses internal mechanisms to ensure that the data you are reading is consistent (that is, it will not change mid-read).
Finally, it is possible to specify that you want a write lock for your read operation. You do this using LockMode.RMW. Use RMW when you know that your read will subsequently be followed up with a write operation. Doing so can help to avoid deadlocks.
An operation that writes to a record obtains a write lock on that record. While the write lock is in progress, no other locks can be obtained for that record (either read or write).
All locks, read or write, obtained from within a transaction are held until the transaction is either committed or aborted.
This means that the longer a transaction lives, the more likely other threads in your application are to run into deadlocks. That is, write operations performed outside of the scope of the transaction will not be able to obtain a lock on those records while the transaction is in progress. Also, by default, reads performed outside the scope of the transaction will not be able to lock records written by the transaction. However, this behavior can be overridden by configuring your reader to perform dirty reads.