Synchronization Keyword in Java – Complete Guide

Overview

The synchronized keyword in Java is used to control access to critical sections of code by multiple threads. It ensures that only one thread can execute a synchronized block/method at a time.

1. Synchronized Methods

Instance Method Synchronization

public class SynchronizedMethods {
static class Counter {
private int count = 0;
// Synchronized instance method
public synchronized void increment() {
count++;
System.out.println(Thread.currentThread().getName() + 
" incremented to: " + count);
}
// Synchronized instance method
public synchronized void decrement() {
count--;
System.out.println(Thread.currentThread().getName() + 
" decremented to: " + count);
}
public synchronized int getCount() {
return count;
}
}
static class CounterTask implements Runnable {
private Counter counter;
private boolean increment;
public CounterTask(Counter counter, boolean increment) {
this.counter = counter;
this.increment = increment;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
if (increment) {
counter.increment();
} else {
counter.decrement();
}
try {
Thread.sleep(100); // Simulate some work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Synchronized Instance Methods ===");
Counter counter = new Counter();
Thread t1 = new Thread(new CounterTask(counter, true), "Increment-Thread");
Thread t2 = new Thread(new CounterTask(counter, false), "Decrement-Thread");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + counter.getCount());
}
}

Static Method Synchronization

public class StaticSynchronization {
static class SharedResource {
private static int staticCounter = 0;
private int instanceCounter = 0;
// Synchronized static method - locks on Class object
public static synchronized void incrementStatic() {
staticCounter++;
System.out.println(Thread.currentThread().getName() + 
" - Static counter: " + staticCounter);
try {
Thread.sleep(100); // Simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// Synchronized instance method - locks on instance
public synchronized void incrementInstance() {
instanceCounter++;
System.out.println(Thread.currentThread().getName() + 
" - Instance counter: " + instanceCounter);
try {
Thread.sleep(100); // Simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Static vs Instance Synchronization ===");
SharedResource resource1 = new SharedResource();
SharedResource resource2 = new SharedResource();
// Threads using static synchronization (class-level lock)
Thread t1 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
SharedResource.incrementStatic();
}
}, "Static-Thread-1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
SharedResource.incrementStatic();
}
}, "Static-Thread-2");
// Threads using instance synchronization (instance-level lock)
Thread t3 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
resource1.incrementInstance();
}
}, "Instance-Thread-1");
Thread t4 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
resource2.incrementInstance();
}
}, "Instance-Thread-2");
t1.start();
t2.start();
t3.start();
t4.start();
t1.join();
t2.join();
t3.join();
t4.join();
System.out.println("All threads completed");
}
}

2. Synchronized Blocks

Instance Synchronized Blocks

