Thread safety utilities and Thread safe collections

Java provides several utilities and collections that help in writing thread-safe code. These utilities and collections are part of the java.util.concurrent package.

Thread Safety Utilities

Locks and Synchronizers

ReentrantLock: Provides a re-entrant mutual exclusion lock with the same basic behaviour and semantics as the implicit monitor lock accessed using synchronized methods and statements but with extended capabilities.

ReadWriteLock: Maintains a pair of associated locks, one for read-only operations and one for write operations. The read lock may be held simultaneously by multiple reader threads as long as there are no writers.

Countdown Latch: A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

Cyclic Barrier: A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.

Semaphore: A counting semaphore that can be used to control access to a resource with a fixed number of permits.

Exchanger: A synchronization point at which threads can pair and swap elements within pairs.

Executor Framework

ExecutorService: A higher-level replacement for working with threads directly. Allows managing a pool of threads.

ScheduledExecutorService: A service that can schedule commands to run after a given delay or to execute periodically.

Thread-Safe Collections

Blocking Queues

ArrayBlockingQueue: A bounded blocking queue backed by an array.

LinkedBlockingQueue: An optionally bounded blocking queue based on linked nodes.

PriorityBlockingQueue: An unbounded blocking queue that uses the same ordering rules as PriorityQueue.

DelayQueue: A time-based scheduling queue where elements cannot be accessed until a certain delay has expired.

SynchronousQueue: A blocking queue in which each insert operation must wait for a corresponding remove operation by another thread and vice versa.

Concurrent Collections

ConcurrentHashMap: A hash table supporting full concurrency of retrievals and adjustable expected concurrency for updates.

ConcurrentSkipListMap: A scalable concurrent NavigableMap implementation.

ConcurrentSkipListSet: A scalable concurrent NavigableSet implementation.

CopyOnWriteArrayList: A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array.

CopyOnWriteArraySet: A thread-safe variant of HashSet that uses an internal CopyOnWriteArrayList for all its operations.

Program

Demonstrating the use of ConcurrentHashMap and ReentrantLock to handle thread-safe operations in a banking scenario.

//Bank.java 
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
public class Bank {
    private final ConcurrentHashMap<String, Double> accounts = new ConcurrentHashMap<>();
    private final ReentrantLock lock = new ReentrantLock();
    public void addAccount(String accountId, double initialBalance) {
        accounts.put(accountId, initialBalance);
    }
    public void deposit(String accountId, double amount) {
        lock.lock();
        try {
            accounts.computeIfPresent(accountId, (id, balance) -> balance + amount);
            System.out.println(Thread.currentThread().getName() + " deposited " + amount + " to account " + accountId + ". New balance: " + accounts.get(accountId));
        } finally {
            lock.unlock();
        }
    }
    public void withdraw(String accountId, double amount) {
        lock.lock();
        try {
            accounts.computeIfPresent(accountId, (id, balance) -> {
                if (balance >= amount) {
                    return balance - amount;
                } else {
                    System.out.println(Thread.currentThread().getName() + " tried to withdraw " + amount + " from account " + accountId + " but insufficient balance. Current balance: " + balance);
                    return balance;
                }
            });
            System.out.println(Thread.currentThread().getName() + " withdrew " + amount + " from account " + accountId + ". New balance: " + accounts.get(accountId));
        } finally {
            lock.unlock();
        }
    }
    public double getBalance(String accountId) {
        return accounts.getOrDefault(accountId, 0.0);
    }
    public static void main(String[] args) {
        Bank bank = new Bank();
        bank.addAccount("12345", 1000);
        Runnable depositor = () -> {
            for (int i = 0; i < 5; i++) {
                bank.deposit("12345", 100);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Runnable withdrawer = () -> {
            for (int i = 0; i < 5; i++) {
                bank.withdraw("12345", 50);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread depositorThread1 = new Thread(depositor, "DepositorThread1");
        Thread depositorThread2 = new Thread(depositor, "DepositorThread2");
        Thread withdrawerThread1 = new Thread(withdrawer, "WithdrawerThread1");
        Thread withdrawerThread2 = new Thread(withdrawer, "WithdrawerThread2");
        depositorThread1.start();
        depositorThread2.start();
        withdrawerThread1.start();
        withdrawerThread2.start();
        try {
            depositorThread1.join();
            depositorThread2.join();
            withdrawerThread1.join();
            withdrawerThread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Final balance: " + bank.getBalance("12345"));
    }
}

/*
C:\>javac Bank.java

C:\>java Bank
DepositorThread1 deposited 100.0 to account 12345. New balance: 1100.0
DepositorThread2 deposited 100.0 to account 12345. New balance: 1200.0
WithdrawerThread2 withdrew 50.0 from account 12345. New balance: 1150.0
WithdrawerThread1 withdrew 50.0 from account 12345. New balance: 1100.0
DepositorThread1 deposited 100.0 to account 12345. New balance: 1200.0
DepositorThread2 deposited 100.0 to account 12345. New balance: 1300.0
DepositorThread2 deposited 100.0 to account 12345. New balance: 1400.0
DepositorThread1 deposited 100.0 to account 12345. New balance: 1500.0
WithdrawerThread2 withdrew 50.0 from account 12345. New balance: 1450.0
WithdrawerThread1 withdrew 50.0 from account 12345. New balance: 1400.0
DepositorThread2 deposited 100.0 to account 12345. New balance: 1500.0
WithdrawerThread1 withdrew 50.0 from account 12345. New balance: 1450.0
WithdrawerThread2 withdrew 50.0 from account 12345. New balance: 1400.0
DepositorThread1 deposited 100.0 to account 12345. New balance: 1500.0
DepositorThread1 deposited 100.0 to account 12345. New balance: 1600.0
DepositorThread2 deposited 100.0 to account 12345. New balance: 1700.0
WithdrawerThread2 withdrew 50.0 from account 12345. New balance: 1650.0
WithdrawerThread1 withdrew 50.0 from account 12345. New balance: 1600.0
WithdrawerThread1 withdrew 50.0 from account 12345. New balance: 1550.0
WithdrawerThread2 withdrew 50.0 from account 12345. New balance: 1500.0
Final balance: 1500.0
*/

Java offers a rich set of thread safety utilities and thread-safe collections to simplify multi-threaded programming and avoid concurrency issues. Utilities like Semaphore, CountDownLatch, CyclicBarrier, and Exchanger help coordinate and control thread execution efficiently. Meanwhile, thread-safe collections like Vector, Hashtable, and classes from java.util.concurrent (ConcurrentHashMap, CopyOnWriteArrayList, etc.) provide built-in mechanisms for safe access and modification across multiple threads without needing explicit synchronization.

By using these utilities and collections appropriately, developers can build high-performance, scalable, and robust multi-threaded applications with significantly reduced risk of data corruption, deadlocks, and race conditions.

Scroll to Top