top

Search

Java Tutorial

.

UpGrad

Java Tutorial

Volatile Keyword in Java

Introduction

A volatile keyword in Java is mainly used to change the value of a variable through various threads and is often used to make classes thread-safe. This means numerous strings can use this technique and instance of the classes simultaneously. The volatile keyword can be used either with primitive types or objects.

By marking a variable as “volatile”, we ensure its value is always read and written from the main memory rather than cached in a thread's local memory. 

Overview

This tutorial guides you in understanding the concept of volatile keywords in Java, along with illustrative examples and critical differences between Synchronized, Transient, and Atomic keywords. 

What Is a Volatile Keyword in Java?

The volatile keyword in Java shows that the value of a variable can be altered through various strings simultaneously. When a variable is marked 'volatile', it guarantees that all reads and writes to that variable are performed directly from the primary memory, bypassing any caches that are thread-specific. 

This ensures that any change made to the variable by one string will be promptly apparent to any remaining strings getting to it. Generally, the volatile keyword in Java provides a method for synchronizing the access and visibility of shared variables between numerous threads, making it a powerful tool for simultaneous programming and preventing inconsistencies in data or race conditions.

Volatile Keyword in Java Example

Here's an example that demonstrates the use of the volatile keyword in Java:

public class VolatileExample {
    private volatile boolean flag = false;

    public static void main(String[] args) {
        VolatileExample example = new VolatileExample();
        example.startThreads();
    }

