Synchronized Blocks in Java

Table of Contents

  1. Introduction to Synchronized Blocks
  2. Synchronized Method vs Synchronized Block
  3. Object Level Locking
  4. Class Level Locking
  5. Reentrant Synchronization
  6. Synchronized Collections
  7. Deadlock Prevention
  8. Performance Considerations
  9. Best Practices
  10. Complete Examples

Introduction to Synchronized Blocks

Synchronized blocks in Java are used to control access to shared resources in multithreaded environments. They ensure that only one thread can execute a particular block of code at a time, preventing race conditions.

Why Synchronization is Needed:

public class WithoutSynchronization {
private int counter = 0;
public void increment() {
counter++; // Not thread-safe
}
public static void main(String[] args) throws InterruptedException {
WithoutSynchronization example = new WithoutSynchronization();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Expected: 2000, Actual: " + example.counter);
// Output may be less than 2000 due to race condition
}
}

Synchronized Method vs Synchronized Block

Synchronized Method:

public class SynchronizedMethodExample {
private int counter = 0;
// Synchronized method - locks on 'this' object
public synchronized void increment() {
counter++;
}
public synchronized int getCounter() {
return counter;
}
}

Synchronized Block:

public class SynchronizedBlockExample {
private int counter = 0;
private final Object lock = new Object();
public void increment() {
// Synchronized block with custom lock object
synchronized (lock) {
counter++;
}
}
public int getCounter() {
synchronized (lock) {
return counter;
}
}
}

Comparison Example:

public class MethodVsBlockComparison {
private int counter1 = 0;
private int counter2 = 0;
private final Object lock = new Object();
// Synchronized method - coarse-grained locking
public synchronized void incrementWithMethod() {
counter1++;
// Simulate some work
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// Synchronized block - fine-grained locking
public void incrementWithBlock() {
// Only synchronize the critical section
synchronized (lock) {
counter2++;
}
// Other non-critical work can happen outside synchronized block
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) throws InterruptedException {
MethodVsBlockExample example = new MethodVsBlockExample();
long startTime = System.currentTimeMillis();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
example.incrementWithMethod();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
example.incrementWithMethod();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
long methodTime = System.currentTimeMillis() - startTime;
startTime = System.currentTimeMillis();
Thread t3 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
example.incrementWithBlock();
}
});
Thread t4 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
example.incrementWithBlock();
}
});
t3.start();
t4.start();
t3.join();
t4.join();
long blockTime = System.currentTimeMillis() - startTime;
System.out.println("Synchronized method time: " + methodTime + "ms");
System.out.println("Synchronized block time: " + blockTime + "ms");
System.out.println("Block is " + (methodTime - blockTime) + "ms faster");
}
}

Object Level Locking

Instance Level Synchronization:

public class ObjectLevelLocking {
private int instanceCounter = 0;
private final Object instanceLock = new Object();
// Different instances have different locks
public void increment() {
synchronized (instanceLock) {
instanceCounter++;
System.out.println(Thread.currentThread().getName() + 
" - Counter: " + instanceCounter);
try {
Thread.sleep(100); // Simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) throws InterruptedException {
ObjectLevelLocking instance1 = new ObjectLevelLocking();
ObjectLevelLocking instance2 = new ObjectLevelLocking();
// These threads will NOT block each other - different instances
Thread t1 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
instance1.increment();
}
}, "Thread-1-Instance1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
instance1.increment();
}
}, "Thread-2-Instance1");
Thread t3 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
instance2.increment();
}
}, "Thread-3-Instance2");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println("Instance1 final counter: " + instance1.instanceCounter);
System.out.println("Instance2 final counter: " + instance2.instanceCounter);
}
}

Output:

Thread-1-Instance1 - Counter: 1
Thread-3-Instance2 - Counter: 1
Thread-1-Instance1 - Counter: 2
Thread-3-Instance2 - Counter: 2
Thread-2-Instance1 - Counter: 3
Thread-3-Instance2 - Counter: 3
Thread-1-Instance1 - Counter: 4
Thread-2-Instance1 - Counter: 5
Thread-2-Instance1 - Counter: 6
Instance1 final counter: 6
Instance2 final counter: 3

Class Level Locking

Static Synchronization:

