Logo

dev-resources.site

for different kinds of informations.

Effective Java: Use Lazy Initialization Judiciously

Published at
12/8/2021
Categories
java
effective
concurrency
architecture
Author
kylec32
Author
7 person written this
kylec32
open
Effective Java: Use Lazy Initialization Judiciously

Lazy initialization is the pattern of putting off the creation of an object or process until it is needed. The idea behind this pattern is that you may never need the object and thus you saved the initialization costs. The main reason that lazy initialization is used is as an optimization. The other use that lazy initialization has is breaking tricky circular dependencies in your code.

As discussed in a previous item going down the path of optimizations is often fraught with peril and we can sometimes even decrease performance in the search of performance improvements. As with all optimizations, we should test out and confirm that we will truly see the improvements we are after. With lazy initialization, this will largely rely on how often we can completely avoid initialization of the object and how expensive that initialization is. Of note though, bringing in lazy initialization, especially in the presence of multiple threads, can introduce a new level of complexity which might not be worth its cost. For these reasons, unless presented with hard evidence to the contrary, you should use eager instantiation in almost all cases.

Let's say that we have determined that lazy instantiation is required, let's look at a few patterns of how to accomplish it. The first method can be used when trying to break a circular dependency since it is the simplest:

private FieldType field;

private synchronized FieldType getField() {
  if (field == null) {
    field = computeFieldValue();
  }
  return field;
}
Enter fullscreen mode Exit fullscreen mode

This ends up being fairly simple. Within a synchronized method, determine if the field is initialized, if it isn't, initialize it, if it is, just return it. The synchronized keyword allows us to use this method with concurrent threads at the cost of some performance. This same pattern can be used with a static field by simply marking the field as static and the function as static.

The next pattern we can use is useful when we need to lazily initialize a static field with a pattern called lazy initialization holder class idiom. This idiom uses the guarantee that a class will not be initialized until it is used.

private static class FieldHolder {
  static final FieldType field = computeFieldValue();
}

private static FieldType getField() {
  return FieldHolder.field;
}
Enter fullscreen mode Exit fullscreen mode

This is quite a beautiful pattern. On the first call to the getField method, the object reads FieldHolder.field at which point the class will be initialized. This idiom doesn't require any explicit synchronization as that will be provided only on the initialization of the class and only does a field access which means it's going to be extremely performant as well.

The next use case we may find ourselves in is needing to do lazy initialization for performance reasons on an instance field. In this case, we should look at the double-check idiom. This pattern avoids the cost of synchronization once the field is initialized with the trade-off that we need to check the field twice. It first checks the field to determine if it should look into initializing the field. If it appears it needs initialization then it obtains the lock and then double checks that initialization is still required. Because there is no locking once initialized the field must be marked as volatile.

private volatile FieldType field;

private FieldType getField() {
  FieldType result = field;
  if (result == null) {
    synchronized(this) {
      if (field == null) {
        field = result = computeFieldValue();
      }
    }
  }
  return result;
}
Enter fullscreen mode Exit fullscreen mode

The code is a bit convoluted, to be honest, but via that convolution there are benefits. The need for the local result variable may seem unclear. The purpose of this local variable is to ensure that in the common case (the case where the field is initialized) it is only read once. While not necessary it can improve performance. In the case where a field can tolerate repeated initialization, we can instead use the single-check idiom. As the name suggests, this pattern drops one of the checks to simplify the code at the cost of possible multiple initializations.

private volatile FieldType field;

private FieldType getField() {
  FieldType result = field;
  if (result == null) {
    field = result = computeFieldValue();
  }
  return result.
}
Enter fullscreen mode Exit fullscreen mode

The final variant that can be considered is if we are OK with up to a reinitialization per thread and the field is of a primitive type (except long and double) we can forgo the volatile keyword. On some architectures that can lead to greater performance.

There are many complications when trying to tackle lazy initialization. If at all possible we should avoid it. That said if after testing it is confirmed it will be of benefit, lazy initialization can be useful for improving performance or breaking a circular dependency. There are proven methods we can use presented above that balance safety and performance even in a concurrent environment.

effective Article's
30 articles in total
Favicon
What is The Best Time to Learn New Things Effectively?
Favicon
Designing an Efficient Biomass Generator Set System
Favicon
Top 10 Essential Features for an Effective Knowledge Base
Favicon
Effective Java: Consider Serialization Proxies Instead of Serialized Instances
Favicon
Effective Java: For Instance Control, Prefer Enum types to readResolve
Favicon
Effective Java: Write readObject Methods Defensively
Favicon
Effective Java: Consider Using a Custom Serialized Form
Favicon
Effective Java: Implement Serializable With Great Caution
Favicon
Effective Java: Prefer Alternatives To Java Serialization
Favicon
Effective Java: Don't Depend on the Thread Scheduler
Favicon
Effective Java: Use Lazy Initialization Judiciously
Favicon
Effective Java: Document Thread Safety
Favicon
Effective Java: Prefer Concurrency Utilities Over wait and notify
Favicon
Effective Java: Prefer Executors, Tasks, and Streams to Threads
Favicon
Effective Java: Avoid Excessive Synchronization
Favicon
Effective Java: Synchronize Access to Shared Mutable Data
Favicon
Effective Java: Don't Ignore Exceptions
Favicon
Effective Java: Strive for Failure Atomicity
Favicon
Effective Java: Include Failure-Capture Information in Detail Messages
Favicon
Effective Java: Document All Exceptions Thrown By Each Method
Favicon
Effective Java: Throw Exceptions Appropriate To The Abstraction
Favicon
Effective Java: Favor The Use of Standard Exceptions
Favicon
Effective Java: Avoid Unnecessary Use of Checked Exceptions
Favicon
Effective Java: Use Checked Exceptions for Recoverable Conditions
Favicon
Effective Java: Use Exceptions for Only Exceptional Circumstances
Favicon
Effective Java: Adhere to Generally Accepted Naming Conventions
Favicon
Effective Java: Optimize Judiciously
Favicon
Effective Java: Use Native Methods Judiciously
Favicon
Effective Java: Prefer Interfaces To Reflection
Favicon
10 effective tips for New Developers

Featured ones: