2012-02-10

Java Module Systems, SwingWorker, Runnable, Thread and Class Loader Issues

People often have a hard time getting used to a Java module systems constraints on class loaders and dealing with compile time versus runtime dependencies. There are a lot of issues just around getting the dependencies organized.

Often overlooked is how threading impacts class loaders. NetBeans modules, when "they" create a thread for you, will generally set the context class loader to the current class loader. However, in your own code you need to handle this.

Note Runnable is mentioned above. This does not necessarily consider the cases of EventQueue.invokeLater and invokeAndWait as those things are generally cases where the object instances have been realized, and class loading is no longer in the picture, or at least they should be, as those things should be short and to the point as they happen on the EDT, but it focuses on Runnable when given to the Thread constructor.

However, this could become a source of issues, and if creating new object instances in the Runnable, and it is ever a possibility those classes could be included in more than one module in the targeted system, then you should probably go ahead and deal with a class loader change out as your code will be running on the EDT.

Either way, the reason this becomes important is class access and class collision.

Access deals with the visibility or permissions a given class loader gives the calling code. Calling code may not have access to a certain class, and thus the current class loader should not be the one to load classes, but should instead delegate that to another module which has access to not only the public API but the private API of the module itself. That would be the one loading the classes the caller can access or the public API of the called module.

Collision is a more subtle thing to deal with. Imagine one module has class mine.Foo and another module does as well. Even though these modules may not call or touch each other and thus not collide with each other under normal calling conditions, they may very well do that in a thread without setting the context class loader as those classes will have to be loaded some how, and here the system class loader will be used which will access all the class loaders, and that means either an arbitrary class must be chosen or the first one found, and in nearly all conditions this is not what you want.

In the NetBeans module system this will cause an error as the NetBeans developers do not want this to be a surprise and thus hide crashes. Others may load them; I'm not sure, so if anyone knows how various module systems handle this please comment. Either way, unless you give Java and the module system more guidance, you will run into strange and unusual problems.

Given a class, the following should really be used:

public class MyClass {

Runnable r = new Runnable(){
public void run(){
ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(MyClass.class.getClassLoader());
}finally{
Thread.currentThread().setContextClassLoader(oldCl);
}
}
};

Thread t = new Thread(){
public void run(){
ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(MyClass.class.getClassLoader());
}finally{
Thread.currentThread().setContextClassLoader(oldCl);
}
}
};

SwingWorker<Void, Void> = new SwingWorker<Void, Void>{
public Void doInBackGround(){
ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(MyClass.class.getClassLoader());
}finally{
Thread.currentThread().setContextClassLoader(oldCl);
}
return null;
}
};

}


This does a couple things at various runtimes. When the code runs in a regular Java application with no module system, the system class loader will always be used. Next, when run in a modular application, the current caller, or current module will use its class loader in the threaded logic. Finally, in both cases, the loader is reset to whatever it was before; for throw away threads, that is probably not a big deal, but what if your code is using a thread pool?

This is a basic convention to follow for safety and refactorability. Safety as noted in access and collision. If your code is used in a modular system, and does not follow the above, it will crash as soon as you add the same classes to other modules. Refactorability per the fact that if more than one module later adds a different version of a class, or the same version different class loader, then the application will start to crash until fixed, and if the person tracking down the issue has to spend time finding and/or fixing it, they can't focus on refactoring code to work with the same classes in different ways.

Another thing to keep in mind is there are times when you may need to pass a specific class other than your own for some block of code to pin-point an exact class loader. Imagine some code you are calling will need classes which your class loader does not have access as the module being called does not expose them as part of its public API.

So, there are times when you have to think beyond the current class loader, but the good thing is no matter which class works in the modular system, the code will continue to work if used outside of one in a standard Java application.

The one major time this differs are libraries written to load and use classes dynamically. MyBatis is a good example. In such cases, the current thread context class loader should be passed along. The idea being the caller is telling you which class loaders to use. This only applies to threads however as the module wrapping the called code will have configured the context class loader as needed for the current thread.

The MyBatis folks were working to address this issue the last time I was on the lists. Someone using it in such a context now should comment if able.

I hope this will help projects (open source and not) write Java code which is compatible in both modular and non-modular applications.

2 comments:

Java Tutorial said...

Also check out more tips at Current Thread in Java

Jeff Edlund said...

@JavaTutorial, Your site extreme-java.com is completely worthless. Maybe you felt you could earn a little money by putting ads everywhere... But instead what you did was make someone avoid your site.
You think it's clever to place google ads in the middle of a paragraph? Dumb.
In the future, I plan to avoid your site at all costs!!