public class ClassLevelLocking {
private static int staticCounter = 0;
private static final Object classLock = new Object();
// Class level locking - all instances share the same lock
public static void incrementStatic() {
synchronized (classLock) {
staticCounter++;
System.out.println(Thread.currentThread().getName() + 
" - Static Counter: " + staticCounter);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// Alternative: synchronized on class object
public static void incrementStaticAlternative() {
synchronized (ClassLevelLocking.class) {
staticCounter++;
System.out.println(Thread.currentThread().getName() + 
" - Static Counter (Alt): " + staticCounter);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) throws InterruptedException {
// Multiple instances but static synchronization
ClassLevelLocking instance1 = new ClassLevelLocking();
ClassLevelLocking instance2 = new ClassLevelLocking();
// All these threads will block each other - class level lock
Thread t1 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
ClassLevelLocking.incrementStatic();
}
}, "Thread-1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
ClassLevelLocking.incrementStaticAlternative();
}
}, "Thread-2");
Thread t3 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
instance1.incrementStatic(); // Using instance, but still class level
}
}, "Thread-3");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println("Final static counter: " + ClassLevelLocking.staticCounter);
}
}

Reentrant Synchronization

Reentrant Locking Example:

public class ReentrantSynchronization {
private int counter = 0;
private final Object lock = new Object();
public void outerMethod() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " in outerMethod");
counter++;
innerMethod(); // Reentrant - same thread can reacquire the same lock
}
}
public void innerMethod() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " in innerMethod");
counter++;
deepestMethod();
}
}
public void deepestMethod() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " in deepestMethod");
counter++;
}
}
public void nonReentrantCall() {
// This will cause deadlock if called from synchronized context
Thread otherThread = new Thread(() -> {
synchronized (lock) {
System.out.println("Other thread acquired lock");
}
});
otherThread.start();
try {
otherThread.join(); // Wait for other thread - can cause deadlock
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantSynchronization example = new ReentrantSynchronization();
Thread t1 = new Thread(() -> {
example.outerMethod();
System.out.println("Thread 1 completed");
}, "Thread-1");
Thread t2 = new Thread(() -> {
// This thread will wait for t1 to release the lock
example.outerMethod();
System.out.println("Thread 2 completed");
}, "Thread-2");
t1.start();
Thread.sleep(100); // Ensure t1 starts first
t2.start();
t1.join();
t2.join();
System.out.println("Final counter: " + example.counter);
// Demonstrate potential deadlock scenario
ReentrantSynchronization deadlockExample = new ReentrantSynchronization();
Thread deadlockThread = new Thread(() -> {
synchronized (deadlockExample.lock) {
System.out.println("Main thread acquired lock, calling nonReentrantCall...");
deadlockExample.nonReentrantCall(); // This may cause issues
}
});
deadlockThread.start();
deadlockThread.join(2000); // Timeout to prevent permanent blocking
if (deadlockThread.isAlive()) {
System.out.println("Potential deadlock detected - thread still alive after timeout");
deadlockThread.interrupt();
}
}
}

Synchronized Collections

Thread-Safe Collections:

