Lesson 12: Multi-threaded Execution
Introduction
Multithreading is a complex subject, and it is very hard to find the right balance between too little and too much information in a document such as this. We will attempt to point out the basic issues related to multithreading so you know what follow-up questions to ask.
The first thing you should find out when you're considering the use of one proxy object on multiple threads is whether the underlying Java types are thread-safe. The proxy types do not introduce any limitations as to their usage on multiple threads. Any proxy object may be used on as many threads as you wish, as long as the underlying Java object supports this usage.
The only time this is not true is when you use a proxy object on multiple threads and one of the threads
calls Dispose()
on it, either directly or indirectly through a using
statement.
That is really not a JuggerNET limitation but a bug in your code. It's no different from
freeing some memory on one thread while it is still being used on other threads.
Conversely, it is very possible to have two different proxy objects that reference the same Java object. Please remember that a proxy object is just a wrapper around a reference to a Java object; it is possible for many different proxy objects to reference one and the same Java object. You can for example call a proxy Java factory method twice and if it returns the same object from both calls you now have two proxy objects that reference the same Java object. If you use these proxy objects on different threads you are still using the same Java object on different threads. This is no different from the same situation in pure Java code. We just mention it here because when you are experiencing problems related to multithreaded use, we want you to remember that just because you're using different proxy objects does not necessarily mean that you're using different Java objects and you very well could have a race condition somewhere in your code.
Using the JavaProxyMonitor
Type
As an example of some well-understood Java types with different thread-safety, take the java.util.ArrayList
and the java.util.Vector
types. They provide similar functionality, but they differ in their approach to
thread-safety (chiefly for performance reasons). ArrayLists
are faster but do not support unsynchronized
concurrent modifications whereas Vectors
are slower and do support unsynchronized concurrent modifications.
An instance of a Vector
could be used without additional safeguards on the .NET side, whereas an instance
of an ArrayList
could not.
What if you have to use a non-threadsafe type concurrently from multiple .NET threads? If the instance is only used
from the .NET side, you can use .NET synchronization to safeguard access. If the instance is also concurrently modified
by Java worker threads, you have to use Java synchronization to safeguard against concurrent access. The JuggerNET runtime
exposes a helper class for this purpose: Codemesh.JuggerNET.JavaProxyMonitor
.
The JavaProxyMonitor
type allows you to explicitly request and relinquish exclusive access to a proxy
instance from the current thread of execution via the Enter()
and Exit() functions. The
following example illustrates the usage:
using Codemesh.JuggerNET; using Java.Util; ArrayList list = new ArrayList(); ... // code that might be executed concurrently on multiple threads; // This essentially creates a critical section for the 'list' object; // You're in charge of entering/exiting the critical section JavaProxyMonitor monitor = new JavaProxyMonitor( list ); monitor.Enter(); list.Add( "value1" ); list.Add( "value2" ); monitor.Exit();
Using the JavaProxyLockHolder
Type
The above code has just one problem: it's not exception-safe. If one of the Add()
methods throws
an exception, our thread will never relinquish its exclusive hold on the 'list' instance because the Exit()
method never gets called. The JavaProxyLockHolder
allows you to use a much nicer pattern that takes care
of this concern:
using Codemesh.JuggerNET; using Java.Util; ArrayList list = new ArrayList(); ... // code that might be executed concurrently on multiple threads; // essentially creates a critical section for the 'list' object // that has the scope of the using clause using( JavaProxyLockHolder h = new JavaProxyLockHolder( list ) ) { list.Add( "value1" ); list.Add( "value2" ); }
This second example is much easier to write and much safer to use because the JavaProxyLockHolder
automatically invokes Enter()
in its constructor and is guaranteed to invoke Exit()
when
execution leaves the scope of the using clause, whether or not it's due to an exception. You could still call
Exit()
manually, but that is unnecessary.