synchronized Keyword in Java
The synchronized
keyword in Java controls access to shared resources among multiple threads. It guarantees that only one thread can executed a synchronized block or method at a time, preventing thread interference and ensuring memory consistency.
Usage
The synchronized
keyword can be applied to methods or blocks within methods. When a method or block is declared as synchronized, a lock is obtained on the specified object, and other threads are blocked from accessing the synchronized code until the lock is released.
Syntax
Synchronized Method
public synchronized void synchronizedMethod() {
// method code
}
Synchronized Block
public void method() {
synchronized (object) {
// synchronized code
}
}
object
: The object on which to synchronize. This is typically the current instance (this
) or a specific object.
Examples
Example 1: Synchronized Method
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
public static void main(String[] args) {
Counter counter = new Counter();
// Create multiple threads to increment the counter
Thread t1 = new Thread(() -> counter.increment());
Thread t2 = new Thread(() -> counter.increment());
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount());
}
}
In this example, the increment
method is synchronized, ensuring that only one thread can increment the counter at a time, preventing race conditions.
Example 2: Synchronized Block
public class SynchronizedBlockExample {
private final Object lock = new Object();
private int count = 0;
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
return count;
}
public static void main(String[] args) {
SynchronizedBlockExample example = new SynchronizedBlockExample();
// Create multiple threads to increment the counter
Thread t1 = new Thread(() -> example.increment());
Thread t2 = new Thread(() -> example.increment());
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + example.getCount());
}
}
This example uses a synchronized block to control access to the count
variable. The lock object ensures that only one thread can execute the synchronized block at a time.
Tips and Best Practices
- Minimize Synchronized Code: Keep synchronized blocks as short as possible to reduce contention and improve performance.
- Use Final Objects: When using synchronized blocks, use final objects as locks to ensure consistency.
- Avoid Nested Synchronization: Nested synchronized blocks can lead to deadlocks. Avoid them or use them with caution.
- Use Concurrent Collections: For complex synchronization, consider using concurrent collections from the
java.util.concurrent
package, such asConcurrentHashMap
, which provide built-in synchronization. - Volatile Keyword: For variables that are accessed by multiple threads but do not require complex synchronization, consider using the
volatile
keyword to ensure visibility.
private volatile boolean flag;
- Thread Safety: Always ensure that shared resources are properly synchronized to maintain thread safety and avoid data corruption.
- Synchronized Static Methods: Synchronize static methods to enforce synchronized access at the class level, ensuring only one thread can execute the static method across all instances.
public static synchronized void staticMethod() {
// method code
}
- Reentrancy: Java's intrinsic locks are reentrant, meaning if a thread holds a lock, it can acquire it again without deadlocking itself.