import java.util.*;
public class SynchronizedCollectionsExample {
public static void main(String[] args) throws InterruptedException {
// Unsafe collection
List<Integer> unsafeList = new ArrayList<>();
// Synchronized wrapper
List<Integer> synchronizedList = Collections.synchronizedList(new ArrayList<>());
// Vector (thread-safe alternative)
Vector<Integer> vector = new Vector<>();
System.out.println("Testing Unsafe List:");
testCollection(unsafeList, "UnsafeList");
System.out.println("\nTesting Synchronized List:");
testCollection(synchronizedList, "SynchronizedList");
System.out.println("\nTesting Vector:");
testCollection(vector, "Vector");
}
private static void testCollection(List<Integer> collection, String name) 
throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
collection.add(i);
}
});
Thread t2 = new Thread(() -> {
for (int i = 1000; i < 2000; i++) {
collection.add(i);
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(name + " size: " + collection.size() + 
" (Expected: 2000)");
// Additional synchronization needed for iteration
if (collection == Collections.synchronizedList(collection)) {
synchronized (collection) {
Iterator<Integer> iterator = collection.iterator();
while (iterator.hasNext()) {
iterator.next(); // Safe iteration
}
}
}
}
}
// Custom synchronized collection
class SynchronizedCollection<T> {
private final Collection<T> collection;
private final Object lock = new Object();
public SynchronizedCollection(Collection<T> collection) {
this.collection = collection;
}
public boolean add(T element) {
synchronized (lock) {
return collection.add(element);
}
}
public boolean remove(T element) {
synchronized (lock) {
return collection.remove(element);
}
}
public int size() {
synchronized (lock) {
return collection.size();
}
}
public void forEach(Consumer<T> action) {
synchronized (lock) {
for (T element : collection) {
action.accept(element);
}
}
}
// Functional interface for convenience
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
}

Deadlock Prevention

Deadlock Example and Prevention:

public class DeadlockPrevention {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
private final Object lock3 = new Object();
// CAUSES DEADLOCK - inconsistent lock ordering
public void methodA() {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " acquired lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " acquired lock2");
}
}
}
public void methodB() {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " acquired lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " acquired lock1");
}
}
}
// DEADLOCK PREVENTION - consistent lock ordering
public void methodASafe() {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " acquired lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " acquired lock2");
}
}
}
public void methodBSafe() {
synchronized (lock1) { // Same order as methodASafe
System.out.println(Thread.currentThread().getName() + " acquired lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " acquired lock2");
}
}
}
// DEADLOCK PREVENTION - tryLock with timeout
public void methodWithTimeout() {
long startTime = System.currentTimeMillis();
while (true) {
if (System.currentTimeMillis() - startTime > 5000) {
System.out.println(Thread.currentThread().getName() + " - Lock acquisition timeout");
return;
}
boolean locked1 = false;
boolean locked2 = false;
try {
// Try to acquire first lock
synchronized (lock1) {
locked1 = true;
System.out.println(Thread.currentThread().getName() + " acquired lock1");
// Try to acquire second lock with short wait
if (Thread.holdsLock(lock2)) {
System.out.println("Would deadlock - releasing lock1");
return;
}
synchronized (lock2) {
locked2 = true;
System.out.println(Thread.currentThread().getName() + " acquired lock2");
// Do work
return;
}
}
} finally {
// Locks are automatically released when leaving synchronized blocks
}
}
}
public static void main(String[] args) throws InterruptedException {
DeadlockPrevention example = new DeadlockPrevention();
System.out.println("=== Deadlock Scenario ===");
Thread deadlock1 = new Thread(() -> example.methodA(), "Deadlock-Thread-1");
Thread deadlock2 = new Thread(() -> example.methodB(), "Deadlock-Thread-2");
deadlock1.start();
deadlock2.start();
// Wait a bit then check for deadlock
Thread.sleep(2000);
if (deadlock1.isAlive() && deadlock2.isAlive()) {
System.out.println("DEADLOCK DETECTED! Threads are stuck.");
deadlock1.interrupt();
deadlock2.interrupt();
}
deadlock1.join(100);
deadlock2.join(100);
System.out.println("\n=== Safe Scenario ===");
Thread safe1 = new Thread(() -> example.methodASafe(), "Safe-Thread-1");
Thread safe2 = new Thread(() -> example.methodBSafe(), "Safe-Thread-2");
safe1.start();
safe2.start();
safe1.join();
safe2.join();
System.out.println("Safe threads completed successfully");
System.out.println("\n=== Timeout Prevention ===");
Thread timeout1 = new Thread(() -> example.methodWithTimeout(), "Timeout-Thread-1");
Thread timeout2 = new Thread(() -> example.methodWithTimeout(), "Timeout-Thread-2");
timeout1.start();
timeout2.start();
timeout1.join();
timeout2.join();
System.out.println("Timeout threads completed");
}
}

Performance Considerations

Performance Comparison:

