Synchronization
Synchronization
Synchronization in Java provides the ability to regulate and coordinate the access of multiple threads to a shared resource.
It is particularly useful when the intention is to restrict access to the shared resource to only one thread at a time
Java Synchronization is employed through synchronization methods to ensure that only a single thread is allowed to access a particular resource at any given moment.
Synchronization is mainly used to JAVA
To prevent thread interference.
To prevent consistency problem.
In Java, there are two types of thread synchronization:
Process synchronization
Thread synchronization
Process synchronization pertains to managing the execution of individual programs (processes) running independently and isolated from one another within an operating system. The operating system allocates resources such as CPU time and memory to these processes.
Process synchronization ensures that these processes coordinate and communicate effectively to avoid conflicts and ensure orderly execution. It's crucial for preventing resource contention and maintaining overall system stability.
Thread synchronization, on the other hand, is a specific aspect of concurrency management that focuses on coordinating the execution of threads within a single process. Unlike processes, threads share the same memory space and resources within a process.
Thread synchronization aims to ensure that only one thread executes a particular code segment at a time, while other threads may need to wait their turn. This coordination minimizes interference between threads and helps prevent problems like data inconsistency and race conditions.
Mutual exclusion ensures that threads do not interfere with each other when accessing shared resources by preventing simultaneous access. It maintains exclusivity, allowing only one thread at a time to use a critical section, preventing conflicts and data corruption.
Inter-thread communication in Java refers to a mechanism where one thread, which is currently executing within a critical section, can be temporarily paused, allowing another thread to access or lock the same critical section. This approach enables controlled coordination between threads, ensuring that they can safely exchange data or take turns executing specific tasks within the critical section."
Mutual Exclusion ensures that threads do not disrupt each other when they need to access shared data. There are three different forms of Mutual Exclusion outlined below
Synchronized Method
Synchronized Block
Static Synchronization
A synchronized method is a method that you can designate as synchronized in your code by adding the "synchronized" keyword before the method's name. This synchronization mechanism ensures that the code within the synchronized method is executed in a thread-safe manner, meaning that multiple threads can't concurrently access or modify shared resources when this method is being executed.
Sentex:-
synchronized public void methodName() { }
Example:
class Table {
void printTable(int n) {
for (int i = 1; i <= 5; i++) {
System.out.println(n * i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
System.out.println(ie);
}
}
}
}
class Thread1 extends Thread {
Table t;
Thread1(Table t) {
this.t = t;
}
public void run() {
t.printTable(2);
}
}
class Thread2 extends Thread {
Table t;
Thread2(Table t) {
this.t = t;
}
public void run() {
t.printTable(10);
}
}
public class UnsynchronizedMethod {
public static void main(String[] args) {
Table obj = new Table();
Thread1 t1 = new Thread1(obj);
Thread2 t2 = new Thread2(obj);
t1.start();
t2.start();
}
}
Output:-
2
10
4
20
30
6
40
8
50
10
Synchronized Block in JAVA.
A synchronized block provides the capability to synchronize access to a particular resource or section of code within a method.
In situations where you have a method containing 50 lines of code but you specifically want to apply synchronization to only a portion of those lines (e.g., just 5 lines), you can make use of a synchronized block.
When we enclose the entire code within a synchronized block, it will have the same effect as using a synchronized method.
Sentax:-
synchronized (object reference)
{
// Insert code here
}
Example:-
class Table {
void printTable(int n) {
synchronized (this) { // Synchronized block using 'this' as the monitor
for (int i = 1; i <= 5; i++) {
System.out.println(n * i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
System.out.println(ie);
}
}
}
}
}
class MyThread extends Thread {
Table t;
MyThread(Table t) {
this.t = t;
}
public void run() {
t.printTable(5);
}
}
public class SynchronizedBlockExample {
public static void main(String[] args) {
Table obj = new Table();
MyThread t1 = new MyThread(obj);
MyThread t2 = new MyThread(obj);
t1.start();
t2.start();
}
}
Output:-
5
10
15
20
25
5
10
15
20
25
In this program:
We have a Table class with a printTable(int n) method that is synchronized using a synchronized block. We use the this keyword as the monitor object, meaning that only one thread can access this method for a given Table object at a time.
We create two instances of the MyThread class, t1 and t2, both of which share the same Table object obj.
Both t1 and t2 call the printTable(5) method concurrently, which prints the multiplication table for 5. Since the method is synchronized using a synchronized block, only one thread can execute it at a time for the same Table object.
Static Synchronization
When a method is declared as static in this context, it implies that the synchronization lock is applied to the entire class, not to individual objects, ensuring that only one thread can access that class's synchronized static method at a time
Synchronizing a Static Method:
public class MyClass { // Synchronized static method
public static synchronized void myStaticMethod() {
// Code that requires synchronization
}
}
Synchronizing on a Static Object:
public class MyClass { // A static object used for synchronization
private static final Object lock = new Object();
public void myMethod() {
synchronized (lock) {
// Code that requires synchronization
}
}
}
Example:-
class Counter {
private static int count = 0;
public static synchronized void increment() {
count++;
}
public static synchronized int getCount() {
return count;
}
}
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
Counter.increment();
System.out.println("Thread ID: " + Thread.currentThread().getId() + ", Count: " + Counter.getCount());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class StaticSynchronizationExample {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
Output:-
Thread ID: 11, Count: 1
Thread ID: 12, Count: 2
Thread ID: 11, Count: 3
Thread ID: 12, Count: 4
Thread ID: 11, Count: 5
Thread ID: 12, Count: 6
Thread ID: 11, Count: 7
Thread ID: 12, Count: 8
Thread ID: 11, Count: 9
Thread ID: 12, Count: 10
Synchronization revolves around an internal concept called a lock or monitor, which is associated with each object. According to convention, a thread that requires consistent access to the fields of an object must first obtain the object's lock before interacting with those fields, and subsequently release the lock when it has completed its operations