This article is based on ManifoldCF in Action, to be published on Oct 2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code ‘wright0535’ and get 35% discount on eBooks and pBooks ]
also read:
- Java Tutorials
- Java EE Tutorials
- Design Patterns Tutorials
- Java File IO Tutorials
Thread Contexts
Introduction
To save work, ManifoldCF provides a mechanism for keeping around data that is local to a persistent thread. This thread-local storage mechanism is called a thread context. You will find that many of the classes and methods in ManifoldCF require a thread context as an argument. The type of the argument is org.apache.manifoldcf.core.interfaces.IThreadContext.
The methods supported by a thread context are relatively simple. If you look at the interface above, you will see that there is a save() method and a get() method for saving and later retrieving some keyed piece of data.
A thread context is usually created shortly after a persistent thread begins executing. In the case of a main thread or request thread, ManifoldCF creates the thread context as soon as ManifoldCF gets control. The way to create a thread context is as follows:
IThreadContext context = ThreadContextFactory.make();
This seems pretty straightforward. But that’s not all there is to it. While it is sometimes acceptable to have more than one thread context per actual thread, it is never acceptable to trade thread contexts across threads. Most of the time, when you are writing connectors in ManifoldCF, there is rarely any need to worry about this situation, because most objects are created and used by a single thread only. But, occasionally, people need to create shared objects. When this happens, they are occasionally tempted to store thread contexts or (more typically) other objects or handles that were created using a specific thread context, in objects which may be used by more than one ManifoldCF persistent thread. Doing this is a great way to build software that fails in all sorts of non-obvious ways, and this practice should be avoided at all costs.
For example, listing 1 should immediately raise red flags in your mind.
Listing 1 A very bad way to construct a shared object
public class MyObjectFactory { static Integer lock = new Integer(0); static MyObject multithreadReference; public static MyObject get(IThreadContext tc) { synchronized (lock) #A { if (multithreadReference == null) #B multithreadReference = new MyObject(tc); #B return multithreadReference; } } } public static class MyObject { IDBInterface database; public MyObject(IThreadContext tc) { database = DBInterfaceFactory.make(tc,"mydatabasename", #C "myusername","mypassword"); #C } ... #D } #A Only one thread at a time #B If object not yet created, create it #C Create member variable “database” #D Do stuff that needs “database”
In this example, the programmer attempts to use a shared object in multiple threads. The intent is that a thread uses the MyObjectFactory.get() method to get a reference to the shared object. The shared object (of class MyObject) is, however, initialized in a manner that requires a thread context! Even worse, looking inside the MyObject class, you can see it is using the thread context to create a secondary object, which is then being saved as a member variable. Someone looking only at the MyObject member variables might not even be able to figure this out. This is definitely not going to work in real life.
So, what is the proper way to construct code like this? Actually, there are two answers. One answer involves passing the thread context into each method of the MyObject class so that each method will create the database handle. See listing 2.
Listing 2 One solution to the problem of cross-thread shared objects
public class MyObjectFactory { static Integer lock = new Integer(0); static MyObject multithreadReference; public static MyObject get() { synchronized (lock) { if (multithreadReference == null) { multithreadReference = new MyObject(); #A } return multithreadReference; } } } public static class MyObject { public MyObject() { } protected IDBInterface makeDatabaseHandle(IThreadContext tc) #B { #B return DBInterfaceFactory.make(tc,"mydatabasename", #B "myusername","mypassword"); #B } #B ... #C } #A Construct object without thread context #B Protected method builds database handle #C All methods have thread context argument
In this solution, the object is still shared across threads with the same semantics, but care has been taken to be sure that no thread-context-related data is stored as a class member. This, of course, means that every MyObject method that needs a database will have to receive a thread context argument.
Is there a better way? There may be—but only if the contract is changed somewhat. You see, as soon as the shared object becomes specific to a thread in any way, it can only be used by that one thread until that thread is done with it. So, instead of allowing multiple threads to use the same object simultaneously, we can imagine a shared object that only one thread at a time can use. See listing 3.
Listing 3 Introducing a setContext() method to allow cross-thread sharing
public class MyObjectFactory { static Integer lock = new Integer(0); static MyObject multithreadReference = new MyObject(); public static MyObject grab(IThreadContext tc) #A throws InterruptedException #A { while (true) { synchronized (lock) { if (multithreadReference == null) { lock.wait(); continue; } MyObject rval = multithreadReference; multithreadreference = null; rval.setContext(tc); #B return multithreadReference; } } } public static void release(MyObject myobject) #C { synchronized (lock) { myobject.setContext(null); #D multithreadReference = myobject; lock.notifyAll(); } } } public static class MyObject { protected IDBInterface database = null; public MyObject() { } protected void setContext(IThreadContext tc) #E { #E if (tc == null) #E database = null; #E else #E database = DBInterfaceFactory.make(tc,"mydatabasename", #E "myusername","mypassword"); #E } #E ... #F } #A Method to get shared object #B Initialize with the thread context #C Method to release shared object #D Clear the thread context, to prevent problems #E Context set method initializes “database” #F Methods use “database”
In this implementation, the caller is meant to use the methods MyObjectFactory.grab() and MyObjectFactory.release() to obtain the object temporarily and later release it. As part of obtaining the object, the object is initialized with the current thread context. The object is expected to be used by the same thread that performed the grab() operation, or there would be little point in the whole exercise.
Summary
We discussed thread-local storage mechanism called thread context, which is used by ManifoldCF for keeping around data that is local to a persistent thread.