Java tutorials > Multithreading and Concurrency > Threads and Synchronization > How to synchronize access to shared resources (`synchronized`)?
How to synchronize access to shared resources (`synchronized`)?
This tutorial explains how to synchronize access to shared resources in Java using the `synchronized` keyword. Synchronization is crucial for preventing race conditions and ensuring data integrity when multiple threads access the same data.
Introduction to Synchronization
In a multithreaded environment, multiple threads can execute concurrently. If these threads access shared resources (e.g., variables, files, databases) without proper coordination, it can lead to data corruption and inconsistent results. This is known as a race condition. Synchronization mechanisms, such as the `synchronized` keyword in Java, provide a way to control access to shared resources and prevent these issues.
The `synchronized` Keyword
The `synchronized` keyword in Java is used to create critical sections in your code. A critical section is a block of code that can only be executed by one thread at a time. When a thread enters a `synchronized` block or method, it acquires a lock associated with the object or class being synchronized on. Other threads attempting to enter the same `synchronized` block or method will be blocked until the lock is released.
Synchronized Methods
Synchronizing a method ensures that only one thread can execute that method at a time. In this example, the `increment()` and `getCount()` methods of the `Counter` class are synchronized. This means that if one thread is executing `increment()`, no other thread can execute `increment()` or `getCount()` until the first thread has finished executing `increment()`. When a method is declared `synchronized`, the lock associated with the object (instance of `Counter` in this case) is acquired when the method is called and released when the method returns.
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
Synchronized Blocks
Synchronized blocks provide finer-grained control over synchronization. You can specify the object to lock on using the `synchronized` keyword followed by parentheses containing the object reference. Only one thread can execute the code within the `synchronized` block at a time, while other threads are blocked waiting for the lock to be released. In this example, a separate `lock` object is used for synchronization. This is useful when you want to synchronize access to specific parts of a method rather than the entire method. Using a separate lock object can improve performance compared to synchronizing the entire method, especially if the method contains sections of code that don't require synchronization. The object specified in the `synchronized` block is crucial. If multiple blocks synchronize on different objects, they will not block each other.
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
Concepts Behind the Snippet
The core concept behind `synchronized` is mutual exclusion. Mutual exclusion ensures that only one thread can access a shared resource at any given time, preventing race conditions and data corruption. The `synchronized` keyword achieves mutual exclusion by using locks. Each Java object has an associated lock (also known as a monitor). When a thread enters a `synchronized` block or method, it must acquire the lock associated with the object or class. If the lock is already held by another thread, the thread will block until the lock is released.
Real-Life Use Case Section
Consider a bank account where multiple threads (e.g., representing different users or transactions) might try to deposit or withdraw money simultaneously. Without synchronization, it's possible for two threads to read the current balance, both add their respective amounts, and then write the result back, leading to an incorrect balance. Using `synchronized` methods or blocks ensures that only one transaction updates the balance at a time, maintaining data integrity.
Best Practices
Interview Tip
When discussing synchronization in interviews, be prepared to explain the concepts of race conditions, mutual exclusion, and the role of locks. Demonstrate your understanding of the `synchronized` keyword and its use in both methods and blocks. Be ready to discuss the trade-offs between using `synchronized` and other concurrency mechanisms. You should also understand the potential for deadlocks and how to avoid them.
When to Use Them
Use `synchronized` when you need to ensure that only one thread accesses a shared resource at a time to prevent data corruption or race conditions. It is especially useful for simple synchronization needs within a single object. However, for more complex concurrency requirements, consider using the classes in `java.util.concurrent`.
Memory Footprint
The `synchronized` keyword has a relatively small memory footprint. Each Java object has an associated monitor (lock), but this monitor only consumes memory when it's actively being used (i.e., when a thread holds the lock). The overhead is typically minimal. However, excessive synchronization can impact performance by introducing contention and blocking threads.
Alternatives
Alternatives to `synchronized` include:
Pros
Cons
FAQ
-
What happens if I try to synchronize on `null`?
Attempting to synchronize on `null` will throw a `NullPointerException`. -
Is it possible to synchronize on a primitive type?
No, you cannot synchronize on a primitive type directly. You can synchronize on the corresponding wrapper object (e.g., `Integer`, `Boolean`). However, be aware that if multiple threads use the same boxed `Integer` object (e.g., due to autoboxing of the same integer value), they might inadvertently synchronize on the same object when they didn't intend to. -
What is the difference between `synchronized` and `ReentrantLock`?
`synchronized` is a built-in language feature that provides basic locking functionality. `ReentrantLock` is a class in the `java.util.concurrent` package that offers more flexibility and control, such as fairness, the ability to interrupt waiting threads, and the ability to try to acquire a lock without blocking.