Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

The Malleable Singleton Pattern: Bend, Don't Break

DZone's Guide to

The Malleable Singleton Pattern: Bend, Don't Break

This tweak on the classic Singleton pattern is ideal for systems with limited amounts of memory resources, like mobile or IoT platforms.

· Java Zone
Free Resource

Learn how to troubleshoot and diagnose some of the most common performance issues in Java today. Brought to you in partnership with AppDynamics.

The Singleton design pattern is designed to meet two main criteria. First, there shall only be one instance of the class at a time. Second, the Singleton instance shall be globally available within the application. The drawback to the pattern is typically that resources are preallocated and never released. On systems where memory resources are constrained, such as mobile or the Internet of Things (IoT), this can be problematic. The Singleton pattern presented here provides a solution for memory constrained systems.

The Malleable Singleton Pattern

A general solution for the Singleton pattern creates an instance, either lazily or statically, and uses that reference for the length of the application's runtime. The instance will remain active once created. Any resources allocated for the instance usage will more than likely not be reclaimed. A typical Singleton pattern cycle is depicted in Figure 1 below. The Malleable Singleton pattern’s cycle differs slightly and can be represented by Figure 2 below. We create a new instance, use that instance, and eventually reclaim that instance. When the instance will be reclaimed depends on its implementation. However, new instances will only be created as necessary, when there is no existing instance.


Image titleFigure 1 Standard Singleton Pattern Lifecycle

Figure 1: Standard Singleton Pattern Lifecycle

Image titleFigure 2 Malleable Singleton Pattern Lifecycle

Figure 2: Malleable Singleton Pattern Lifecycle

There are three cases that must be handled due to the unique features of the Malleable Singleton pattern. This requires the code to perform multiple checks to ensure we have a valid object reference, as we cannot just rely on a simple null check. As the Java reference implementations will show later, the actual object is wrapped within a container that is either a SoftReference or a WeakReference. To ensure compliance with the Singleton pattern, we need to handle these cases as part of the implementation.

  • Reference is initially null. This only occurs on the first invocation.
  • Reference is not null and is valid. We have a valid object reference.
  • Reference is not null and is invalid (null). We previously had a valid object reference.

The Malleable Singleton pattern, as described here, is dependent on modern programming languages that perform memory management like Java’s Virtual Machine (JVM). The JVM’s object lifecycle consists of five stages governing the reachability of an object. These stages, as defined in Figure 3 below, and are strong, soft, weak, phantom, and unreachable. When an object is instantiated, it starts with a strong reference. As the objects are used and discarded, they transition to soft and weak references. Eventually, they transfer to an unreachable stage, when the object is finally reclaimed and the resources made available for reuse.

Image title

Figure 3: An Object's Reachability Lifecycle (Source)

Costs

The on-demand feature of the Malleable Singleton pattern comes at a cost. The initial costs come with the extra level of synchronization that is necessary to ensure that the object's references are valid when the instance is requested. Additionally, any actions that are necessary to construct a new instance must be performed again. This may include local resource allocation, service lookup, database queries, and other similar actions. These factors will need to be taken into account when choosing to use this pattern.

Malleable Singleton Pattern Reference Implementation

The Malleable Singleton pattern reference here is implemented in Java using two forms; weak and soft. The behavior between the two is subtle, but different. In a weak implementation, the internal resources will be reclaimed if there are no active references the next time the garbage collection event is run. However, in the soft implementation, the internal resources will only be reclaimed if the system resources are constrained.

Weak Reference Implementation

The weak Malleable Singleton pattern reference implementation depends on the garbage collector aggressively reclaiming any resources that are unused during its next event. This means that if there are no active references to the Singleton object's instance, its internal references will be reclaimed by the JVM. On the next call to its instance method, a new object reference will be created. The weak reference implementation relies on the Java’s WeakReference class. The WeakReference class has been around since Java 1.2 and is used by the WeakHashMap implementation. The sample reference implementation is shown in Figure 4 below.

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;

