Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In Java, it’s not uncommon to need synchronized access to static variables. In this short tutorial, we’ll look at several ways to synchronize access to static variables among different threads.

2. About Static Variables

As a quick refresher, static variables belong to the class rather than an instance of the class. This means all instances of a class have the same state for the variable.

For example, let’s consider an Employee class with a static variable:

public class Employee {
    static int count;
    int id;
    String name;
    String title;
}

In this case, the count variable is static and represents the number of total employees that have ever worked at the company. No matter how many Employee instances we create, all of them will share the same value for count.

We can then add code to the constructor to ensure we track the count with each new employee:

public Employee(int id, String name, String title) {
    count = count + 1;
    // ...
}

While this approach is straightforward, it potentially has problems when we want to read the count variable. This is especially true in a multi-threaded environment with multiple instances of the Employee class.

Below, we’ll see different ways to synchronize access to the count variable.

3. Synchronizing Static Variables With the synchronized Keyword

The first way we can synchronize our static variable is by using Java’s synchronized keyword. There are several ways we can utilize this keyword for accessing our static variable.

First, we can create a static method that uses the synchronized keyword as a modifier in its declaration:

public Employee(int id, String name, String title) {
    incrementCount();
    // ...
}

private static synchronized void incrementCount() {
    count = count + 1;
}

public static synchronized int getCount() {
    return count;
}

In this case, the synchronized keyword locks on the class object because the variable is static. This means no matter how many instances of Employee we create, only one can access the variable at once, as long as they use the two static methods.

Secondly, we can use a synchronized block to explicitly synchronize on the class object:

private static void incrementCount() {
    synchronized(Employee.class) {
        count = count + 1;
    }
}

public static int getCount() {
    synchronized(Employee.class) {
        return count;
    }
}

Note that this is functionally equivalent to the first example, but the code is a little more explicit.

Finally, we can also use a synchronized block with a specific object instance instead of the class:

private static final Object lock = new Object();

public Employee(int id, String name, String title) {
    incrementCount();
    // ...
}

private static void incrementCount() {
    synchronized(lock) {
        count = count + 1;
    }
}

public static int getCount() {
    synchronized(lock) {
        return count;
    }
}

The reason this is sometimes preferred is because the lock is private to our class. In the first example, it’s possible for other code outside of our control to also lock on our class. With a private lock, we have full control over how it’s used.

The Java synchronized keyword is only one way to synchronize access to a static variable. Below, we’ll look at some Java APIs that can also provide synchronization to static variables.

4. Java APIs To Synchronize Static Variables

The Java programming language offers several APIs that can help with synchronization. Let’s look at two of them.

4.1. Atomic Wrappers

Introduced in Java 1.5, the AtomicInteger class is an alternative way to synchronize access to our static variable. This class provides atomic read and write operations, ensuring a consistent view of the underlying value across all threads.

For example, we could rewrite our Employee class using the AtomicInteger type instead of int:

public class Employee {
    private final static AtomicInteger count = new AtomicInteger(0);

    public Employee(int id, String name, String title) {
        count.incrementAndGet();
    }

    public static int getCount() {
        count.get();
    }
}

In addition to AtomicInteger, Java provides atomic wrappers for long and boolean, as well as reference types. All of these wrapper classes are great tools for synchronizing access to static data.

4.2. Reentrant Locks

Also introduced in Java 1.5, the ReentrantLock class is another mechanism we can use to synchronize access to static data. It provides the same basic behavior and semantics as the synchronized keyword we used earlier but with additional capabilities.

Let’s see an example of how our Employee class can use the ReentrantLock instead of int:

public class Employee {
    private static int count = 0;
    private static final ReentrantLock lock = new ReentrantLock();

    public Employee(int id, String name, String title) {
        lock.lock();
        try {
            count = count + 1;
        }
        finally {
            lock.unlock();
        }

        // set fields
    }

    public static int getCount() {
        lock.lock();
        try {
            return count;
        }
        finally {
            lock.unlock();
        }
    }
}

There are a couple of things to note about this approach. First, it’s much more verbose than the others. Each time we access the shared variable, we have to ensure we lock right before the access and unlock right after. This can lead to programmer errors if we forget to do this sequence in every place we access the shared static variable.

Additionally, the documentation for the class suggests using a try/finally block to properly lock and unlock. This adds additional lines of code and verbosity, as well as more potential for programmer error if we forget to do this in all cases.

That said, the ReentrantLock class offers additional behavior beyond the synchronized keyword. Among other things, it allows us to set a fairness flag and query the state of the lock to get a detailed view of how many threads are waiting on it.

5. Conclusion

In this article, we looked at several different ways to synchronize access to a static variable across different instances and threads. We first looked at the Java synchronized keyword and saw examples of how we use it as both a method modifier and a static code block.

We then looked at two features of the Java concurrent API: AtomicInteger and ReeantrantLock. Both of these APIs offer ways to synchronize access to shared data with some additional benefits beyond the synchronized keyword.

All of the examples above can be found over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.