3

I have a singleton object that implements some basic functionality in a common library. It's much more complicated than this, but for documenting the issue, below is the Singleton implementation, with all but one inner properties/objects removed:

package mystuff.lib.common

public class CommonSingleton {


    public final static CommonSingleton INSTANCE = new CommonSingleton();

    private Cache<String, Object> cache = null;

    private CommonSingleton() {
        // Exists only to defeat instantiation.
        Initialize();
    }

    private void Initialize() {
        cache = null;
    }

    public Cache<String, Object> getCache() {
        if (cache == null) {
            cache = new Cache<String, Object>();
        }
        return cache;
    }

    public void Reload() {
        Initialize();
    }

}

I have included that library in two SEPARATE projects. The library isn't installed on the machine where the code runs. The contents of the common library is included within each JAR when the artifacts are created in IntelliJ IDEA.

Both projects (JAR files) load and run (as plugins) inside a parent application, so they're both loaded inside the same JVM. I expected each JAR to have its own copy of this common singleton, since they are, after all, separate JAR files. It is only included in a common library because many of my projects require the same functionality and I would rather include the library in my project than have multiple copies of the code floating around.

Project/Module #1: TestPluginOne.JAR

package mystuff.plugins.TestPluginOne;

import mystuff.lib.common.CommonSingleton;
import mystuff.lib.common.Objects.Cache;

public class TestClassOne {

    public TestClassOne() {
    }

    public Boolean SaveItemInCache(String key, String content) {
        return CommonSingleton.INSTANCE.getCache().add(key, content, 30);
    }

    public void Reload() {
        CommonSingleton.INSTANCE.Reload();
    }

}

Project/Module #2: TestPluginTwo.JAR

package mystuff.plugins.TestPluginTwo;

import mystuff.lib.common.CommonSingleton;
import mystuff.lib.common.Objects.Cache;

public class TestClassTwo {

    public TestClassTwo() {
    }

    public String GetItemFromCache(String key) {
        return CommonSingleton.INSTANCE.getCache().get(key);
    }

    public void Reload() {
        CommonSingleton.INSTANCE.Reload();
    }

}

I am finding, however, that when I use this object in both of my modules (separate JAR files, mind you), that they are both accessing the same singleton. While this particular example with a Cache object, might not indicate any issue with this, there are many reasons why I need these treated as separate objects. There are many internal/private member variables that need to contain different values, depending on the project/module/plugin that is consuming this Singleton.

I'm confused. When I do this in .NET (for example) in separate DLLs, each DLL maintains it's own internal instance of that common object.

How can I have this common Singleton functionality included in separate projects, from a common library, without them sharing instances of the Singleton object?

Thanks!

  • Just wondering, would ThreadLocal work here? A `ThreadLocal`? – vikingsteve Feb 10 '14 at 09:13
  • @vikingsteve I haven't used ThreadLocal before. I checked into that and I don't think it would really apply to my situation. Besides, I believe that the host application that our plugins run in, is single threaded anyway. – Todd W. Powers Feb 10 '14 at 18:47
  • The architecture works perfectly when I only have one plugin spinning up. When accessing the common functionality the first time, a reference to the parent plugin object is passed in, so that the common library can operate appropriately. The problem I have is that when a second module (JAR) is involved, the actual common objects are shared, not just the implementation. So the second plugin passes in a reference to itself, overwriting the first plugin's initialization. – Todd W. Powers Feb 10 '14 at 18:51
  • Sounds like you just need to bite the bullet and use two distinct singletons. – vikingsteve Feb 11 '14 at 12:30
  • Yea. It's much more complicated than that though. All of the objects defined in the common library, make use of the common singleton and all of its objects. When the plugin fired up, I was passing an instance of the plugin into the common singleton, then referencing that instance from any of the common library functions. Now that I have discovered that there is no isolation between instances of the common singleton between each plugin, I will have to pass an instance of the plugin object into virtually every common library function and/or object instantiation. Can you say BARF!?! – Todd W. Powers Feb 14 '14 at 02:46

1 Answers1

1

/* What a nicely pure example of the evil of singletons. */

I'm afraid that as long as the same class is attempted to be loaded by the same class loader, the class will only be loaded once. No matter which jars are involved: a jar, unlike a DLL, is just a signed extension of a file system.

The simplest (regarding required code changes) I can imagine is to subclass your singleton in each plugin, so that a separate class is instantiated, with separate state.

Otherwise, you could either explicitly initialize instances or use dependency injection to provide you with instances.

Community
  • 1
  • 1
9000
  • 37,110
  • 8
  • 58
  • 98
  • Yes. I agree that singletons can be evil if not used correctly, however when I need to ensure that there is only ever one instance of an object like a logging or caching mechanism in existence, there really is no better way. The reason that I did not initially subclass the singleton was due to the fact that the singleton has no default constructor, so I can not add my own private constructor in the subclass to initialize the object. – Todd W. Powers Feb 09 '14 at 08:24
  • The solution to that would be to make the class abstract and declare an abstract method that all subclasses must implement to initialize themselves, then calling that method from the private constructor on the abstract baseclass. However, that doesn't work either because the public static member variable INSTANCE can not be initialized within an abstract class. – Todd W. Powers Feb 09 '14 at 08:39
  • My only option is to simply subclass the singleton manually, then override the Initialize() method manually and be sure to call super.Initialize(). Which is messy, manual, and would not be enforced or automatically handled if someone else decided to implement the same solution in another one of our projects. – Todd W. Powers Feb 09 '14 at 08:39
  • Scratch that. I can't subclass a class that has no default constructor anyway. Sure wish Java had a construct similar to a DLL, so I didn't have to copy all this code into each project I use it for. Ugh... – Todd W. Powers Feb 09 '14 at 08:54
  • Is there any other way to create a common library of functions in Java that will provide isolation between uses? This seems ludicrous that I can't create common functionality and re-use it from multiple plugins. Almost all of the common functions log errors and/or send information to the console that need to reference the name of the plugin, so the operator knows where the message is coming from. I don't want to have to pass an instance of the plugin into each individual method call or object creation in the common library. – Todd W. Powers Feb 14 '14 at 02:53
  • You could create an actual _instance_ of an utility class with common methods, something like `Utility utility = Utility.getInstance(MyPlugin.class)`. The `getInstance` factory would create and cache instances for you. It will look up the name of `MyPlugin` class and use it where needed, e.g. in logging: `utility.logInfo("Flux capacitor depleted")`. Would this work for you? – 9000 Feb 14 '14 at 07:38
  • I have been attempting to do something of this nature. I converted my CommonSingleton object's get methods to require an instance of the plugin, then to create/store/return the required objects based on a ConcurrentHashMap, indexed by the plugin instance. This seems to work alright, but I still had to convert all of those objects to take a plugin instance in their constructor, and store it internally. Although not my preference, this worked pretty well for stand-alone objects. However I have a number of both concrete and abstract classes that also made use of that plugin instance. – Todd W. Powers Feb 15 '14 at 11:49
  • My biggest issue is the need to obtain an instance of the plugin that called the common functionality, without passing it in everywhere. Some of the common objects are abstract classes and I can't require a parameter on the constructor. I have to define an abstract method that prompts for plugin instance. Logging is only one of the common operations defined in this library. After researching for several days, I have determined that Java does not allow any method of obtaining an instance of any object in the stack. This would have solved my issue across all objects. :/ – Todd W. Powers Feb 15 '14 at 11:51