๐ŸŽ“ A1Learner
๐Ÿ”
0 of 30 lessons completed

Java Synchronized and Locks

A synchronized method lets only one thread enter it at a time. A lock gives the same kind of guard with steps the program controls by hand.

A synchronized method guards the counter

Two threads each call the synchronized increment method one thousand times. The main thread joins both threads and prints the guarded total.

public class Main {
  public static void main(String[] args) throws Exception {
    Counter counter = new Counter();
    Thread first = new Thread(new AddTask(counter));
    Thread second = new Thread(new AddTask(counter));
    first.start();
    second.start();
    first.join();
    second.join();
    System.out.println("Counter: " + counter.value);
  }
}
class Counter {
  int value;
  synchronized void increment() {
    value++;
  }
}
class AddTask implements Runnable {
  private final Counter counter;
  AddTask(Counter counter) {
    this.counter = counter;
  }
  public void run() {
    for (int i = 0; i < 1000; i++) {
      counter.increment();
    }
  }
}

A ReentrantLock guards the counter

The counter locks before each increment and unlocks in a finally block after it. Two threads add one thousand each through that guard.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
  public static void main(String[] args) throws Exception {
    LockCounter counter = new LockCounter();
    Thread first = new Thread(new AddTask(counter));
    Thread second = new Thread(new AddTask(counter));
    first.start();
    second.start();
    first.join();
    second.join();
    System.out.println("Counter: " + counter.value);
  }
}
class LockCounter {
  private final Lock lock = new ReentrantLock();
  int value;
  void increment() {
    lock.lock();
    try {
      value++;
    } finally {
      lock.unlock();
    }
  }
}
class AddTask implements Runnable {
  private final LockCounter counter;
  AddTask(LockCounter counter) {
    this.counter = counter;
  }
  public void run() {
    for (int i = 0; i < 1000; i++) {
      counter.increment();
    }
  }
}

A synchronized block guards one section

The increment method holds the guard only inside a synchronized block on a dedicated lock object. Two threads add one thousand each through that block.

public class Main {
  public static void main(String[] args) throws Exception {
    BlockCounter counter = new BlockCounter();
    Thread first = new Thread(new AddTask(counter));
    Thread second = new Thread(new AddTask(counter));
    first.start();
    second.start();
    first.join();
    second.join();
    System.out.println("Counter: " + counter.value);
  }
}
class BlockCounter {
  private final Object lock = new Object();
  int value;
  void increment() {
    synchronized (lock) {
      value++;
    }
  }
}
class AddTask implements Runnable {
  private final BlockCounter counter;
  AddTask(BlockCounter counter) {
    this.counter = counter;
  }
  public void run() {
    for (int i = 0; i < 1000; i++) {
      counter.increment();
    }
  }
}

Example

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
  public static void main(String[] args) throws Exception {
    SafeBag bag = new SafeBag();
    Thread first = new Thread(new FillTask(bag, "alpha"));
    Thread second = new Thread(new FillTask(bag, "beta"));
    first.start();
    second.start();
    first.join();
    second.join();
    System.out.println("Items: " + bag.size());
  }
}
class SafeBag {
  private final Lock lock = new ReentrantLock();
  private final List<String> items = new ArrayList<>();
  void add(String item) {
    lock.lock();
    try {
      items.add(item);
    } finally {
      lock.unlock();
    }
  }
  int size() {
    return items.size();
  }
}
class FillTask implements Runnable {
  private final SafeBag bag;
  private final String item;
  FillTask(SafeBag bag, String item) {
    this.bag = bag;
    this.item = item;
  }
  public void run() {
    for (int i = 0; i < 500; i++) {
      bag.add(item);
    }
  }
}

Key points

  • A race happens when two threads update the same value and one update overwrites the other.
  • A synchronized method guards its counter, so each increment finishes before the next one starts.
  • A guarded counter reaches its exact total because no increment is lost.
  • A ReentrantLock guards a block of code between its lock and unlock calls.
  • The unlock call sits in a finally block, so the lock is released even when the guarded code fails.
  • A synchronized block on a dedicated lock object guards only the code inside that block.

In this example, two threads each add five hundred items to one shared list through a lock. The main thread joins both threads and prints the exact size the guard protected.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

Why does the guarded counter print exactly 2000?