← Back to Multithreading Course

Class 13: Synchronization with synchronized Methods

Learn how Java helps us protect shared data when multiple threads try to update it at the same time.

1. The Problem We Saw in the Previous Class

In our previous class, we learned about race conditions. We saw how multiple threads updating the same counter could produce an incorrect result.

Let's quickly revisit that example.

class Counter {
    int count = 0;

    void increment() {
        count++;
    }
}

Imagine two threads calling the increment() method at the same time.

Both threads may read the same value, increase it, and write their results back. One update can overwrite the other.

This is how we can end up with an incorrect counter value.

So, how do we prevent this?

2. What Is Synchronization?

Synchronization is a way of controlling access to shared data when multiple threads are involved.

Think about a small room with only one key.

If one person is using the room, another person must wait until the first person comes out and returns the key.

Only one person can use the room at a time.

Java provides a similar mechanism using the synchronized keyword.

When a method is synchronized, only one thread at a time can execute that method on the same object.

3. Our First synchronized Method

Let's modify our previous Counter example.

class Counter {
    int count = 0;

    synchronized void increment() {
        count++;
    }
}

Notice the word synchronized before the method.

This small change makes a big difference.

When one thread enters the increment() method, another thread trying to enter the same synchronized method on the same Counter object must wait.

Once the first thread finishes executing the method, another thread can enter it.

In this way, the shared counter is protected from simultaneous updates through this method.

4. Understanding It with Two Threads

Let's see how two threads work with our synchronized method.

class Counter {
    int count = 0;

    synchronized void increment() {
        count++;
    }
}

public class Main {

    public static void main(String[] args)
            throws InterruptedException {

        Counter counter = new Counter();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                counter.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                counter.increment();
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(counter.count);
    }
}

Let's understand what is happening.

  • We create one Counter object.
  • Both threads use the same Counter object.
  • Each thread calls increment() 100,000 times.
  • The method is synchronized, so only one thread at a time can execute it on this object.
  • We use join() to wait for both threads to finish.

The expected output is:

200000

Every increment is now protected from another thread executing this same synchronized method on the same object at the same time.

5. What Happens When Another Thread Tries to Enter?

Every Java object has an associated monitor, which we can think of as a lock.

When a thread enters a synchronized instance method, it acquires the lock associated with that object.

If another thread tries to enter a synchronized instance method on the same object, it must wait until the lock becomes available.

Once the first thread leaves the synchronized method, it releases the lock. Another waiting thread can then acquire it.

For now, remember this simple rule:

A synchronized instance method allows only one thread at a time to execute synchronized instance methods using the same object.

6. Important: Synchronization Is Based on the Object

Let's understand one important point.

Consider this example:

Counter counter1 = new Counter();
Counter counter2 = new Counter();

We now have two different Counter objects.

Each object has its own lock.

Therefore, one thread can execute a synchronized method on counter1, while another thread can simultaneously execute a synchronized method on counter2.

They do not need to wait for each other because they are working with different objects and their respective locks.

This is an important distinction. Synchronization on one object does not automatically block access to every other object of the same class.

7. Can We Synchronize More Than One Method?

Yes. We can have multiple synchronized methods in the same class.

class BankAccount {

    private int balance = 1000;

    synchronized void deposit(int amount) {
        balance = balance + amount;
    }

    synchronized void withdraw(int amount) {
        balance = balance - amount;
    }

    synchronized int getBalance() {
        return balance;
    }
}

All three methods use the same object's lock when called on the same BankAccount object.

So, if one thread is executing deposit(), another thread calling withdraw() on that same object must wait.

This helps us protect the shared balance from concurrent access through these methods.

8. A Few Things to Remember

  • synchronized helps control access to shared data.
  • Only one thread at a time can hold a particular object's monitor lock.
  • A synchronized instance method uses the lock of the current object.
  • Different objects have different instance locks.
  • When a thread leaves a synchronized method, it releases the lock.
  • Synchronization works only when the relevant access to shared data is properly coordinated. Making one method synchronized does not automatically protect every other way of accessing that data.

Test Your Understanding

Let's see how well you understood this class!

Question 1: Why do we use the synchronized keyword?

Question 2: What happens when one thread enters a synchronized instance method?

Question 3: Two threads call the same synchronized instance method on the same object. What happens?

Question 4: Do two different Counter objects share the same instance lock?

Question 5: When is the object's lock released after a synchronized method executes?

Class Summary

Let's quickly revise what we learned in this class.

  • Synchronization helps protect shared data when multiple threads access it.
  • We use the synchronized keyword to make a method synchronized.
  • A synchronized instance method uses the lock associated with the current object.
  • Only one thread at a time can hold that object's monitor lock.
  • Different objects have different instance locks.
  • The lock is released when the thread leaves the synchronized method.

Remember: Synchronization helps us control which thread can access shared data at a particular time.

What's Next?

Class 14: Synchronization with synchronized Blocks

We have learned how to make an entire method synchronized. But what if we want to synchronize only a small portion of a method instead of the whole method?

In the next class, we will learn about synchronized blocks and how they help us control synchronization more precisely.

See you in the next class!

0% Completed

You have completed 13 out of 46 classes.