transient vs volatile in Java

In this post, we will learn the difference between "Transient" vs "Volatile" Modifiers in Java. This is a frequently asked question in Java interviews for beginners. Let's dive into it.

Difference between "Transient" vs "Volatile" Modifiers in Java

Feature transient Modifier volatile Modifier
Purpose Used to exclude a field from serialization. Used to indicate that a variable's value may change unexpectedly.
Field Serialization Fields marked as transient are not serialized when an object is serialized. No impact on field serialization.
Usage in Serialization Used with serialization mechanisms (e.g., Java Object Serialization). Not specific to serialization, used for concurrency control.
Data Persistence Used to avoid persisting sensitive or unnecessary data during serialization. Used for ensuring visibility and atomicity in multithreaded environments.
Storage Exclusion Fields marked as transient are excluded from the object's default state. No impact on the storage of variables.
Thread Synchronization No relation to thread synchronization. Used to ensure visibility of shared variables in multi-threaded environments.
Example transient int cache; volatile boolean flag;

Example

Let's create an example to demonstrate the difference between transient and volatile in Java. 

transient Keyword: 

The transient keyword is used to indicate that a variable should not be serialized when an object is converted to a byte stream using serialization. When an object is deserialized, the transient variable will be assigned its default value.

import java.io.*;

class Person implements Serializable {
    private String name;
    private transient int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

public class TransientExample {
    public static void main(String[] args) {
        Person person = new Person("Alice", 30);

        // Serialize the object to a byte array
        byte[] byteArray;
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
            oos.writeObject(person);
            byteArray = bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        // Deserialize the object from the byte array
        Person deserializedPerson;
        try (ByteArrayInputStream bis = new ByteArrayInputStream(byteArray);
             ObjectInputStream ois = new ObjectInputStream(bis)) {
            deserializedPerson = (Person) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return;
        }

        // Output the deserialized object
        System.out.println("Name: " + deserializedPerson.getName());
        System.out.println("Age: " + deserializedPerson.getAge());
    }
}

Output:

Name: Alice
Age: 0
In the example above, we have a Person class with two fields: name and age. The age field is marked as transient. When we serialize the Person object and then deserialize it, the age field is not preserved, and its value is set to the default value of int, which is 0.

volatile Keyword: 

The volatile keyword is used to indicate that a variable's value may be modified by multiple threads. It ensures that any read or write of the variable is done directly from/to the main memory, and not from a thread's local cache, thus avoiding data inconsistencies.

public class VolatileExample {
    private static volatile boolean flag = false;

    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println("Thread 1: Waiting for the flag to be true...");
            while (!flag) {
            }
            System.out.println("Thread 1: Flag is now true. Exiting...");
        }).start();

        new Thread(() -> {
            System.out.println("Thread 2: Sleeping for 2 seconds...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread 2: Setting flag to true...");
            flag = true;
        }).start();
    }
}

Output:

Thread 1: Waiting for the flag to be true...
Thread 2: Sleeping for 2 seconds...
Thread 1: Flag is now true. Exiting...
Thread 2: Setting flag to true...
In the example above, we have two threads. Thread 1 waits until the flag becomes true, while Thread 2 sets the flag to true after sleeping for 2 seconds. If the flag were not declared as volatile, Thread 1 might not see the change made by Thread 2, and it could end up waiting indefinitely. However, because we used the volatile keyword for the flag, Thread 1 reads the updated value from the main memory and exits as expected.


Comments