Skip to main content

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

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.

Comments

Popular posts from this blog

Deploying Spring Boot microservices on Kubernetes Cluster

This article guides you through the deployment of two Spring Boot microservices, namely "order-service" and "inventory-service," on Kubernetes using "MiniKube" . We will establish communication between them, with "order-service" making calls to an endpoint in "inventory-service." Additionally, we will configure "order-service" to be accessible from the local machine's browser . 1) Create Spring Boot microservices The Spring Boot microservices, "order-service" and "inventory-service," have been developed and can be found in this GitHub repository. If you are interested in learning more about creating Spring Boot REST microservices, please refer to this or this (Reactive) link. 2) Build Docker Images The Docker images for both "order-service" and "inventory-service" have already been generated and deployed on DockerHub, as shown below. codeburps/order-service cod...

Circuit Breaker Pattern with Resilience4J in a Spring Boot Application

Read Also: Spring Cloud Circuit Breaker + Resilience4j Resilience4j is a lightweight fault tolerance library that draws inspiration from Netflix Hystrix but is specifically crafted for functional programming. The library offers higher-order functions, known as decorators , designed to augment any functional interface, lambda expression, or method reference with features such as Circuit Breaker, Rate Limiter, Retry, or Bulkhead . These functionalities can be seamlessly integrated within a project, class, or even applied to a single method. It's possible to layer multiple decorators on any functional interface, lambda expression, or method reference, allowing for versatile and customizable fault tolerance. While numerous annotation-based implementations exist online, this article focuses solely on the reactive approach using router predicates and router functions . How Circuit Breaker Pattern works? In general, a circuit breaker functions as an automatic electrical s...

How to create a basic Spring 6 project using Maven

Below is a step-by-step guide to creating a basic Spring project using Maven. 1) Create a Maven Project Use the following Maven command to create a new Maven project. mvn archetype:generate -DgroupId=com.tb -DartifactId=spring-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false 2) Import in IntelliJ IDEA If you haven't already, open IntelliJ IDEA on your system. Go to "File" > "New" > "Project from Existing Sources..." . In the file dialog, navigate to the directory where your Maven project is located. Select the pom.xml file within the project directory and click "Open." 3) Update pom.xml In total, the application requires the below-mentioned dependencies: 4) Create Spring Configuration Create a Java configuration class that uses annotations to define your Spring beans and their dependencies. This class should be annotated with @Configuration . 5) Create the Main Application C...