    private void startThreads() {
        Thread writerThread = new Thread(() -> {
            try {
                Thread.sleep(1000);
                flag = true;  // Updating the volatile variable
                System.out.println("Flag is set to true.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread readerThread = new Thread(() -> {
            while (!flag) {
                // Reading the volatile variable
            }
            System.out.println("Flag is now true.");
        });

        writerThread.start();
        readerThread.start();
    }
}

In this example, we have a VolatileExample class with a private volatile boolean variable called flag. The startThreads method creates a writer thread and a reader thread.

The writer thread sleeps for 1 second and then sets the flag variable to true. Since the flag is declared as volatile, this update is immediately visible to all other threads.

The reader thread continuously checks the value of the flag variable in a loop. It keeps looping until the flag becomes true. Once it detects that the flag is true, it prints a message indicating that it has been updated.

When you run this program, you'll see that the reader thread eventually exits the loop and prints, "Flag is now true." This demonstrates the visibility guarantee provided by the volatile keyword. Without the volatile keyword, the reader thread might run indefinitely, as it may not see the updated value of the flag variable made by the writer thread.

Using the volatile keyword ensures that the changes made to the flag variable by one thread are visible to other threads, avoiding potential visibility issues and ensuring thread-safe communication.

Key Difference Between ‘Volatile’ and ‘Synchronized’ Keyword

The synchronized keyword in Java allows synchronization and mutual exclusion in environments with multiple threads. It can be applied to techniques or blocks of code to guarantee that only a single thread can execute them. With synchronized keywords, one can prevent data races and keep up with the consistency of shared assets. 

However, the volatile keyword in Java ensures that the variable's read and write operations maintain atomicity and visibility across numerous threads. Here is a table elucidating the stark differences between the two:

Parameters

Volatile

Synchronized

Purpose

Ensures visibility and atomicity of variables

Provides mutual exclusion and atomicity of code

Level of synchronization

Visibility

Mutual exclusion and atomicity

Usage

Shared variables

Shared resources, critical sections, compound operations

Provides atomicity

No

Yes

Supports compound operations

No

Yes

Mutual exclusion

No

Yes

Performance impact

Lower overhead

Higher overhead

Code complexity

Simpler

Requires explicit block or method synchronization

Example of using the synchronized keyword for comparison:

public class SynchronizedExample {
    private static int counter = 0;


    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (SynchronizedExample.class) {
                for (int i = 0; i < 1000; i++) {
                    counter++;
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (SynchronizedExample.class) {
                for (int i = 0; i < 1000; i++) {
                    counter--;
                }
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final counter value: " + counter);
    }
}

Transient Keyword in Java

Here is an example that demonstrates the use of the transient keyword in Java:

After running the above program, this person.ser file gets generated as well:

Code:

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("John", 25);

        // Serialize the object to a file
        try (FileOutputStream fileOut = new FileOutputStream("person.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut)) {

            out.writeObject(person);
            System.out.println("Object serialized successfully.");

        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize the object from the file
        try (FileInputStream fileIn = new FileInputStream("person.ser");
             ObjectInputStream in = new ObjectInputStream(fileIn)) {

            Person deserializedPerson = (Person) in.readObject();
            System.out.println("Object deserialized successfully.");

            System.out.println("Name: " + deserializedPerson.getName());
            System.out.println("Age: " + deserializedPerson.getAge());

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

In this example, we have a Person class that implements the Serializable interface, indicating that objects of this class can be serialized. The Person class has two fields: name (a String) and age (an int). However, the age field is marked as transient.

We first create a Person object named "John" and the age “25”. We then serialize the object by writing it to a file named "person.ser". When we serialize the object, the age field, being transient, is excluded from the serialization process.

Later, we deserialize the object from the file. The deserialized object is assigned to the deserializedPerson variable. When we print the name and age of the deserialized person, we'll see that the name field is successfully deserialized and displayed, while the age field will have its default value of 0 (since it was not serialized).

Using the transient keyword, we control which fields of an object are included or excluded from the serialization process. This is useful when certain fields are not required to be persisted or transferred along with the object's state.

Transient and Volatile Keyword in Java

The transient keyword in Java provides synchronization and mutual exclusion in multi-threaded environments. It ensures that only one thread can access a synchronized block of code or a synchronized method at a time, thereby preventing data races and maintaining the consistency of shared resources. 

Volatile keywords, on the other hand, do not directly relate to serialization and have no impact on the serialization process. Here is a table elucidating the stark differences between the two:

Parameters

Transient

Volatile

Purpose

Serialization control

Thread visibility and atomicity

Serialization

Excludes the variable from serialization

No direct impact on the serialization process

Thread Visibility

No guarantee of visibility across threads

Guarantees visibility of changes across threads

Atomicity

No guarantee of atomicity

Provides atomicity for simple read/write operations

Usage

Serialization

Multithreaded programming

Atomic and Volatile Keyword in Java

In Java, the Atomic classes provide atomic operations, ensuring that operations on variables are performed atomically, without interference from other threads.  

The volatile keyword does not guarantee atomicity by itself. It only ensures the visibility and order of variable access across threads. Here is a table elucidating the stark differences between the two:

Feature

Atomic Classes

Volatile Keyword

Purpose

Atomic operations and thread safety

Visibility and ordering of variable access

Atomicity

Provides atomic operations on variables

Does not guarantee atomicity by itself

Thread Safety

Ensures thread safety for operations

Ensures visibility and ordering of variable access

Use Cases

Performing atomic operations (e.g., incrementing)

Ensuring visibility of changes across multiple threads

Compound Operations

Supports compound operations with atomic guarantees

Does not provide inherent thread safety for compound operations

Synchronization

Implicitly provides synchronization through atomic operations

Does not provide mutual exclusion or synchronization

Performance Impact

Relatively higher overhead due to atomic guarantees

Lower overhead compared to atomic operations

Granularity of Control

Fine-grained control over individual variables

Applied at the variable level

Usage Complexity

More complex due to specific atomic operations

Simpler, as it is applied at the variable declaration level


Example of using the atomic keyword for comparison:

import java.util.concurrent.atomic.AtomicInteger;


public class AtomicExample {
    private static AtomicInteger counter = new AtomicInteger(0);


    public static void main(String[] args) {
        // Increment the counter atomically
        counter.incrementAndGet();


        // Retrieve the current value of the counter atomically
        int currentValue = counter.get();


        System.out.println("Current value of the counter: " + currentValue);
    }
}

Conclusion

In conclusion, the volatile keyword in Java is a valuable tool for ensuring the visibility and atomicity of variables in concurrent programming. It allows multiple threads to safely access shared variables, preventing data inconsistencies and race conditions. Understanding the nuances of “volatile” is essential for developing robust and thread-safe applications.

To master these concepts in Java, you can take up some of the comprehensive and industry-relevant courses offered by upGrad to enhance your skills and knowledge in various domains.

FAQs

1. What is the purpose of volatile keywords in Java?

The volatile keyword ensures the visibility and atomicity of variables in Java's simultaneous programming.

2. What is a volatile and transient keyword in Java?

The volatile keyword ensures visibility and atomicity of variables, while the transient keyword excludes variables from serialization.

3. Where are volatile variables stored in Java?

Volatile variables are stored in the main memory in Java.

Leave a Reply

Your email address will not be published. Required fields are marked *