Synchronization in java

Synchronization in Java

Table of Contents

Introduction for synchronization in Java :

Synchronization in Java is a fundamental concept that ensures thread safety in multi-threaded programming. It allows developers to control the access of multiple threads to shared resources, preventing inconsistent behavior and data corruption. This blog dives into the details of synchronization in Java, its importance, and how to implement it effectively.

What is Synchronization in Java?

In Java, synchronization is the capability to control the access of multiple threads to shared resources. It ensures that only one thread can access a synchronized block or method at a time, thus maintaining data integrity.

Java’s synchronization mechanism is built around the concept of monitors. Every object in Java has an intrinsic lock (or monitor lock) that is used for synchronization. When a thread enters a synchronized block or method, it acquires the lock associated with the object. Other threads attempting to access the same synchronized block or method must wait until the lock is released.

Best Practices for Using Synchronization in Java

  • Minimize Synchronized Blocks:
    Synchronize only the critical section of code to reduce contention and improve performance. This ensures that Synchronization in Java is used efficiently without unnecessarily blocking other threads.
  • Avoid Nested Locks:
    Nested synchronized blocks can lead to complex dependencies and increase the risk of deadlocks. Use structured locking mechanisms to simplify Synchronization in Java and avoid potential issues.
  • Use High-Level Utilities:
    Prefer high-level concurrency utilities from the java.util.concurrent package over low-level synchronization when possible. These utilities simplify the implementation and reduce errors in Synchronization in Java.
  • Document Synchronization Logic:
    Clearly document the purpose and scope of synchronization in your code. Properly documenting Synchronization in Java improves maintainability and helps other developers understand the logic.
  • Test Thoroughly:
    Multi-threaded code can be challenging to debug. Use robust testing strategies, including stress tests, to identify and resolve issues with Synchronization in Java early in the development process.

Thread Synchronization in Java

Java provides built-in synchronization in Java mechanisms for managing access to shared resources. These include:

1. Synchronized Methods

The synchronized a keyword can be applied to methods to restrict access to one thread at a time.

java public synchronized void synchronizedMethod() {
    // Critical section
}

When a thread invokes a synchronized method, it acquires the intrinsic lock (monitor lock) on the object. Other threads trying to access the synchronized method on the same object are blocked until the lock is released.

In the Java platform, synchronization in Java is the capability to control access to shared resources that can be shared between threads or processes. We will talk about thread synchronization because it’s a more common task. Threads can access the same fields and the object’s references. Without correct synchronization is can cause thread interference, memory consistency errors or unexpected behavior of your application.

Let’s look at an example. It will help us to understand the problem without synchronization. Assuming that we have a user with a bank account. There are two transactions on that account with different duration. We are starting the longer transaction. Right after, we are calling the shorter one.

public class SynchronizationExample {
public static void main(String[] args){
Account account = new Account();
Thread thread1 = new Transaction(account, 3000);
Thread thread2 = new Transaction(account, 1000);
thread1.start();
thread2.start();
}
}

