Object Sharing in Multi-threaded Environments
Tags: java, tutorial, beginners, programming
Published: 2024-03-30 10:42:50

All the intricacies of a multi-threaded environment arises from sharing objects across threads. As discussed [here](https://medium.com/@the_infinity/thread-safety-from-an-object-oriented-perspective-2913b1879d0c), 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_**’.

```java
class Escape1 {
  // ...
  public static List<Integer> activeUserIds = new ArrayList<>();
  // ...
}
```

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.

```java
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);
    }
  }
}
```

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.

```java
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();
    }
}
```

## 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 ~
* [Visibility across Threads](https://medium.com/p/e13119e8d685)
* [Thread Safety from an Object Oriented Perspective](https://medium.com/p/2913b1879d0c)
* [Java’s Synchronized Collections](https://medium.com/@the_infinity/javas-synchronized-collections-07712ae3b2cb)
* [A Nice Race Condition](https://medium.com/@the_infinity/a-nice-race-condition-58a560e34cec)
* [Producer-Consumer pattern using Java’s Blocking Queue](https://medium.com/@the_infinity/producer-consumer-pattern-using-javas-blocking-queues-06d147d3c38a)