[go: up one dir, main page]

0% found this document useful (0 votes)
7 views8 pages

Synchronization in Java: // Synchronized Code Block

Synchronization in Java is essential for safe interaction among multiple threads accessing shared resources, preventing race conditions and ensuring data integrity. It can be achieved through synchronized methods and blocks, with intrinsic locks managing access to resources. While synchronization enhances thread safety and coordination, it can introduce performance overhead, deadlocks, and complexity in multi-threaded applications.

Uploaded by

gvnbca
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views8 pages

Synchronization in Java: // Synchronized Code Block

Synchronization in Java is essential for safe interaction among multiple threads accessing shared resources, preventing race conditions and ensuring data integrity. It can be achieved through synchronized methods and blocks, with intrinsic locks managing access to resources. While synchronization enhances thread safety and coordination, it can introduce performance overhead, deadlocks, and complexity in multi-threaded applications.

Uploaded by

gvnbca
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 8

Synchronization in Java

Synchronization in Java is a critical concept in concurrent programming that ensures multiple


threads can interact with shared resources safely. In a nutshell, synchronization prevents race
conditions, where the outcome of operations depends on the timing of thread execution. It is the
capability to control the access of multiple threads to any shared resource. Synchronization is a
better option where we want to allow only one thread to access the shared resource.

Understanding Threads and Shared Resources

Thread represents an independent path of execution within a program. When multiple threads
access shared resources concurrently, problems may arise due to unpredictable interleaving of
operations. Consider a scenario where two threads increment a shared variable concurrently:

class Counter {
private int count = 0;
public void increment() {
count++;
}
}
If two threads execute increment() simultaneously, they might read the current value of count,
increment it, and write it back concurrently. This can result in lost updates or incorrect final values
due to race conditions.

Introducing Synchronization

Synchronization in Java tackles these problems through the capacity of a single thread to have
exclusive access to either a synchronized block of code or a synchronized method associated with an
object in question at a time. There are two primary mechanisms for synchronization in Java:
synchronized methods and synchronized blocks.

Synchronized Methods

In Java, you can declare entire methods as synchronized which prevent multiple threads from
accessing the method simultaneously. With this, synchronization becomes a simpler process because
the mechanism is applied to all invocations of the synchronized method automatically.

Example: Synchronized Counter

class SynchronizedCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
With this modification, concurrent calls to increment() or getCount() will be synchronized,
preventing race conditions.

Synchronized Blocks

Synchronized block provides exclusive access to shared resources, and only one thread is allowed to
execute it in the same time frame. It's structured as follows:

synchronized (object) {
// Synchronized code block
}
This monitor object or lock is the subject. While only one thread can be holding a lock on a monitor
object at one instance. Other threads that want to go into the synchronized blocks with this object
must wait till the lock becomes available.
Intrinsic Locks and Synchronization

In Java, every object automatically has an intrinsic lock (or monitor lock) associated to it. The
moment a thread enters the synchronized block or method, it gets the lock for the object, and then
no other thread is allowed to enter the synchronized block or method for that object until the lock is
released.

Deadlocks

On the one hand, synchronization ensures that race condition is ruled out, but on the other hand, it
may lead to deadlocks if not used critically. Stalemates could result as two or more threads are
ceaseless when they are waiting for resources from each other. Avoid deadlocks by ordering the
locks and releasing them in an opposite sequence of both.

Locking Granularity

The best locking granularity has to be selected and has to avoid contention and thus be good for
performance. Leaning too broadly can reduce concurrency, while leaning too finely can lead to
overhead increase. Identification of the only section of the code which needs to be the only one
having an exclusive access to the shared resources and synchronization of that section alone.

Concurrent Collections

Java contains a thread-safe versions of the common collections classes that found in the
java.util.concurrent package, including the ConcurrentHashMap and ConcurrentLinkedQueue. These
classes provide internal synchronization mechanisms that guarantee the thread safety without giving
up the synchronization control to the user.

Volatile Keyword

Furthermore, volatile keyword may be used to maintain the visibility of changes made to the
variables among the threads. For variables declared as volatile, their value will always be read
directly from memory and the writes to them will be visible to all the other threads immediately.
However, volatile does not ensure as such for complex instructions such as incrementing.

Atomic Classes

Java gives atomic classes in the java.util.concurrent.atomic package including Atomic Integer and
Atomic Long, which offer atomic operations of variables without using explicit synchronization.
These kind of classes use hardware's low level atomic operations to make thread safety.