import java.util.concurrent.atomic.AtomicInteger;
public class SynchronizationPerformance {
private int synchronizedCounter = 0;
private final Object lock = new Object();
private AtomicInteger atomicCounter = new AtomicInteger(0);
private volatile int volatileCounter = 0;
// Synchronized increment
public void incrementSynchronized() {
synchronized (lock) {
synchronizedCounter++;
}
}
// Atomic increment
public void incrementAtomic() {
atomicCounter.incrementAndGet();
}
// Unsafe increment
public void incrementUnsafe() {
volatileCounter++; // Not thread-safe despite volatile
}
// Thread-local counter
private ThreadLocal<Integer> threadLocalCounter = ThreadLocal.withInitial(() -> 0);
public void incrementThreadLocal() {
threadLocalCounter.set(threadLocalCounter.get() + 1);
}
public static void main(String[] args) throws InterruptedException {
SynchronizationPerformance test = new SynchronizationPerformance();
int numThreads = 10;
int incrementsPerThread = 10000;
// Test synchronized
long startTime = System.currentTimeMillis();
testSynchronized(test, numThreads, incrementsPerThread);
long syncTime = System.currentTimeMillis() - startTime;
// Test atomic
startTime = System.currentTimeMillis();
testAtomic(test, numThreads, incrementsPerThread);
long atomicTime = System.currentTimeMillis() - startTime;
// Test thread-local
startTime = System.currentTimeMillis();
testThreadLocal(test, numThreads, incrementsPerThread);
long threadLocalTime = System.currentTimeMillis() - startTime;
System.out.println("\n=== Performance Results ===");
System.out.printf("Synchronized: %d ms%n", syncTime);
System.out.printf("Atomic: %d ms%n", atomicTime);
System.out.printf("ThreadLocal: %d ms%n", threadLocalTime);
System.out.println("\n=== Final Counters ===");
System.out.println("Synchronized counter: " + test.synchronizedCounter);
System.out.println("Atomic counter: " + test.atomicCounter.get());
System.out.println("Thread-local requires aggregation");
}
private static void testSynchronized(SynchronizationPerformance test, 
int numThreads, int increments) 
throws InterruptedException {
Thread[] threads = new Thread[numThreads];
for (int i = 0; i < numThreads; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < increments; j++) {
test.incrementSynchronized();
}
});
}
for (Thread thread : threads) thread.start();
for (Thread thread : threads) thread.join();
}
private static void testAtomic(SynchronizationPerformance test, 
int numThreads, int increments) 
throws InterruptedException {
Thread[] threads = new Thread[numThreads];
for (int i = 0; i < numThreads; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < increments; j++) {
test.incrementAtomic();
}
});
}
for (Thread thread : threads) thread.start();
for (Thread thread : threads) thread.join();
}
private static void testThreadLocal(SynchronizationPerformance test, 
int numThreads, int increments) 
throws InterruptedException {
Thread[] threads = new Thread[numThreads];
for (int i = 0; i < numThreads; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < increments; j++) {
test.incrementThreadLocal();
}
});
}
for (Thread thread : threads) thread.start();
for (Thread thread : threads) thread.join();
}
}

Best Practices

1. Use Private Final Lock Objects:

public class BestPracticeLocks {
// Good practice - private final lock objects
private final Object primaryLock = new Object();
private final Object secondaryLock = new Object();
// Avoid - using intrinsic lock on public objects
public final Object publicLock = new Object(); // Not recommended
// Avoid - locking on 'this'
public void methodNotRecommended() {
synchronized (this) { // Not recommended
// critical section
}
}
// Good - using private final lock
public void methodRecommended() {
synchronized (primaryLock) {
// critical section
}
}
}

2. Keep Synchronized Blocks Small:

