Lazy Loading with Virtual Proxy Pattern | Thread-safety and beyond
Published: 2023-07-05 20:24:06

#What can you learn?
- What problem virtual proxy design pattern solves
- How the basic virtual proxy pattern works
= How to make the pattern thread-safe without compromising fast performance

#Prerequisite
- Basic understanding of how lambda expression works
- Just a little bit of software development experience

---

## What is Virtual Proxy Pattern?
It is a software design pattern used for lazy-loading heavy objects. Let's see an example.
```java
import java.util.*;
 
interface Image {
    public void displayImage();
}
 
class RealImage implements Image {
    private String filename;
    public RealImage(String filename) { 
        this.filename = filename;
        loadImageFromDisk();
    }

    private void loadImageFromDisk() {
        // ...
        System.out.println("Loading   "+filename);
    }

    public void displayImage() { System.out.println("Displaying "+filename); }
}
 
class ProxyImage implements Image {
    private String filename;
    private Image image;
 
    public ProxyImage(String filename) { this.filename = filename; }
    public void displayImage() {
        if (image == null) {
            image = new RealImage(filename); // Lazy allocation
        }
        image.displayImage();
    }
}
 
class ProxyExample {
    public static void main(String[] args) {
        Image image1 = new ProxyImage("HiRes_10MB_Photo1");
        Image image2 = new ProxyImage("HiRes_10MB_Photo2");
        Image image3 = new ProxyImage("HiRes_10MB_Photo3");      

        image1.displayImage(); // needs loading
        image2.displayImage(); // needs loading
        image2.displayImage(); // already loaded
        // image3 has not been loaded
    }
}
```
In the example above, ProxyImage contains a RealImage object. So we can think of it as a placeholder for an RealImage. 

But since RealImage instances are heavy weight, we only want to instantiate an actual RealImage instance only when it's needed. 

This is what Virtual Proxy pattern solves. A RealImage instance is initialized when and only when displayImage is called. 
Simple, right?

## What is the problem with Virtual Proxy pattern?
There is one thing that is sadly making the code above unusable. That is, the code has a race condition.

Consider when a multiple threads are running concurrently, and they call displayImage on the same ProxyImage instance at once. Each thread thinks "It's the first time displayImage is called. I need to initialize it!".

This ends up calling the heavy operation many times. In the worst case scenario, the heavy operation could have non-read-only operation, leaving the program in an undefined state.

## `synchronized` to the rescue
Java conveniently supports locking with ease, just by using `synchronized` keyword in the method signature.
We modify displayImage of ProxyImage class as follows:
```java
public synchronized void displayImage() {
        if (image == null) {
            image = new RealImage(filename); // Lazy allocation with thread-safety
        }
        image.displayImage();
    }
```
Now that we have locking in place, we achieved thread-safety. Yay! ... but it still has another concern.

## Performance concern
Although displayImage method is now thread-safe, it suffers performance issue. 

After the first call, there will be no mutation of `image` field of ProxyImage, which means race condition only exists in the first invocation of the method.

To address issue, we can make use of `Supplier` functional interface of java.

### With Supplier
```java
import java.util.*;
 
interface Image {
    public void displayImage();
}
 
class ProxyImage implements Image {
    private String filename;
    private Supplier<Image> imageSupplier = () -> loadImage();
 
    public ProxyImage(String filename) { this.filename = filename; }

    private synchronized Image loadImage() {
        class ImageFactory implements Supplier<Image> {
            private final String filename;
            private final Image imageInstance = new RealImage(filename);
            public ImageFactory(String filename) {
                this.filename = filename;
            }
            public Image get() { return imageInstance; }
        }
        if (!ImageFactory.class.isInstance(imageSupplier)) {
            imageSupplier = new ImageFactory();
        }
        return imageSupplier.get();
    }

    public void displayImage() {
        imageSupplier.get().displayImage();
    }
}
```
Quite a lot of changes, eh? Let's break down the changes.
### `Supplier<Image>`
- Supplier<Image> is an functional interface with `get` method that returns an instance of Image
- Initially, `imageSupplier` is a synchronized `loadImage` function but it gets replaced after the first call

### `loadImage`
- `loadImage` instantiates a ImageFactory, which is another image supplier type, defined within `loadImage`
- replaces its supplier field

### `ImageFactory`
- implements `Supplier<Image>`
- finally instantiates a `RealImage` object and returns this instance from `get` method

In a nutshell, the supplier is only synchronized for the first call and it's replaced with a non-synchronized supplier thereafter.

That is it. You made it!
That was quite long of a refactor but certainly makes your code more robust and performant.