Skip to main content

Real-life problem (Java Spring application)

We have a stateless application that integrates with a third party system. Our app identifies users by a JWT token. Whenever calls to the third party system are made from inside our app (as a result of user-made HTTP calls), it needs to create a session for the user towards the outside system. This session should be reused and there should be a one-to-one mapping between it and the user.

What happens when we have, for example, two parallel calls made by one user towards our application? There is a possibility for the aforementioned one-to-one relationship to be broken and multiple sessions to be created for that same user. We want the second thread/call to reuse the session created by the first. Basically, we want our “session creation/retrieval” block to be thread synchronized by the user ID (which we retrieve from the JWT token).

We know that Java’s synchronized block locks by Object reference. In our case, we want the value of the user ID to serve as a locking mutex. Of course, we cannot change the behavior of Java’s synchronized block. Nor can we lock by a Session Object, since our application is stateless and does not leverage the use of one.

Solution

So, what are we to do?

Let us keep our user IDs in a HashMap. That way, we can retrieve the same Object reference for a given user ID from the map and lock by it.

There are two impediments in this approach:

  1. We need the map to be able to handle concurrency/multiple threads.
  2. We need the map to dynamically remove stale entries (thus releasing memory) when they are not used for synchronization. This is very important for large applications with millions of users. We do not want to get memory overflows. Java’s weak references will come in handy here.

There is a ConcurrentHashMap in Java, as well as a WeakHashMap (which leverages the native functionality of Java’s WeakReference type). In our situation – we want both. We can make use of Collections.synchronizedMap(new WeakHashMap<>()), but its performance is not as good as one would think.

Luckily, there exists a ConcurrentReferenceHashMap class inside the maven package org.hibernate:hibernate-validator.

Note: This map is not to be confused with the one coming from Spring with the same name. That one does not suit our case scenario and is mainly intended for internal Spring-related functionalities.

Here is the maven dependency:

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>8.0.0.Final</version>
</dependency>

If you do not want to add the whole dependency to your code you can just copy and paste the map class directly. It contains no additional external dependencies towards outside classes.

Note: If you are using org.springframework.boot:spring-boot-starter-validation, you already have the hibernate-validator as a transitive dependency in your classpath.

Next, create XMutexFactory class :

import org.hibernate.validator.internal.util.ConcurrentReferenceHashMap;
import org.hibernate.validator.internal.util.ConcurrentReferenceHashMap.ReferenceType;

public class XMutexFactory<T> {

    private static final int INITIAL_CAPACITY = 16;
    private static final float LOAD_FACTOR = 0.75f;
    private static final int CONCURRENCY_LEVEL = 16;
    private static final ReferenceType REFERENCE_TYPE = ReferenceType.WEAK;

    private final ConcurrentReferenceHashMap<T, XMutex<T>> map;

    public XMutexFactory() {
        map = new ConcurrentReferenceHashMap<>(
                INITIAL_CAPACITY,
                LOAD_FACTOR,
                CONCURRENCY_LEVEL,
                REFERENCE_TYPE,
                REFERENCE_TYPE,
                null
        );
    }

    public XMutex<T> getMutex(T key) {
        return map.computeIfAbsent(key, XMutex::new);
    }
}

There is only one method here (getMutex), which will be used for retrieving the locking reference for the synchronized block.

The important part to note here is the REFERENCE_TYPE we send to the map’s constructor. This should be set to WEAK for both keys and values (hence the 18th and 19th lines of code from the snippet above).

The XMutex class will serve as the value and as a wrapper around the key (read on until the end to find out why this is needed).

public class XMutex<T> {

    private final T key;

    public XMutex(T key) {
        this.key = key;
    }

    public T getKey() {
        return key;
    }
}

The only thing left to do now is to use this factory when coding our synchronized blocks.

You can create a bean (if you are using Spring) and inject it where necessary, or you can just create your own specific singleton of this factory.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class XMutexFactoryConfiguration {

    @Bean
    public XMutexFactory<String> stringMutexFactory() {
        return new XMutexFactory<>();
    }
}

Example usage:

public Session getSession() {
  String userId = userService.getLoggedInUserID();
  synchronized (stringMutexFactory.getMutex(userId)) {
    // Create session. If one is already present, return that one.
    // ...
  }
}

That is it! So, you basically just call stringMutexFactory.getMutex("whatever value you want" /* in our case it is the user ID */) which will serve as the locking reference. This method will return us a mutex for the current user.

You might be asking: how are we certain that the garbage collector (GC) will not delete the user’s map entry while one of the threads still executes the synchronized block? We are using weak references for our keys and values, after all. That means that whenever we lack strong references to a given object, that object is liable for removal. If the GC deletes our key or value during the execution of a particular thread, the thread coming in second will get a new freshly created mutex and enter the synchronized block in parallel with the first. If that were bound to happen, then we have achieved nothing. Luckily, our design of the <T, XMutex<T>> pair will not allow it.

Take a look at the following image (pink lines are weak references; black lines are regular strong references):

As you can see, the synchronized block has a strong reference towards XMutex(key). XMutex(key) in turn has a strong reference to the actual key. So, while the synchronized block “lives”, there will be two strong references in existence – one towards the value and one towards the key. After the synchronized block ends, the mutex can be removed from memory by the GC. This means that the key: "abc" can also be removed afterwards, since there would be no more strong references to it (that is, if the application does not reference that same key somewhere else in the code). Only the couple of weak references coming from the internal structure of the map would remain, hence both XMutex(key) and key: "abc" will be liable for deletion.

The map will eventually – in the background – also clear/release (for garbage collecting) the “empty” WeakReference objects (by “empty” we mean WeakReferences that have null as a referent). This will also clear up some additional memory.

Note: Whenever the map happens to contain a mapping for a given key to a null value (in our case this can happen after XMutex(key) is deleted from memory and the WeakReference(value) in the map is left with null as a referent), it is bound to release (for garbage collecting) both of the WeakReference objects. It does not matter if the key: "abc" is still in memory and if it might still have strong references to it from the outside. Mapping a key to null brings no worth and so the map will cut both of the weak references, thus releasing additional memory.

We might even do something like this (instead of creating a wrapper XMutex(key), we will use the same key as value):

The problem with this is that whenever there is a strong reference (for whatever reason) to the key (in our case the user ID), the WeakReference(value) inside the map will not be predisposed for removal, hence the whole entry will keep its memory in the map. We do not have that weakness in the first example. We want to use the value for synchronization purposes only, so there is no sense for it being the same reference as the key.

Summary

To wrap up, you only need to do a small number of things to leverage the power of “value” synchronization (mind the quotes; we are only making it look as if we are using values instead of references as mutexes):

  1. Add the dependency for the map.
  2. Copy and paste the XMutex class.
  3. Copy and paste the XMutexFactory class.
  4. Inject the factory where you need it and use it for mutex retrieval and synchronization.

Keep in mind that this value can be whatever Object, not just a user ID String as per our example. The important thing is for the “value”-class to implement hashCode and equals, so that it can be used as a key inside the ConcurrentReferenceHashMap.

A big pro to this solution is that it is not Spring framework specific. One can use this same architecture down to the most basic console Java applications.

Additional resources

For background knowledge, it would be good for one to read about Strong, Soft and Weak references in Java. General knowledge about thread synchronization is also a must.

Leave a Reply


The reCAPTCHA verification period has expired. Please reload the page.