HOLIDAY SALE! Save 50% on Membership with code HOLIDAY50. Save 15% on Mentorship with code HOLIDAY15.

4) Multithreading Lesson

Synchronizing Java Threads

5 min to complete · By Ryan Desmond

The synchronized keyword plays a crucial role in orchestrating the harmony of concurrent execution. Read on, and you'll learn what synchronized does, why it's essential, and how to wield its power effectively.

Understanding the Need for Synchronization

When multiple threads access shared resources simultaneously, chaos can ensue. Imagine two threads attempting to modify the same variable or data structure simultaneously – as you've seen. This scenario is what is called a "race condition". To prevent such conflicts and ensure orderly execution, we turn to the synchronized keyword.

Synchronized Methods

Here's a simple example. Suppose you have a class called Counter with a method that increments a shared counter:

public class Counter {
  private int count = 0;

  public synchronized void increment() {
    count++;
    System.out.println("Current count: " + count);
  }
}

In the example above, the increment() method is marked as synchronized. This means only one thread can execute this method at a time, preventing race conditions.

Synchronized Blocks

While synchronizing entire methods is convenient, there are situations where you want more control. Enter synchronized blocks. Consider a scenario where only part of a method needs synchronization:

public class Counter {
  private Object lock = new Object();
  private int count = 0;

  public void increment() {
      synchronized (lock) {
          // Critical section
          count++;
          System.out.println("Current count: " + count);
      }
  }
}

Here, you use a synchronized block with an explicitly defined lock (Object lock). This allows for more fine-grained control over the critical section of code.

Static Methods and Class-Level Locks

For static methods, the synchronized keyword operates at the class level, using the class itself as the lock:

public class SharedResource {
  private static int sharedVariable = 0;

  public static synchronized void staticMethod() {
      sharedVariable++;
      System.out.println("Shared variable: " 
        + sharedVariable);
  }
}

In this case, the entire static method is synchronized, preventing multiple threads from invoking it concurrently.

Synchronizing on Objects

Sometimes, you may want to synchronize based on specific objects. Here's an example with a shared resource:

public class SharedResource {
  private Object lock = new Object();
  private int sharedVariable = 0;

  public void performOperation() {
      synchronized (lock) {
          sharedVariable++;
          System.out.println("Shared variable: " 
            + sharedVariable);
      }
   }
}

By synchronizing on the lock object, you gain more control over which parts of your code are synchronized.

Deadlocks and Caution

While synchronized is a powerful tool, improper usage can lead to deadlocks – a situation where two or more threads are blocked forever. Always be cautious and ensure that your synchronized blocks are well-structured to avoid such pitfalls.

Summary: Synchronizing Java Threads

  • The synchronized keyword ensures order in multithreading scenarios.
  • Synchronization helps prevent race conditions when multiple threads access shared resources concurrently.
  • Synchronizing methods ensures only one thread can execute the synchronized method at a time.
  • Synchronized blocks provide more control over critical sections within methods.
  • Synchronizing static methods uses the class as a lock.
  • Synchronizing objects synchronizes based on specific objects for fine-grained control.
  • Ensure well-structured synchronized blocks to avoid deadlocks.