public class WeakSingleton
{
    private transient static Reference<WeakSingleton> instanceOf;

    private WeakSingleton()
    {
        // constructor
    }

    /// case 1 initial reference to instanceOf is null
    /// case 2 instanceOf references an invalid object (was GC by JVM)
    /// case 3 instanceOf references a valid object
    public static WeakSingleton getInstanceOf()
    {
        synchronized (WeakSingleton.class) {
            if ((instanceOf == null) || (instanceOf.get() == null))
            {
                    WeakSingleton weakSingleton = new WeakSingleton();
                    instanceOf = new WeakReference<WeakSingleton>(weakSingleton);
            }
            return instanceOf.get();
        }
    }
}

Figure 4: Weak Malleable Singleton Reference Code

Soft Reference Implementation

The soft implementation is a Malleable Singleton pattern that depends on the garbage collector lazily reclaiming resources that are unused by the application. This means if there are no active references to the Singleton, and JVM resources are low, its internal references will be reclaimed. On the next call to its instance method, a new object reference will be created. The reference implementation is shown below in Figure 5.

import java.lang.ref.Reference;
import java.lang.ref.SoftReference;

public class SoftSingleton
{
    private transient static Reference<SoftSingleton> instanceOf;

    private SoftSingleton()
    {
        // constructor
    }

    /// case 1 initial reference to instanceOf is null
    /// case 2 instanceOf references an invalid object (was GC by JVM)
    /// case 3 instanceOf references a valid object
    public static SoftSingleton getInstanceOf()
    {
        synchronized (SoftSingleton.class) {
            if ((instanceOf == null) || (instanceOf.get() == null))
            {
                    SoftSingleton softSingleton = new SoftSingleton();
                    instanceOf = new SoftReference<SoftSingleton>(softSingleton);
            }
            return instanceOf.get();
        }
    }
}

Figure 5: Soft Malleable Singleton Reference Code

Potential for Abuse

The Malleable Singleton pattern implementation expects the invoking code to not cache its instance. Caching an instance will prevent the allocated object's resources from being released. A typical Singleton usage example is shown in Table 6. A bad example, Table 7, demonstrates a cached copy. It is acceptable to maintain a brief reference to the object instances for a short duration in certain cases. This is shown in Table 8, where the instance is kept because it will be used multiple times, then discarded afterward. However, cases where code maintains a permanent or global reference to the Singleton instance will defeat the purpose of the pattern. In this case, the instance is cached and the Malleable Singleton will mimic a traditional Singleton pattern.

import java.lang.ref.SoftReference;

public class XYZ
{
    public void do()
    {
         SoftSingleton.getInstanceO().f();
         …
    }
}

Figure 6: Typical Use Case


import java.lang.ref.SoftReference;

public class XYZ
{
    private static SoftSingleton instanceOf = SoftSingleton.getInstanceOf();

    public void do()
    {
         instanceOf.f();
         …
    }
}

Figure 7: Bad Use Case


import java.lang.ref.SoftReference;

public class XYZ
{
    public void do()
    {
         SoftwareSingleton softSingleton = SoftSingleton.getInstanceO();

         for (int i = 0; i < 10; i++)
         {
              softSingleton.f();
              …
         }
         …
    }
}

Figure 8: Acceptable Use Case

Final Remarks

The Malleable Singleton pattern is designed to extend, but maintain, backward compatibility with the Singleton pattern. The concept will help systems with low memory requirements, such as mobile and IoT platforms, maintain a dynamic, but small footprint by reclaiming unused resources

But its use is not restricted to their application.

However, its flexibility does come with a cost, as it may result in new instances needing to be constructed on demand. Additionally, the pattern is subject to abuse by developers if used improperly by maintaining a permanent reference to it. Despite this, if the rules are followed, the Malleable Singleton pattern can be a valuable usage pattern for your project.

Understand the needs and benefits around implementing the right monitoring solution for a growing containerized market. Brought to you in partnership with AppDynamics.

Topics:
singleton design pattern ,java ,java memory management ,tutorial ,low memory systems

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}