Fatskills
Practice. Master. Repeat.
Study Guide: Java Multithreading Synchronisation synchronized Keyword Locks
Source: https://www.fatskills.com/java-programming/chapter/java-multithreading-synchronisation-synchronized-keyword-locks

Java Multithreading Synchronisation synchronized Keyword Locks

By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.

⏱️ ~5 min read

What This Is and Why It Matters

Synchronization is a crucial concept in Java concurrency, involving the coordination of multiple threads to access shared resources without causing inconsistencies. The synchronized keyword and locks are fundamental tools for achieving this. In real-world applications, improper synchronization can lead to data corruption, deadlocks, and unpredictable behavior, severely impacting system reliability. For exam candidates, mastering synchronization is essential as it often constitutes a significant portion of concurrency-related questions. For instance, a banking application without proper synchronization could result in incorrect account balances, leading to financial discrepancies.

Core Knowledge (What You Must Internalize)

  • Synchronized Keyword: Used to control access to a block of code or a method, ensuring only one thread can execute it at a time. (Why this matters: Prevents race conditions and data inconsistencies.)
  • Intrinsic Locks: Every object in Java has an intrinsic lock associated with it. (Why this matters: Understanding this helps in managing thread access to shared resources.)
  • Reentrant Locks: Part of the java.util.concurrent.locks package, offering more flexibility than synchronized methods. (Why this matters: Allows for more complex locking scenarios and better performance.)
  • Deadlock: A situation where two or more threads are waiting for each other to release resources. (Why this matters: Can freeze the application, making it unresponsive.)
  • Lock Interface: Provides more extensive locking operations than synchronized methods. (Why this matters: Useful for advanced synchronization needs.)

Step‑by‑Step Deep Dive

  1. Understand the Synchronized Keyword
  2. Action: Use the synchronized keyword to make a method or block of code thread-safe.
  3. Principle: Only one thread can execute a synchronized method or block at a time.
  4. Example:
    java
    public synchronized void safeMethod() {
    // Thread-safe code
    }
  5. ⚠️ Pitfall: Overusing synchronized can lead to performance bottlenecks.

  6. Intrinsic Locks and Synchronized Blocks

  7. Action: Use synchronized blocks for finer control over locking.
  8. Principle: Locks are acquired on the object specified in the synchronized block.
  9. Example:
    java
    public void safeBlock() {
    synchronized (this) {
    // Thread-safe code
    }
    }
  10. ⚠️ Pitfall: Always verify the object being locked to avoid unintended behavior.

  11. Reentrant Locks

  12. Action: Use ReentrantLock for more control over locking.
  13. Principle: Allows for interruptible locks, try locks, and lock polling.
  14. Example:
    java
    ReentrantLock lock = new ReentrantLock();
    lock.lock();
    try {
    // Thread-safe code
    } finally {
    lock.unlock();
    }
  15. ⚠️ Pitfall: Always unlock in a finally block to prevent deadlocks.

  16. Avoiding Deadlocks

  17. Action: Design code to avoid circular wait conditions.
  18. Principle: Ensure threads acquire locks in a consistent order.
  19. Example:
    java
    lock1.lock();
    lock2.lock();
    try {
    // Thread-safe code
    } finally {
    lock2.unlock();
    lock1.unlock();
    }
  20. ⚠️ Pitfall: Inconsistent lock ordering can lead to deadlocks.

How Experts Think About This Topic

Experts view synchronization as a balance between safety and performance. They focus on minimizing the scope of synchronized blocks and using advanced locking mechanisms only when necessary. Instead of relying solely on the synchronized keyword, they leverage ReentrantLock and other concurrency utilities to fine-tune thread behavior.

Common Mistakes (Even Smart People Make)

  1. The mistake: Using synchronized on non-final methods.
  2. Why it's wrong: Subclasses can override and break synchronization.
  3. How to avoid: Use private or final methods for synchronization.
  4. Exam trap: Questions involving subclassing and synchronization.

  5. The mistake: Not using the same lock object.

  6. Why it's wrong: Different lock objects lead to uncoordinated access.
  7. How to avoid: Always use the same lock object for related synchronized blocks.
  8. Exam trap: Scenarios with multiple lock objects.

  9. The mistake: Forgetting to unlock in a finally block.

  10. Why it's wrong: Can cause deadlocks if an exception occurs.
  11. How to avoid: Always unlock in a finally block.
  12. Exam trap: Code snippets without proper unlocking.

  13. The mistake: Overusing synchronized blocks.

  14. Why it's wrong: Leads to performance bottlenecks.
  15. How to avoid: Minimize the scope of synchronized blocks.
  16. Exam trap: Questions about performance optimization.

Practice with Real Scenarios

Scenario: A banking application where multiple threads update account balances.
Question: How to make the balance update method thread-safe? Solution: 1. Use the synchronized keyword on the method.
2. Alternatively, use a ReentrantLock.
Answer:


public class Account {
private int balance;
private final ReentrantLock lock = new ReentrantLock();
public void updateBalance(int amount) {
lock.lock();
try {
balance += amount;
} finally {
lock.unlock();
}
} }

Why it works: The ReentrantLock ensures that only one thread can update the balance at a time, preventing race conditions.

Scenario: Two threads need to access a shared resource in a specific order.
Question: How to avoid deadlocks? Solution: 1. Acquire locks in a consistent order.
2. Use tryLock to avoid waiting indefinitely.
Answer:


ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();

public void safeMethod() {
while (true) {
boolean gotLock1 = lock1.tryLock();
boolean gotLock2 = lock2.tryLock();
if (gotLock1 && gotLock2) {
try {
// Thread-safe code
} finally {
lock2.unlock();
lock1.unlock();
}
return;
}
if (gotLock1) lock1.unlock();
if (gotLock2) lock2.unlock();
} }

Why it works: The tryLock method prevents threads from waiting indefinitely, reducing the risk of deadlocks.

Quick Reference Card

  • Core rule: Use synchronized or ReentrantLock to control thread access to shared resources.
  • Key formula: lock.lock(); try { ... } finally { lock.unlock(); }
  • Critical facts:
  • Synchronized methods are inherently thread-safe.
  • Always unlock in a finally block.
  • Avoid circular wait conditions.
  • Dangerous pitfall: Inconsistent lock ordering.
  • Mnemonic: "Lock, Try, Finally" (LTF) for safe locking.

If You're Stuck (Exam or Real Life)

  • What to check first: Verify the scope of synchronized blocks and the order of lock acquisition.
  • How to reason from first principles: Think about the critical sections and the shared resources.
  • When to use estimation: Estimate the likelihood of race conditions based on the number of threads and shared resources.
  • Where to find the answer: Refer to the Java Concurrency API documentation and examples.

Related Topics

  • Thread Pools: Understand how to manage a pool of worker threads efficiently.
  • Concurrency Utilities: Learn about other utilities like CountDownLatch and CyclicBarrier for advanced synchronization.


ADVERTISEMENT