: code tutorial (v3)

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 JMS Courier 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.