Why use Synchronization?

The synchronization is mainly used to

1. thread interference, and


2. Consistency problem
Types of Synchronization

There are the following two types of synchronization:

1. Process Synchronization
2. Thread Synchronization
Here, we will discuss only thread synchronization.

Thread Synchronization

There are two types of thread synchronization in Java: mutual exclusive and inter-thread
communication.

1. Mutual Exclusive

1. Synchronized method.
2. Synchronized block.
3. Static synchronization.
2. Cooperation (Inter-thread communication in Java)
Mutual Exclusive

Mutual Exclusive helps keep threads from interfering with one another while sharing data. It can be
achieved by using the following three ways:

1. By Using Synchronized Method


2. By Using Synchronized Block
3. By Using Static Synchronization
Concept of Lock in Java

Synchronization is built around an internal entity known as the lock or monitor. Every object has a
lock associated with it. By convention, a thread that needs consistent access to an object's fields has
to acquire the object's lock before accessing them, and then release the lock when it's done with
them.

From Java 5 the package java.util.concurrent.locks contains several lock implementations.

Understanding the Problem without Synchronization

In this example, there is no synchronization, so output is inconsistent. Let's see the example:

Example

class Table {
// Method to print the table, not synchronized
void printTable(int n) {
for(int i = 1; i <= 5; i++) {
// Print the multiplication result
System.out.println(n * i);
try {
// Pause execution for 400 milliseconds
Thread.sleep(400);
} catch(Exception e) {
// Handle any exceptions
System.out.println(e);
}
}
}
}
class MyThread1 extends Thread {
Table t;
// Constructor to initialize Table object
MyThread1(Table t) {
this.t = t;
}
// Run method to execute thread
public void run() {
// Call printTable method with argument 5
t.printTable(5);
}
}
class MyThread2 extends Thread {
Table t;
// Constructor to initialize Table object
MyThread2(Table t) {
this.t = t;
}
// Run method to execute thread
public void run() {
// Call printTable method with argument 100
t.printTable(100);
}
}
public class Main {
public static void main(String args[]) {
// Create a Table object
Table obj = new Table();
// Create MyThread1 and MyThread2 objects with the same Table object
MyThread1 t1 = new MyThread1(obj);
MyThread2 t2 = new MyThread2(obj);
// Start both threads
t1.start();
t2.start();
}
}
Output:

100

10

200

15

300

20

400

25

500

Java Synchronized Method

If you declare any method as synchronized, it is known as synchronized method.

Synchronized method is used to lock an object for any shared resource.

When a thread invokes a synchronized method, it automatically acquires the lock for that object and
releases it when the thread completes its task.

Example

class Table {
// Synchronized method to print the table
synchronized void printTable(int n) {
for(int i = 1; i <= 5; i++) {
// Print the multiplication result
System.out.println(n * i);
try {
// Pause execution for 400 milliseconds
Thread.sleep(400);
} catch(Exception e) {
// Handle any exceptions
System.out.println(e);
}
}
}
}
class MyThread1 extends Thread {
Table t;
// Constructor to initialize Table object
MyThread1(Table t) {
this.t = t;
}
// Run method to execute thread
public void run() {
// Call synchronized method printTable with argument 5
t.printTable(5);
}
}
class MyThread2 extends Thread {
Table t;
// Constructor to initialize Table object
MyThread2(Table t) {
this.t = t;
}
// Run method to execute thread
public void run() {
// Call synchronized method printTable with argument 100
t.printTable(100);
}
}
public class Main {
public static void main(String args[]) {
// Create a Table object
Table obj = new Table();
// Create MyThread1 and MyThread2 objects with the same Table object
MyThread1 t1 = new MyThread1(obj);
MyThread2 t2 = new MyThread2(obj);
// Start both threads
t1.start();
t2.start();
}
}
Compile and Run

Output:

10

15

20

25

100

200

300

400

500
Example of Synchronized Method by Using Anonymous Class

In this program, we have created the two threads by using the anonymous class, so less coding is
required.

Example

// Program of synchronized method by using anonymous class