public class SynchronizedBlocks {
static class BankAccount {
private double balance;
private final Object lock = new Object();
private String accountHolder;
public BankAccount(String accountHolder, double initialBalance) {
this.accountHolder = accountHolder;
this.balance = initialBalance;
}
// Using synchronized block with 'this'
public void deposit(double amount) {
synchronized (this) {
balance += amount;
System.out.println(Thread.currentThread().getName() + 
" deposited: " + amount + 
", Balance: " + balance);
// Simulate some processing time
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// Using synchronized block with custom lock object
public void withdraw(double amount) {
synchronized (lock) {
if (balance >= amount) {
balance -= amount;
System.out.println(Thread.currentThread().getName() + 
" withdrew: " + amount + 
", Balance: " + balance);
} else {
System.out.println(Thread.currentThread().getName() + 
" - Insufficient funds for withdrawal: " + amount);
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// Different operations can use different locks
public void updateAccountHolder(String newHolder) {
synchronized (lock) { // Using custom lock
String oldHolder = this.accountHolder;
this.accountHolder = newHolder;
System.out.println(Thread.currentThread().getName() + 
" updated holder from " + oldHolder + 
" to " + newHolder);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public double getBalance() {
synchronized (this) { // Using 'this' lock
return balance;
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Synchronized Blocks ===");
BankAccount account = new BankAccount("John Doe", 1000);
// Create multiple threads performing different operations
Thread depositThread1 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
account.deposit(100);
}
}, "Depositor-1");
Thread depositThread2 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
account.deposit(50);
}
}, "Depositor-2");
Thread withdrawThread = new Thread(() -> {
for (int i = 0; i < 4; i++) {
account.withdraw(200);
}
}, "Withdrawer");
Thread updateThread = new Thread(() -> {
account.updateAccountHolder("Jane Smith");
}, "Updater");
depositThread1.start();
depositThread2.start();
withdrawThread.start();
updateThread.start();
depositThread1.join();
depositThread2.join();
withdrawThread.join();
updateThread.join();
System.out.println("Final balance: " + account.getBalance());
}
}

Static Synchronized Blocks

public class StaticSynchronizedBlocks {
static class ConfigurationManager {
private static Map<String, String> config = new HashMap<>();
private static final Object staticLock = new Object();
// Static synchronized block
public static void updateConfig(String key, String value) {
synchronized (staticLock) {
String oldValue = config.put(key, value);
System.out.println(Thread.currentThread().getName() + 
" updated " + key + 
" from '" + oldValue + "' to '" + value + "'");
try {
Thread.sleep(100); // Simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// Another static method using class-level lock
public static void clearConfig() {
synchronized (ConfigurationManager.class) {
int size = config.size();
config.clear();
System.out.println(Thread.currentThread().getName() + 
" cleared " + size + " config entries");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static String getConfig(String key) {
synchronized (staticLock) {
return config.get(key);
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Static Synchronized Blocks ===");
// Multiple threads updating static configuration
Thread t1 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
ConfigurationManager.updateConfig("server" + i, "192.168.1." + i);
}
}, "Config-Updater-1");
Thread t2 = new Thread(() -> {
for (int i = 3; i < 6; i++) {
ConfigurationManager.updateConfig("server" + i, "192.168.1." + i);
}
}, "Config-Updater-2");
Thread t3 = new Thread(() -> {
ConfigurationManager.clearConfig();
}, "Config-Cleaner");
t1.start();
t2.start();
Thread.sleep(200); // Let some updates happen
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println("Configuration management completed");
}
}

3. Producer-Consumer Problem

Classic Producer-Consumer with Synchronization

public class ProducerConsumer {
static class MessageQueue {
private final Queue<String> queue = new LinkedList<>();
private final int capacity;
public MessageQueue(int capacity) {
this.capacity = capacity;
}
public synchronized void produce(String message) throws InterruptedException {
while (queue.size() == capacity) {
System.out.println(Thread.currentThread().getName() + 
" - Queue full, waiting...");
wait(); // Wait until consumer notifies
}
queue.add(message);
System.out.println(Thread.currentThread().getName() + 
" produced: " + message + 
" | Queue size: " + queue.size());
notifyAll(); // Notify waiting consumers
}
public synchronized String consume() throws InterruptedException {
while (queue.isEmpty()) {
System.out.println(Thread.currentThread().getName() + 
" - Queue empty, waiting...");
wait(); // Wait until producer notifies
}
String message = queue.remove();
System.out.println(Thread.currentThread().getName() + 
" consumed: " + message + 
" | Queue size: " + queue.size());
notifyAll(); // Notify waiting producers
return message;
}
public synchronized int size() {
return queue.size();
}
}
static class Producer implements Runnable {
private MessageQueue queue;
private int messageCount;
public Producer(MessageQueue queue, int messageCount) {
this.queue = queue;
this.messageCount = messageCount;
}
@Override
public void run() {
try {
for (int i = 1; i <= messageCount; i++) {
String message = "Message-" + i + "-from-" + Thread.currentThread().getName();
queue.produce(message);
// Simulate production time
Thread.sleep((long) (Math.random() * 200));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
static class Consumer implements Runnable {
private MessageQueue queue;
private int messageCount;
public Consumer(MessageQueue queue, int messageCount) {
this.queue = queue;
this.messageCount = messageCount;
}
@Override
public void run() {
try {
for (int i = 1; i <= messageCount; i++) {
queue.consume();
// Simulate consumption time
Thread.sleep((long) (Math.random() * 300));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Producer-Consumer with Synchronization ===");
MessageQueue queue = new MessageQueue(3);
Thread producer1 = new Thread(new Producer(queue, 5), "Producer-1");
Thread producer2 = new Thread(new Producer(queue, 5), "Producer-2");
Thread consumer1 = new Thread(new Consumer(queue, 7), "Consumer-1");
Thread consumer2 = new Thread(new Consumer(queue, 3), "Consumer-2");
producer1.start();
producer2.start();
consumer1.start();
consumer2.start();
producer1.join();
producer2.join();
consumer1.join();
consumer2.join();
System.out.println("Final queue size: " + queue.size());
System.out.println("Producer-Consumer completed");
}
}

4. Deadlock Prevention

Deadlock Example and Prevention

public class DeadlockPrevention {
static class Account {
private final String id;
private double balance;
public Account(String id, double balance) {
this.id = id;
this.balance = balance;
}
public String getId() { return id; }
public synchronized void debit(double amount) {
balance -= amount;
}
public synchronized void credit(double amount) {
balance += amount;
}
public synchronized double getBalance() {
return balance;
}
// Potential deadlock method
public void transferWithDeadlock(Account to, double amount) {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + 
" acquired lock on " + this.id);
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (to) {
System.out.println(Thread.currentThread().getName() + 
" acquired lock on " + to.id);
if (this.balance >= amount) {
this.debit(amount);
to.credit(amount);
System.out.println("Transferred " + amount + 
" from " + this.id + " to " + to.id);
}
}
}
}
// Deadlock prevention using ordered locking
public void transferSafe(Account to, double amount) {
// Determine lock order based on account ID
Account firstLock = this.id.compareTo(to.id) < 0 ? this : to;
Account secondLock = this.id.compareTo(to.id) < 0 ? to : this;
synchronized (firstLock) {
System.out.println(Thread.currentThread().getName() + 
" acquired lock on " + firstLock.id);
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (secondLock) {
System.out.println(Thread.currentThread().getName() + 
" acquired lock on " + secondLock.id);
if (this.balance >= amount) {
this.debit(amount);
to.credit(amount);
System.out.println("Transferred " + amount + 
" from " + this.id + " to " + to.id);
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Deadlock Prevention ===");
Account accountA = new Account("A", 1000);
Account accountB = new Account("B", 1000);
// This might cause deadlock
System.out.println("\n--- Potential Deadlock Scenario ---");
Thread t1 = new Thread(() -> {
accountA.transferWithDeadlock(accountB, 100);
}, "Thread-1-A-to-B");
Thread t2 = new Thread(() -> {
accountB.transferWithDeadlock(accountA, 50);
}, "Thread-2-B-to-A");
t1.start();
t2.start();
// Wait a bit to see if deadlock occurs
Thread.sleep(1000);
if (t1.isAlive() && t2.isAlive()) {
System.out.println("Deadlock detected! Stopping threads...");
t1.interrupt();
t2.interrupt();
}
t1.join();
t2.join();
// Safe transfer
System.out.println("\n--- Safe Transfer Scenario ---");
Thread t3 = new Thread(() -> {
accountA.transferSafe(accountB, 100);
}, "Thread-3-A-to-B");
Thread t4 = new Thread(() -> {
accountB.transferSafe(accountA, 50);
}, "Thread-4-B-to-A");
t3.start();
t4.start();
t3.join();
t4.join();
System.out.println("Account A balance: " + accountA.getBalance());
System.out.println("Account B balance: " + accountB.getBalance());
}
}

5. Read-Write Locks Pattern

Synchronized Read-Write Implementation

public class ReadWriteSynchronized {
static class SharedDictionary {
private final Map<String, String> dictionary = new HashMap<>();
private int readCount = 0;
private int writeCount = 0;
public synchronized void put(String key, String value) {
writeCount++;
System.out.println(Thread.currentThread().getName() + 
" writing - Key: " + key + ", Value: " + value +
" (Write #" + writeCount + ")");
// Simulate write operation time
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
dictionary.put(key, value);
System.out.println(Thread.currentThread().getName() + " write completed");
}
public synchronized String get(String key) {
readCount++;
System.out.println(Thread.currentThread().getName() + 
" reading - Key: " + key + 
" (Read #" + readCount + ")");
// Simulate read operation time
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
String value = dictionary.get(key);
System.out.println(Thread.currentThread().getName() + 
" read - Key: " + key + ", Value: " + value);
return value;
}
public synchronized int size() {
return dictionary.size();
}
}
static class Writer implements Runnable {
private SharedDictionary dictionary;
private int entries;
public Writer(SharedDictionary dictionary, int entries) {
this.dictionary = dictionary;
this.entries = entries;
}
@Override
public void run() {
for (int i = 0; i < entries; i++) {
String key = "key-" + Thread.currentThread().getName() + "-" + i;
String value = "value-" + i;
dictionary.put(key, value);
try {
Thread.sleep(150); // Time between writes
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
static class Reader implements Runnable {
private SharedDictionary dictionary;
private int reads;
public Reader(SharedDictionary dictionary, int reads) {
this.dictionary = dictionary;
this.reads = reads;
}
@Override
public void run() {
for (int i = 0; i < reads; i++) {
String key = "key-" + (i % 5); // Read from limited set of keys
dictionary.get(key);
try {
Thread.sleep(50); // Time between reads
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Read-Write with Synchronization ===");
SharedDictionary dictionary = new SharedDictionary();
// Create writers
Thread writer1 = new Thread(new Writer(dictionary, 3), "Writer-1");
Thread writer2 = new Thread(new Writer(dictionary, 3), "Writer-2");
// Create readers
Thread reader1 = new Thread(new Reader(dictionary, 5), "Reader-1");
Thread reader2 = new Thread(new Reader(dictionary, 5), "Reader-2");
Thread reader3 = new Thread(new Reader(dictionary, 5), "Reader-3");
writer1.start();
writer2.start();
reader1.start();
reader2.start();
reader3.start();
writer1.join();
writer2.join();
reader1.join();
reader2.join();
reader3.join();
System.out.println("Final dictionary size: " + dictionary.size());
}
}

6. Double-Checked Locking

Singleton Pattern with Synchronization

public class DoubleCheckedLocking {
// Singleton class with various synchronization approaches
static class Singleton {
private static Singleton instance;
private static final Object lock = new Object();
private int value;
private Singleton() {
// Private constructor
value = 42;
System.out.println("Singleton instance created");
}
// Method 1: Synchronized method (thread-safe but slow)
public static synchronized Singleton getInstanceSyncMethod() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// Method 2: Synchronized block (better performance)
public static Singleton getInstanceSyncBlock() {
synchronized (lock) {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
// Method 3: Double-checked locking (best performance)
public static Singleton getInstanceDoubleChecked() {
if (instance == null) { // First check (no synchronization)
synchronized (lock) {
if (instance == null) { // Second check (with synchronization)
instance = new Singleton();
}
}
}
return instance;
}
// Method 4: Initialization-on-demand holder idiom (recommended)
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstanceHolder() {
return Holder.INSTANCE;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
static class SingletonAccessor implements Runnable {
private int methodType;
public SingletonAccessor(int methodType) {
this.methodType = methodType;
}
@Override
public void run() {
Singleton singleton = null;
switch (methodType) {
case 1:
singleton = Singleton.getInstanceSyncMethod();
break;
case 2:
singleton = Singleton.getInstanceSyncBlock();
break;
case 3:
singleton = Singleton.getInstanceDoubleChecked();
break;
case 4:
singleton = Singleton.getInstanceHolder();
break;
}
System.out.println(Thread.currentThread().getName() + 
" got singleton with value: " + singleton.getValue());
// Modify value to verify we're using same instance
singleton.setValue(singleton.getValue() + 1);
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Singleton Synchronization Patterns ===");
// Test each synchronization method
for (int method = 1; method <= 4; method++) {
System.out.println("\n--- Testing Method " + method + " ---");
// Reset singleton for each test
try {
java.lang.reflect.Field field = Singleton.class.getDeclaredField("instance");
field.setAccessible(true);
field.set(null, null);
} catch (Exception e) {
e.printStackTrace();
}
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new SingletonAccessor(method), "Thread-" + (i + 1));
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
// Verify final value
Singleton finalInstance = null;
switch (method) {
case 1: finalInstance = Singleton.getInstanceSyncMethod(); break;
case 2: finalInstance = Singleton.getInstanceSyncBlock(); break;
case 3: finalInstance = Singleton.getInstanceDoubleChecked(); break;
case 4: finalInstance = Singleton.getInstanceHolder(); break;
}
System.out.println("Final singleton value: " + finalInstance.getValue());
}
}
}

7. Best Practices and Performance

Synchronization Best Practices

public class SynchronizationBestPractices {
// 1. Use private final lock objects
static class PrivateLock {
private final Object lock = new Object();
private int data;
public void updateData() {
synchronized (lock) { // Good: using private lock
data++;
System.out.println("Data updated to: " + data);
}
}
}
// 2. Avoid synchronizing on non-final objects
static class NonFinalLock {
private Object lock = new Object(); // Bad: non-final
public void badMethod() {
synchronized (lock) {
// lock could be reassigned, breaking synchronization
}
}
}
// 3. Keep synchronized blocks small
static class SmallCriticalSections {
private int counter;
private final Object lock = new Object();
// Good: small synchronized block
public void efficientIncrement() {
// Non-critical work outside synchronized block
performValidation();
synchronized (lock) {
// Only critical section is synchronized
counter++;
}
// More non-critical work
performLogging();
}
// Bad: large synchronized block
public void inefficientIncrement() {
synchronized (lock) {
performValidation(); // This doesn't need synchronization
counter++;
performLogging(); // This doesn't need synchronization
}
}
private void performValidation() {
// Simulate validation work
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
private void performLogging() {
// Simulate logging work
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
}
// 4. Use different locks for independent operations
static class MultipleLocks {
private final Object readLock = new Object();
private final Object writeLock = new Object();
private int readCount = 0;
private int writeCount = 0;
public void readOperation() {
synchronized (readLock) {
readCount++;
System.out.println("Read operation #" + readCount);
}
}
public void writeOperation() {
synchronized (writeLock) {
writeCount++;
System.out.println("Write operation #" + writeCount);
}
}
}
// 5. Avoid nested synchronization (deadlock risk)
static class NestedSynchronization {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
// Dangerous: nested synchronization
public void dangerousMethod() {
synchronized (lock1) {
System.out.println("Acquired lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) { // Potential deadlock
System.out.println("Acquired lock2");
}
}
}
// Safe: use ordered locking
public void safeMethod(Object obj1, Object obj2) {
Object firstLock = System.identityHashCode(obj1) < System.identityHashCode(obj2) ? obj1 : obj2;
Object secondLock = System.identityHashCode(obj1) < System.identityHashCode(obj2) ? obj2 : obj1;
synchronized (firstLock) {
synchronized (secondLock) {
// Critical section
}
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Synchronization Best Practices ===");
// Test efficient vs inefficient synchronization
SmallCriticalSections example = new SmallCriticalSections();
System.out.println("Testing efficient synchronization...");
long start = System.currentTimeMillis();
Thread t1 = new Thread(() -> { for (int i = 0; i < 10; i++) example.efficientIncrement(); });
Thread t2 = new Thread(() -> { for (int i = 0; i < 10; i++) example.efficientIncrement(); });
t1.start();
t2.start();
t1.join();
t2.join();
long efficientTime = System.currentTimeMillis() - start;
System.out.println("Testing inefficient synchronization...");
start = System.currentTimeMillis();
Thread t3 = new Thread(() -> { for (int i = 0; i < 10; i++) example.inefficientIncrement(); });
Thread t4 = new Thread(() -> { for (int i = 0; i < 10; i++) example.inefficientIncrement(); });
t3.start();
t4.start();
t3.join();
t4.join();
long inefficientTime = System.currentTimeMillis() - start;
System.out.println("Efficient time: " + efficientTime + "ms");
System.out.println("Inefficient time: " + inefficientTime + "ms");
System.out.println("Improvement: " + 
((inefficientTime - efficientTime) * 100.0 / inefficientTime) + "%");
}
}

Key Points Summary

  1. Synchronized Methods: Lock on this (instance) or Class object (static)
  2. Synchronized Blocks: More flexible, can use any object as lock
  3. Intrinsic Locks: Every object has an intrinsic lock (monitor)
  4. Reentrant: Same thread can acquire the same lock multiple times
  5. Memory Visibility: Synchronization ensures visibility changes across threads

Best Practices

  1. Use private final lock objects instead of synchronizing on this
  2. Keep synchronized blocks small to minimize contention
  3. Use different locks for independent operations
  4. Avoid nested synchronization to prevent deadlocks
  5. Consider higher-level concurrency utilities (java.util.concurrent) for complex scenarios

Synchronization is fundamental for thread safety in Java, but should be used judiciously to avoid performance issues and deadlocks.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper