Files

4.7 KiB
Raw Permalink Blame History

Theory Answers Concurrent Banking System

1. Atomic Variables & Synchronization

1.1 What are atomic variables?

Atomic variables are special types that support lockfree, threadsafe operations on a single variable. They guarantee that certain compound actions (like readmodifywrite) are performed as one indivisible step no other thread can see an intermediate state.

Why do we need them?
In a multithreaded environment, a plain int counter shared among threads can produce wrong results because counter++ is actually three separate steps: read, add, write. Without proper synchronization, threads can interleave these steps and lose updates. Atomic variables solve this using lowlevel hardware support (e.g., CompareAndSwap, CAS) without the overhead of locks.

How they differ from ordinary variables:
Ordinary variables offer no builtin guarantees about visibility or atomicity. If multiple threads access them without external synchronization, you get data races and inconsistent values. Atomic variables, on the other hand, ensure that each operation is applied completely before any other thread can see a change and they also enforce memory visibility (happensbefore relationships).


1.2 Four classes from java.util.concurrent.atomic

  • AtomicInteger for int values.
  • AtomicLong for long values.
  • AtomicBoolean for boolean flags.
  • AtomicReference<V> for object references.

Typical use case for AtomicInteger:
Imagine a web server that counts the total number of processed requests. Every worker thread increments this counter after handling a request. Using AtomicInteger with incrementAndGet() guarantees that the final count is always accurate, no matter how many threads are running concurrently, and it does so without blocking.


1.3 Locks vs. Atomic Variables when to use which?

Aspect Atomic Variables Locks (e.g., synchronized, ReentrantLock)
Overhead Very low (CAS is usually a single CPU instruction) Higher (context switching, queue management)
Complexity Best for simple updates on a single variable Needed for compound actions across multiple resources
Blocking behaviour Nonblocking retry on failure Blocking thread waits if lock is held
Flexibility Limited to atomic operations on one variable Can handle multiple conditions, timeouts, fair ordering

When a lock is a better choice:

  • When you need to update several related variables together (e.g., transfer money between two accounts both must be locked).
  • When you need conditional waiting (e.g., wait until the balance is sufficient).
  • When the contention is low and lock overhead is negligible compared to the work done inside the critical section.

When an atomic variable is preferable:

  • For simple counters, accumulators, or status flags.
  • When you have many threads and you want to avoid blocking and reduce latency.
  • When you are building nonblocking data structures.

🎯 Bonus Task Demonstration Program

Below is a small Java program that launches multiple threads to increment both a plain int and an AtomicInteger. Run it and youll see the plain integer often ends up with a value less than the expected total, while the AtomicInteger always gives the correct result clearly showing the race condition.

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicDemo {
    private static int plainCounter = 0;
    private static AtomicInteger atomicCounter = new AtomicInteger(0);
    private static final int THREADS = 10;
    private static final int INCREMENTS_PER_THREAD = 1000;

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[THREADS];

        for (int i = 0; i < THREADS; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < INCREMENTS_PER_THREAD; j++) {
                    plainCounter++;               // Not atomic
                    atomicCounter.incrementAndGet(); // Atomic
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) {
            t.join();
        }

        System.out.println("Plain counter final value: " + plainCounter);
        System.out.println("Atomic counter final value: " + atomicCounter.get());
        // Expected: 10000 (10 * 1000)
    }
}