class Transaction extends Thread {
private int timeToWait;
private Account account;

public Transaction(Account account,int timeToWait) {
this.timeToWait = timeToWait;
this.account = account;
}

public void run() {
try {
account.performTransaction(timeToWait);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

class Account {
public void performTransaction(int timeToWait) throws InterruptedException {
System.out.println("Before transaction " + Thread.currentThread().getId());
Thread.sleep(timeToWait);
System.out.println("After transaction " + Thread.currentThread().getId());
}
}

The output of this application will be:

Before transaction 12
Before transaction 13
After transaction 13
After transaction 12

The output is inconsistent. The first transaction wasn’t finished, but the second already started. It can cause some problems for a bank client, who wants to know the real amount of money on his account after each transaction. 

Synchronization in Java is built on locks or monitors. A thread that needs consistent access has to acquire the lock before accessing them and release the lock when it’s done with them.

The Java platform allows us to do synchronization in different ways. It can be a synchronized method, synchronized block or static synchronization. 

We have to mention that constructor cannot be marked as synchronized. It will cause a syntax error because doesn’t have sense. Only the thread that creates that object should have access to its creation.Let’s mark our method performTransaction (int timeToWait) as synchronized. After this, our output will be:

Before transaction 12
After transaction 12
Before transaction 13
After transaction 13

This means that two transactions were done one after one. The first thread locked the method performTransaction (int timeToWait) until finished his work. 

Let’s use the synchronized block and get the same result:

class Account {
public void performTransaction(int timeToWait) throws InterruptedException {
synchronized (this) {
System.out.println("Before transaction " +     Thread.currentThread().getId());
Thread.sleep(timeToWait);
System.out.println("After transaction " + Thread.currentThread().getId());
}
}
}

Here we specified the object for synchronized statements. That object provides the intrinsic lock. The scope of the synchronized block is usually smaller than the synchronized method scope. We are blocking the whole object. It is better sometimes to synchronize only a part of your method.

Let’s look at a bit different example. We have a value that will be changed by two threads. The example of code without synchronization between threads:

public class SynchronizationExample {
public static void main(String[] args) throws InterruptedException {
ClientAccount account = new ClientAccount();
System.out.println("Account balance before all transactions is " +     account.getBalance());
//transaction to send 100 to account
Thread thread1 = new Transaction(account,100,3000);
//transaction to get 50 from account
Thread thread2 = new Transaction(account,-50,1000);

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

class Transaction extends Thread {
private int timeToWait;
private int value;
private ClientAccount account;

public Transaction(ClientAccount account, int value, int timeToWait) {
this.timeToWait = timeToWait;
this.value = value;
this.account = account;
}

public void run() {
try {
account.changeBalance(value, timeToWait);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

class ClientAccount {
private Integer balance = 0;
public void changeBalance(int value, int time) throws InterruptedException {
balance += value;
Thread.sleep(time);
System.out.println("Account balance after " + value + " is " + balance);
}
public int getBalance() {
return balance;
}
}

The output will be:

Account balance before all transactions is 0
Account balance after -50 is 50
Account balance after 100 is 50

Our client had a balance 0. We started two transactions. The first was to transfer to the client account 100. The second was to transfer from account 50.  The application result looks not logical: 

0-50 = 50; 

50+100 = 50;

The reason is simple. The two treads entered the method changeBalance(int value, int time). The first thread takes more time. That is why the second one raced it. Finally, we got the messy output.

We can to synchronize access of our treads to the balance value. Let’s use the synchronized block for this:

class ClientAccount {
private Integer balance = 0;

public void changeBalance(int value, int time) throws InterruptedException {
synchronized (balance) {
balance += value;
Thread.sleep(time);
System.out.println("Account balance after " + value + " is " +             balance);
}
}
...
}

Now, our output is consistent and understandable:

Account balance before all transactions is 0
Account balance after 100 is 100
Account balance after -50 is 50
The syntax of the synchronized block is:
synchronized (object reference expression) {   
  //code block   
}

It is important to note that object reference expression has to be an Object, we cannot use primitive types. That is why we used the object wrapper for int in our example. You will get a java.lang.NullPointerException, in case object reference expression of a synchronized block is null.

We can create a synchronized class. In this case, all methods of this class will be declared as synchronization in Java . For example, Vector, Stack, Hashtable, StringBuffer. They are not recommended for use. The reason is that they are slow.

Static synchronization is when you use synchronization for a static method. In this case, the all-class will be locked, not just object.

public class StaticSynchExample {
public static void main(String[] args){
Thread thread1 = new Printer(1);
Thread thread2 = new Printer(2);
thread1.start();
thread2.start();
}
}

class Printer extends Thread {
private int k;
public Printer(int k) {
this.k = k;
}
public void run() {
try {
Document.print(k);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

class Document {
private static int K = 1;
public static synchronized void print(int k) throws InterruptedException {
K = k;
System.out.print("Start");
for(int i = 0; i < 5; i++) {
System.out.print(i*K + " ");
}
System.out.println("End");
}
}

The output will be not logical. The threads accessed and changed our coefficient randomly:

Start Start 0 0 2 4 6 8 End
2 4 6 8 End

After marking our print(int k) method as synchronized, we will get consistent result:

Start 0 1 2 3 4 End
Start 0 2 4 6 8 End

Let’s summarize the important things to know about the synchronized keyword. Synchronization guarantees that two threads cannot execute a synchronized method with the same lock simultaneously or concurrently. The synchronized keyword can be used for methods or blocks (synchronized statements). When a thread enters a synchronized part of code, the code acquires a lock. This lock exists until the thread leaves that code or an error/exception occurs.

A thread acquires an object level lock when enters an instance synchronized method. A thread acquires a class level lock when enters a static synchronized method. 

The synchronized keyword is re-entrant. Let’s explain it. A thread that holds a lock on one synchronized method does not need to acquire a lock for a synchronized method called from the first method.

There is one main disadvantage of Java synchronized keyword. It doesn’t allow concurrent read. This limits scalability. You can use ReentrantReadWriteLock for this use case. It is from package java.util.concurrent.locks. It provides a ready implementation of the ReadWriteLock in Java.

The synchronized keyword can control access to a shared object just within the same JVM.

A synchronized method in Java is very slow. They degrade performance. So, it better to use blocks for critical sections of code.

Your synchronized code could cause a java deadlock or starvation while accessing by multiple threads if synchronization is not implemented correctly.

It’s not good to use a non-final field or String on the synchronized block. A string is an immutable object. A literal string and interned string gets stored in the String pool. Another thread can be locked on the same object despite being completely unrelated. It can cause unexpected behavior.

Besides the synchronized keyword,  we can also use explicit locks. The Java language includes the Lock interface since version 1.5. There are different implementations of this interface (ReentrantLock, ReadWriteLock, StampedLock, etc ). Let’s take a look at the simplest ReentrantLock, just to replace our synchronized keyword from our first example. We will replace this part:

class Account {
public void performTransaction(int timeToWait) throws InterruptedException {
synchronized (this) {
System.out.println("Before transaction " +     Thread.currentThread().getId());
Thread.sleep(timeToWait);
System.out.println("After transaction " + Thread.currentThread().getId());
}
}
}

This code work the same as:

class Account {
private ReentrantLock lock = new ReentrantLock();
public void performTransaction(int timeToWait) throws InterruptedException {
lock.lock();
System.out.println("Before transaction " +   Thread.currentThread().getId());
Thread.sleep(timeToWait);
System.out.println("After transaction " + Thread.currentThread().getId());
lock.unlock();
}
}

One Response

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Share this article
Subscribe
By pressing the Subscribe button, you confirm that you have read our Privacy Policy.
Need a Free Demo Class?
Join H2K Infosys IT Online Training
Enroll Free demo class