Concurrency in Java: "synchronized" and "volatile" keywords

  • 4.4/5
  • 3405
  • Jul 20, 2024

The "synchronized" keyword

The "synchronized" keyword restricts a critical section or method's access to a single thread at a time.

The synchronized keyword can only synchronize a block or a method.

A thread entering inside a synchronized block or method acquires the "lock" and releases it while leaving.

1) On an "instance" method

The "synchronized" keyword on an instance method provides object synchronisation (object-level lock). As a result, only one thread can execute within synchronised instance methods per instance at a time.

In the above code, only one thread will be able to access either of the two synchronized increment() and decrement() methods on the same instance of the "Counter" class.

Making the entire method synchronized really minimises the amount of code to be executed concurrently.

If threadA is executing increment(), threadB is blocked for executing both increment() and decrement() methods on the same instance.

2) On a code block inside an instance method

This approach is equivalent to the one discussed above (synchronized on an instance method) if the entire body of the method is written inside a synchronized block.

The benefit of using "synchronized block" in place of "synchronized method" is that there is no need to synchronize everything inside a method body.

That is, if a section of the method is synchronized and can be accessed by a single thread at a time, the code outside the block is free to be executed by any number of threads concurrently.

Instead of "this," we can also pass the actual instance of the class to the "synchronized" block, as shown in the code below:

One benefit of the above approach is that we can synchronize different blocks for different objects.

If "threadA" is executing a synchronized block in incrementBlockA() methods, "threadB" cannot execute it but can execute a synchronized block in decrementBlockA() methods concurrently on the same object.

That is because the "counter1" object has a lock on the "synchronized" block of the incrementBlockA() method only and no lock on the synchronized block of the decrementBlockA() method.

It is not recommended to synchronise on "string" and "wrapper objects" because the compiler may optimise them, resulting in the use of the same instances in places in the code where you thought you were using different instances.

3) On a "static" method

The "synchronized" keyword on a static method provides the lock on a class's "class" object (class level lock). In Java, only one "class" object exists per class; hence, at a time, only one thread can execute inside a static synchronized method.

If a class contains more than one static synchronized method and/or synchronized blocks inside of static methods, only one thread can execute inside any of these methods or blocks at the same time.

4) On a code block inside a static method

Synchronized blocks can also be used inside static methods.

If a class contains more than one static synchronized method and/or synchronized blocks inside of static methods, only one thread can execute inside any of these methods or blocks at the same time.

The "synchronized" keyword is the oldest tool in Java to provide synchronization capabilities, but it isn't very advanced.

• • •

A synchronized block only allows one thread to enter at a time. However, if a few other threads just want to read a shared value and not update it, that might be safe to allow.

Alternatively, the code can be better protected with a "Read/Write Lock" which has more advanced locking semantics than a synchronized block.

Java 5 introduced a whole new set of concurrency utility classes to help developers implement more fine-grained concurrency control code.

Reentrance

In Java, synchronized blocks are reentrant by nature, i.e., if a thread has a lock on the monitor object and another synchronized block requires that it have a lock on the same monitor object, then the thread can enter that code block.

If a thread already holds the lock on a monitor object, it has access to all blocks synchronized on the same monitor object.

In the above example, if a thread calls methodX() there is no problem calling methodY() from inside methodX(), since both methods (or blocks) are synchronized on the same monitor object ("this").

The "volatile" keyword

The "volatile" keyword guarantees that all reads of a "volatile" variable are read directly from "main memory", and all writes to a "volatile" variable are written directly to main memory.

Every read of a volatile variable will be read from the computer's main memory, and not from the CPU cache, and every write to a volatile variable will be written to main memory and not just to the CPU cache.

The volatile keyword also gives a "happens-before" guarantee to address the instruction reordering challenge. The happens-before guarantee guarantees that:

1) All instructions before the write of the volatile variable are guaranteed not to be reordered to occur after it.

2) The instructions after the read of the volatile variable cannot be reordered to occur before it.

The volatile keyword provides thread safety when only one thread writes to the volatile variable and other threads read its value (the reading threads see the latest value). Or, when multiple threads are writing to a shared variable and the operation is atomic (the new value written does not depend on the previous value).

The volatile keyword does not provide thread safety when "non-atomic" operations (increment and decrement) are performed on shared variables.

The non-atomic operations like "increment" and "decrement", internally involve three steps: reading the value, updating it, and then, writing the updated value back to memory.

Index
Modern Java - What’s new in Java 9 to Java 17

32 min

What is differences between JDK, JVM and JRE ?

2 min

What is ClassLoader in Java ?

2 min

Object Oriented Programming (OOPs) Concept

17 min

Concurrency in Java: Creating and Starting a Thread

12 min

Concurrency in Java: Interrupting and Joining Threads

5 min

Concurrency in Java: Race condition, critical section, and atomic operations

13 min

Concurrency in Java: Reentrant, Read/Write and Stamped Locks

11 min

Concurrency in Java: "synchronized" and "volatile" keywords

10 min

Concurrency in Java: using wait(), notify() and notifyAll()

6 min

Concurrency in Java: What is "Semaphore" and its use?

2 min

Concurrency in Java: CompletableFuture and its use

18 min

Concurrency in Java: Producer-consumer problem using BlockingQueue

2 min

Concurrency in Java: Producer-Consumer Problem

2 min

Concurrency in Java: Thread pools, ExecutorService & Future

14 min

Java 8 Lambdas, Functional Interface & "static" and "default" methods

28 min

Method Reference in Java (Instance, Static, and Constructor Reference)

9 min

What’s new in Java 21: A Tour of its Most Exciting Features

14 min

Java Memory Leaks & Heap Dumps (Capturing & Analysis)

9 min

Memory footprint of the JVM (Heap & Non-Heap Memory)

15 min