class Table {
// Synchronized method to print the table
synchronized void printTable(int n) {
for(int i = 1; i <= 5; i++) {
// Print the multiplication result
System.out.println(n * i);
try {
// Pause execution for 400 milliseconds
Thread.sleep(400);
} catch(Exception e) {
// Handle any exceptions
System.out.println(e);
}
}
}
}
public class Main {
public static void main(String args[]) {
// Create a Table object
final Table obj = new Table(); // Only one object
// Create thread t1 using anonymous class
Thread t1 = new Thread() {
public void run() {
// Call synchronized method printTable with argument 5
obj.printTable(5);
}
};
// Create thread t2 using anonymous class
Thread t2 = new Thread() {
public void run() {
// Call synchronized method printTable with argument 100
obj.printTable(100);
}
};
// Start both threads
t1.start();
t2.start();
}
}
Output:

10

15

20

25

100
200

300

400

500

Advantages of Synchronization in Java

Thread Safety: Synchronization ensures that shared resources are accessed by only one thread at a
time, preventing race conditions and maintaining data integrity. This makes it easier to write multi-
threaded programs without worrying about unpredictable behaviors caused by concurrent access.

Consistency: By using synchronization, you can ensure that concurrent operations on shared
resources are performed in a consistent and predictable manner. This is crucial for maintaining the
correctness of the program's logic and preventing unexpected outcomes.

Data Visibility: Synchronization mechanisms such as locks and memory barriers guarantee that
changes made by one thread to shared variables are visible to other threads. This ensures that
threads always see the most up-to-date values of shared data, preventing inconsistencies due to
stale data.

Prevention of Deadlocks: Synchronization provides tools to prevent deadlocks, a situation where


two or more threads are blocked indefinitely, waiting for each other to release resources. By
following best practices such as acquiring locks in a consistent order and using timeouts, you can
minimize the risk of deadlocks in your multi-threaded applications.

Coordination: Synchronization facilitates coordination and communication between threads by


allowing them to wait for certain conditions to be met before proceeding. This enables the
implementation of synchronization primitives such as semaphores, mutexes, and barriers, which are
essential for designing complex multi-threaded algorithms.

Efficient Resource Utilization: While synchronization adds overhead to multi-threaded programs


due to locking and context switching, it enables efficient utilization of shared resources such as CPU
time, memory, and I/O devices. By allowing multiple threads to work cooperatively without
interfering with each other, synchronization maximizes the throughput and responsiveness of the
application.

Compatibility with Legacy Code: Synchronization is a fundamental concept in Java concurrency that
has been widely adopted in libraries, frameworks, and existing codebases. By leveraging
synchronization, developers can ensure compatibility with legacy code and libraries that rely on
thread-safe programming practices.

Disadvantages of Synchronization in Java

Performance Overhead: Synchronization involves acquiring and releasing locks, which introduces
overhead due to context switching and contention for shared resources. This can degrade
performance, especially in highly concurrent applications where many threads contend for the same
locks.

Potential for Deadlocks: Incorrect use of synchronization primitives can lead to deadlocks, where
threads are blocked indefinitely, waiting for each other to release locks. Deadlocks are challenging to
debug and can cause the entire application to hang, impacting its availability and reliability.

Reduced Scalability: Synchronization can limit the scalability of multi-threaded applications by


introducing bottlenecks. When multiple threads contend for the same locks, they may spend
significant time waiting, reducing overall throughput and scalability, particularly on systems with
many CPU cores.

Complexity and Maintenance: Synchronized code can be more complex and error-prone than
single-threaded or lock-free alternatives. Managing locks, ensuring proper lock acquisition and
release, and avoiding deadlocks require careful design and testing, increasing the complexity and
maintenance burden of the codebase.
Potential for Livelocks: Livelocks are similar to deadlocks but occur when threads continuously
change their states in response to each other, preventing any of them from making progress.
Livelocks can occur when threads repeatedly acquire and release locks in a specific pattern without
making progress toward resolving the contention.

Difficulty in Debugging: Synchronization-related issues such as race conditions, deadlocks, and


livelocks can be challenging to debug, especially in complex multi-threaded applications. These
issues may occur sporadically and may not be reproducible in controlled environments, making them
hard to diagnose and fix.

Decreased Concurrency: Overuse of synchronization can lead to decreased concurrency, as threads


may spend more time waiting for locks than performing useful work. Fine-grained locking can
mitigate this issue but increases complexity and may introduce additional overhead.

Potential for Performance Degradation with I/O Operations: Synchronization may lead to
performance degradation when threads block on I/O operations while holding locks. This can cause
other threads waiting for the same locks to be blocked unnecessarily, reducing overall throughput
and responsiveness.

You might also like