Synchronization in java

Synchronization in Java is a mechanism that ensures that only one thread can access shared resources at a time. It helps prevent race conditions and data inconsistencies that may arise when multiple threads concurrently access and modify shared data. In Java, synchronization is primarily achieved using the synchronized keyword and the concepts of locks and monitors.

Key Concepts:

  1. synchronized Keyword:
  • The synchronized keyword is used to define a critical section of code, known as a synchronized block or method. Only one thread can execute a synchronized block or method at a time.

Synchronized Block:

// Synchronized block using an object lock
synchronized (lockObject) {
    // Critical section code
}

Synchronized Method:

// Synchronized method
public synchronized void synchronizedMethod() {
    // Critical section code
}
  1. Object Locks:
  • When a thread enters a synchronized block or method, it acquires the lock associated with the specified object or the object on which the method is called. Other threads attempting to enter synchronized blocks or methods on the same object must wait until the lock is released.
  1. Class-Level Locks:
  • For static synchronized methods or synchronized blocks, the lock acquired is on the class itself rather than an instance of the class.
// Synchronized static method
public static synchronized void synchronizedStaticMethod() {
    // Critical section code
}
  1. Deadlock:
  • Care must be taken to avoid deadlock, a situation where two or more threads are blocked indefinitely, each waiting for the other to release a lock.

Example of Synchronization:

class SharedResource {
    private int counter = 0;

    // Synchronized method
    public synchronized void increment() {
        counter++;
    }

    public int getCounter() {
        return counter;
    }
}

class MyThread extends Thread {
    private SharedResource sharedResource;

    public MyThread(SharedResource sharedResource) {
        this.sharedResource = sharedResource;
    }

    public void run() {
        for (int i = 0; i < 10000; i++) {
            sharedResource.increment();
        }
    }
}

public class SynchronizationExample {
    public static void main(String[] args) throws InterruptedException {
        SharedResource sharedResource = new SharedResource();

        MyThread thread1 = new MyThread(sharedResource);
        MyThread thread2 = new MyThread(sharedResource);

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

        thread1.join();
        thread2.join();

        System.out.println("Counter value: " + sharedResource.getCounter());
    }
}

In this example, two threads (thread1 and thread2) increment a counter within a synchronized method (increment). Synchronization ensures that the counter is updated atomically, preventing race conditions.

Best Practices for Synchronization:

  1. Use synchronization sparingly:
  • Only synchronize critical sections to avoid unnecessary contention.
  1. Minimize code within synchronized blocks:
  • Keep synchronized blocks or methods as short as possible to reduce the time during which the lock is held.
  1. Avoid holding locks during I/O operations:
  • Long-running I/O operations within synchronized blocks can lead to performance issues.
  1. Consider using higher-level concurrency utilities:
  • Classes from the java.util.concurrent package, such as Lock and Semaphore, provide more flexible synchronization mechanisms.
  1. Use thread-safe collections:
  • Utilize thread-safe collections from the java.util.concurrent package to avoid the need for explicit synchronization.

Synchronization is a powerful tool for managing concurrent access to shared resources, but it should be used judiciously to balance safety and performance considerations. Careful design and testing are crucial to avoiding issues such as deadlock and contention.

When two or more threads need access to a shared resource, they need some way to ensure that the resource will be used by only one thread at a time. The process by which this is achieved is called synchronization

Key to synchronization is the concept of the monitor (also called a semaphore)

A monitor is an object that is used as a mutually exclusive lock, or mutex. Only one thread can own a monitor at a given time. When a thread acquires a lock, it is said to have entered the monitor. All other threads attempting to enter the locked monitor will be suspended until the first thread exits the monitor. These other threads are said to be waiting for the monitor. A thread that owns a monitor can reenter the same monitor if it so desires

Using Synchronized Methods

Example of not synchronized program

class Callme {
void call(String msg) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println("]");
}
}
class Caller implements Runnable {
String msg;
Callme target;
Thread t;
public Caller(Callme targ, String s) {
target = targ;
msg = s;
t = new Thread(this);
t.start();
}
public void run() {
target.call(msg);
}
}
class Synch {
public static void main(String args[]) {
Callme target = new Callme();
Caller ob1 = new Caller(target, "Hello");
Caller ob2 = new Caller(target, "Synchronized");
Caller ob3 = new Caller(target, "World");
// wait for threads to end
try {
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
}
}

output produced by this program

[Synchronized[Hello[World]
]
]

The synchronized Statement

While creating synchronized methods within classes that we create is an easy and effective means of achieving synchronization, it will not work in all cases. To understand why, consider the following. Imagine that you want to synchronize access to objects of a class that was not designed for multithreaded access. That is, the class does not use synchronized methods. Further, this class was not created by we, but by a third party, and we do not have access to the source code. Thus, we can’t add synchronized to the appropriate methods within the class. How can access to an object of this class be synchronized? Fortunately, the solution to this problem is quite easy: we simply put calls to the methods defined by this class inside a synchronized block.

This is the general form of the synchronized statement

synchronized(object) {
// statements to be synchronized
}

Example of synchronized programme

class Callme {
void call(String msg) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println("]");
}
}
class Caller implements Runnable {
String msg;
Callme target;
Thread t;
public Caller(Callme targ, String s) {
target = targ;
msg = s;
t = new Thread(this);
t.start();
}
// synchronize calls to call()
public void run() {
synchronized(target) { // synchronized block
target.call(msg);
}
}
}
class Synch1 {
public static void main(String args[]) {
Callme target = new Callme();
Caller ob1 = new Caller(target, "Hello");
Caller ob2 = new Caller(target, "Synchronized");
Caller ob3 = new Caller(target, "World");
// wait for threads to end
try {
ob1.t.join();
ob2.t.join();
ob3.t.join();
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
}
}

Out of this synchronized programme

[Hello]
[Synchronized]
[World]

Leave a Comment