public class FineGrainedSynchronization {
private final Object lock = new Object();
private int counter = 0;
private String lastThread = "";
// Poor practice - large synchronized block
public void processDataPoor(String threadName) {
synchronized (lock) {
// Critical section - should be minimal
counter++;
lastThread = threadName;
// Non-critical work - should be outside synchronized block
try {
Thread.sleep(100); // Simulate I/O or heavy computation
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Processed by: " + threadName);
}
}
// Good practice - minimal synchronized block
public void processDataGood(String threadName) {
// Non-critical work outside synchronized block
performNonCriticalWork(threadName);
// Only synchronize the actual critical section
synchronized (lock) {
counter++;
lastThread = threadName;
}
// More non-critical work
System.out.println("Processed by: " + threadName);
}
private void performNonCriticalWork(String threadName) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

3. Avoid Nested Synchronized Blocks:

public class NestedSynchronization {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
// Dangerous - can cause deadlocks
public void dangerousMethod() {
synchronized (lock1) {
// Some work
synchronized (lock2) {
// More work
}
}
}
// Safer alternative - use ordered locking
public void saferMethod() {
// Always acquire locks in the same order
Object firstLock = lock1.hashCode() < lock2.hashCode() ? lock1 : lock2;
Object secondLock = lock1.hashCode() < lock2.hashCode() ? lock2 : lock1;
synchronized (firstLock) {
synchronized (secondLock) {
// Work with both locks
}
}
}
// Best - avoid nested synchronization when possible
public void bestMethod() {
// Restructure to avoid nested synchronization
performWorkWithLock1();
performWorkWithLock2();
}
private void performWorkWithLock1() {
synchronized (lock1) {
// Work with lock1
}
}
private void performWorkWithLock2() {
synchronized (lock2) {
// Work with lock2
}
}
}

Complete Examples

Complete Banking System Example:

import java.util.*;
import java.util.concurrent.*;
public class BankingSystemExample {
public static void main(String[] args) throws InterruptedException {
Bank bank = new Bank();
// Create accounts
bank.createAccount("Alice", 1000);
bank.createAccount("Bob", 500);
bank.createAccount("Charlie", 1500);
System.out.println("Initial balances:");
bank.printAllBalances();
// Perform concurrent transactions
ExecutorService executor = Executors.newFixedThreadPool(5);
List<Future<?>> futures = new ArrayList<>();
// Transfer money between accounts
futures.add(executor.submit(() -> bank.transfer("Alice", "Bob", 200)));
futures.add(executor.submit(() -> bank.transfer("Bob", "Charlie", 100)));
futures.add(executor.submit(() -> bank.transfer("Charlie", "Alice", 300)));
futures.add(executor.submit(() -> bank.deposit("Alice", 500)));
futures.add(executor.submit(() -> bank.withdraw("Bob", 50)));
// Wait for all transactions to complete
for (Future<?> future : futures) {
try {
future.get();
} catch (ExecutionException e) {
System.out.println("Transaction failed: " + e.getCause().getMessage());
}
}
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
System.out.println("\nFinal balances:");
bank.printAllBalances();
System.out.println("\nTransaction history:");
bank.printTransactionHistory();
}
}
class Bank {
private final Map<String, BankAccount> accounts = new HashMap<>();
private final List<String> transactionHistory = Collections.synchronizedList(new ArrayList<>());
private final Object accountsLock = new Object();
public void createAccount(String accountHolder, double initialBalance) {
synchronized (accountsLock) {
if (accounts.containsKey(accountHolder)) {
throw new IllegalArgumentException("Account already exists: " + accountHolder);
}
accounts.put(accountHolder, new BankAccount(accountHolder, initialBalance));
addTransaction("CREATE", accountHolder, initialBalance);
}
}
public boolean transfer(String from, String to, double amount) {
// Avoid deadlock by ordering locks
String firstLock = from.compareTo(to) < 0 ? from : to;
String secondLock = from.compareTo(to) < 0 ? to : from;
synchronized (accountsLock) {
BankAccount fromAccount = accounts.get(firstLock);
BankAccount toAccount = accounts.get(secondLock);
if (fromAccount == null || toAccount == null) {
addTransaction("FAILED_TRANSFER", from + "->" + to, amount);
return false;
}
// Nested synchronization with ordered locks
synchronized (fromAccount) {
synchronized (toAccount) {
if (fromAccount.getBalance() >= amount) {
fromAccount.withdraw(amount);
toAccount.deposit(amount);
addTransaction("TRANSFER", from + "->" + to, amount);
return true;
} else {
addTransaction("FAILED_TRANSFER", from + "->" + to, amount);
return false;
}
}
}
}
}
public void deposit(String accountHolder, double amount) {
synchronized (accountsLock) {
BankAccount account = accounts.get(accountHolder);
if (account != null) {
synchronized (account) {
account.deposit(amount);
addTransaction("DEPOSIT", accountHolder, amount);
}
}
}
}
public boolean withdraw(String accountHolder, double amount) {
synchronized (accountsLock) {
BankAccount account = accounts.get(accountHolder);
if (account != null) {
synchronized (account) {
if (account.getBalance() >= amount) {
account.withdraw(amount);
addTransaction("WITHDRAW", accountHolder, amount);
return true;
}
addTransaction("FAILED_WITHDRAW", accountHolder, amount);
return false;
}
}
return false;
}
}
public double getBalance(String accountHolder) {
synchronized (accountsLock) {
BankAccount account = accounts.get(accountHolder);
if (account != null) {
synchronized (account) {
return account.getBalance();
}
}
return -1;
}
}
private void addTransaction(String type, String details, double amount) {
String transaction = String.format("%s: %s - $%.2f [%s]", 
type, details, amount, Thread.currentThread().getName());
transactionHistory.add(transaction);
}
public void printAllBalances() {
synchronized (accountsLock) {
accounts.forEach((name, account) -> {
synchronized (account) {
System.out.printf("%s: $%.2f%n", name, account.getBalance());
}
});
}
}
public void printTransactionHistory() {
synchronized (transactionHistory) {
transactionHistory.forEach(System.out::println);
}
}
}
class BankAccount {
private final String accountHolder;
private double balance;
private final Object accountLock = new Object();
public BankAccount(String accountHolder, double initialBalance) {
this.accountHolder = accountHolder;
this.balance = initialBalance;
}
public void deposit(double amount) {
synchronized (accountLock) {
if (amount > 0) {
balance += amount;
}
}
}
public void withdraw(double amount) {
synchronized (accountLock) {
if (amount > 0 && balance >= amount) {
balance -= amount;
}
}
}
public double getBalance() {
synchronized (accountLock) {
return balance;
}
}
public String getAccountHolder() {
return accountHolder;
}
}

Producer-Consumer with Synchronized Blocks:

import java.util.*;
public class ProducerConsumerExample {
public static void main(String[] args) throws InterruptedException {
MessageQueue queue = new MessageQueue(5);
Thread producer1 = new Thread(new Producer(queue, "Producer-1"));
Thread producer2 = new Thread(new Producer(queue, "Producer-2"));
Thread consumer1 = new Thread(new Consumer(queue, "Consumer-1"));
Thread consumer2 = new Thread(new Consumer(queue, "Consumer-2"));
producer1.start();
producer2.start();
consumer1.start();
consumer2.start();
// Run for 10 seconds
Thread.sleep(10000);
producer1.interrupt();
producer2.interrupt();
consumer1.interrupt();
consumer2.interrupt();
producer1.join();
producer2.join();
consumer1.join();
consumer2.join();
System.out.println("Final queue size: " + queue.size());
}
}
class MessageQueue {
private final Queue<String> queue;
private final int capacity;
private final Object lock = new Object();
public MessageQueue(int capacity) {
this.capacity = capacity;
this.queue = new LinkedList<>();
}
public void put(String message) throws InterruptedException {
synchronized (lock) {
while (queue.size() == capacity) {
System.out.println(Thread.currentThread().getName() + " waiting - queue full");
lock.wait();
}
queue.offer(message);
System.out.println(Thread.currentThread().getName() + " produced: " + message);
System.out.println("Queue size: " + queue.size());
lock.notifyAll(); // Notify waiting consumers
}
}
public String take() throws InterruptedException {
synchronized (lock) {
while (queue.isEmpty()) {
System.out.println(Thread.currentThread().getName() + " waiting - queue empty");
lock.wait();
}
String message = queue.poll();
System.out.println(Thread.currentThread().getName() + " consumed: " + message);
System.out.println("Queue size: " + queue.size());
lock.notifyAll(); // Notify waiting producers
return message;
}
}
public int size() {
synchronized (lock) {
return queue.size();
}
}
}
class Producer implements Runnable {
private final MessageQueue queue;
private final String name;
private int messageCount = 0;
public Producer(MessageQueue queue, String name) {
this.queue = queue;
this.name = name;
}
@Override
public void run() {
Thread.currentThread().setName(name);
while (!Thread.currentThread().isInterrupted()) {
try {
String message = name + "-Message-" + (++messageCount);
queue.put(message);
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
System.out.println(name + " stopped");
}
}
class Consumer implements Runnable {
private final MessageQueue queue;
private final String name;
public Consumer(MessageQueue queue, String name) {
this.queue = queue;
this.name = name;
}
@Override
public void run() {
Thread.currentThread().setName(name);
while (!Thread.currentThread().isInterrupted()) {
try {
String message = queue.take();
// Process message
Thread.sleep((long) (Math.random() * 1500));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
System.out.println(name + " stopped");
}
}

Expected Output:

Producer-1 produced: Producer-1-Message-1
Queue size: 1
Producer-2 produced: Producer-2-Message-1
Queue size: 2
Consumer-1 consumed: Producer-1-Message-1
Queue size: 1
Consumer-2 consumed: Producer-2-Message-1
Queue size: 0
Producer-1 produced: Producer-1-Message-2
Queue size: 1
... (continues for 10 seconds)
Final queue size: 3

Key Takeaways

  1. Synchronized blocks provide finer control than synchronized methods
  2. Use private final objects for locking to prevent external interference
  3. Keep synchronized blocks small to minimize contention
  4. Be careful with nested synchronization to avoid deadlocks
  5. Always use wait/notify in loops to handle spurious wakeups
  6. Consider performance implications and use alternatives like java.util.concurrent when appropriate
  7. Use consistent lock ordering to prevent deadlocks

Synchronized blocks are fundamental to thread safety in Java, but they should be used judiciously with proper understanding of their implications on performance and correctness.

Leave a Reply

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


Macro Nepal Helper