Object Sharing in Multi-threaded Environments

the_infinity

Rishabh Agarwal

Posted on March 30, 2024

Object Sharing in Multi-threaded Environments

All the intricacies of a multi-threaded environment arises from sharing objects across threads. As discussed here, managing access to shared objects is the base of thread safety. If there are no shared objects, our program is inherently thread-safe!

But it is not always possible to design a threaded application without making some objects visible across threads. In this article, we will talk about some standard practices for sharing objects. These practices will help us building more resilient and robust multi-threaded systems!

Escaped Objects

When we publish an object, we make it available to code outside of its current scope. This can be useful in many situations, but there are also cases where we want our objects to remain unpublished. However, there are times when we may need to publish objects, and it’s important to ensure that this is done in a thread-safe manner using proper synchronisation.

An accidentally published object is said to have been escaped. There are several way in which an object may escape!

Making objects visible globally ~ Storing references to objects such that any executing thread can access it causes objects to ‘escape’.

class Escape1 {
  // ...
  public static List<Integer> activeUserIds = new ArrayList<>();
  // ...
}
Enter fullscreen mode Exit fullscreen mode

In the example above, any thread can access activeUserIds and it is technically escaped!

Returning objects from non-private methods ~ Returning objects from non-private methods also publishes objects. This is because any executing thread can get hold of internal states of the object.

class Escape2 {
  // ...
  private static List<Integer> activeUserIds = new ArrayList<>();
  // ...
  public List<Integer> getActiveUserIds() { return activeUserIds; }
  public void addActiveUser(Integer userId) {
    boolean userExists = activeUserIds.stream().anyMatch(id -> id == userId);
    if(!userExists) {
      activeUserIds.add(userId);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In the above example, the activeUserIds variable, intended to be private to Escape2 class, has escaped its intended scope due to the accessibility of the getActiveUserIds() method. This situation poses a risk of breaking the invariant that an ID should only appear once in the activeUserIds list.

Passing object as parameter to alien methods ~ When we pass objects as parameter to an alien method, there is a possibility of that method using the object in a way that breaks the bounded invariants. Even worse, that method may store a reference to the object and modify it later from another thread. It therefore causes objects to escape!

Publishing inner class instance ~ When an object of inner class is published, an implicit reference to the outer class’ object is published. This leads to escaping!

Confining Objects to Thread

When objects are used only from a single thread, there is no requirement of extra management to make your code thread safe.

Utilising an object from a single thread is an implementation decision, and although the language provides mechanisms to enforce these constraints, it ultimately falls on the programmer to guarantee that such objects remain within the intended scope of a single thread.

Use of local variables helps us confine objects to thread. This is because each thread maintains its own set of local variables on stack. This stack is inaccessible to other threads. However, we need to take special care of not publishing reference to such local variables.

Another way of confining object to a thread is to use Java’s ThreadLocal class. Objects of this class hold separate values for separate executing threads.

public class ThreadLocalTesting {
    final static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
    public static void main(String[] args) throws InterruptedException {
        // Setting threadLocal to 5 from manin thread
        threadLocal.set(5);

        // Creating a new thread
        final Thread thread = new Thread(() -> {
            System.out.println("::: Thread => " + threadLocal.get());
            // Setting threadLocal to 10 from new thread
            threadLocal.set(10);
            System.out.println("::: Thread => " + threadLocal.get());
        });

        thread.start();
        System.out.println("::: Main => " + threadLocal.get());
        thread.join();
    }
}
Enter fullscreen mode Exit fullscreen mode

Make your objects Immutable

Need for thread-safety arise when mutable states are shared across threads. We just saw how can we ensure objects are confined to a single thread. Let us consider the other end, objects with immutable states.

Since an immutable object can always be in a single state, it is inherently thread-safe! But how do you define immutability? An immutable object —

  • Cannot have its state modified after construction
  • All its fields are final
  • Properly constructed i.e., this reference does not escape during construction

Immutable objects can be safely accessed across threads even when synchronisation is not used!

Note ~ It is considered a best practice to mark all fields as private unless they require greater visibility. Similarly, it is advisable to make all fields final unless there is a specific requirement for them to be mutable.

Safely Publishing Objects

Sometimes, it’s necessary to share objects across threads, but it’s essential to ensure that this is done in a thread-safe manner. When dealing with immutable shared objects, no extra synchronization is required. However, when working with mutable objects, it’s crucial to ensure they're safe publication to avoid concurrency issues.

To safely publish an object, we can,

➯ initialise object reference from static initialiser
➯ store object reference in a volatile field or AtomicReference
➯ mark the object with final keyword
➯ use proper locking when accessing the object

After an object has been safely published and is no longer subject to modification, no additional measures are necessary to guarantee its correctness in a multi-threaded environment. However, if the object can be modified after publication, it’s crucial to either implement proper locking mechanisms or ensure that the object is inherently thread-safe to maintain thread safety.


A proper understanding of when and how to share objects in a multi-threaded environment is really necessary to ensure thread-safety of your application. Avoiding sharing objects or making them immutable wherever possible is a great rule-of-thumb to ensure there are less things to fail!

With this we reach the end of this blog. If you enjoy reading this blog, consider exploring these other blogs ~

💖 💪 🙅 🚩
the_infinity
Rishabh Agarwal

Posted on March 30, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related