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.
Also, this appendix touches on a topic not discussed in any detail in this manual: transactions. Transactional usage is optional but nevertheless very commonly used for JE applications, especially when writing multi-threaded or multi-process applications. However, transactions also represent a topic that is too large for this book. To read a thorough description of JE and transactional processing, see the Berkeley DB, Java Edition Getting Started with Transaction Processing guide.
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 and databases 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.
Locking is performed at the database record level.
JE always checks for lock conflicts, which can be caused either by operations that run for
too long a period of time, or by deadlocks. JE decides that a lock conflict has occured when
the lock cannot be obtained within a set timeout
period. If it cannot, regardless of why the lock could
not be obtained, then LockConflictException
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 uncommitted 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 lock conflicts.
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 lock conflicts. 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 uncommitted reads.