Files
2026-06-12 23:53:54 +03:30

5.0 KiB
Raw Permalink Blame History

Ninth Assignment — Answers


⚛️ Atomic Variables & Synchronization


1 - What are atomic variables?

Atomic variables are variables that support lock-free, thread-safe operations. This means operations on them (such as increment, compare-and-set, etc.) are performed as a single indivisible step.

Their main purpose is to prevent race conditions when multiple threads access and modify shared data concurrently.

Difference from ordinary variables:

  • Ordinary variables (non-atomic):

    • Operations like x++ are NOT atomic.
    • They consist of multiple steps: read → modify → write.
    • This can lead to race conditions.
  • Atomic variables:

    • Provide built-in thread-safe operations.
    • Use low-level CPU instructions (like CAS Compare-And-Swap).
    • No need for explicit synchronization (like synchronized or locks).

2 - Atomic classes in java.util.concurrent.atomic

Examples of atomic classes:

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean
  • AtomicReference

Example use case: AtomicInteger

AtomicInteger is commonly used as a thread-safe counter.

Example:

  • Counting number of requests in a web server
  • Tracking successful operations across multiple threads

It avoids race conditions without using locks, improving performance under moderate contention.


3 - Locks vs Atomic Variables

Atomic Variables:

Advantages:

  • Faster (lock-free)
  • No blocking
  • Simple for single-variable operations

Limitations:

  • Only suitable for simple operations
  • Not useful for multi-step or multi-variable logic

Locks (synchronized, ReentrantLock):

Advantages:

  • Can protect complex operations
  • Allow coordination across multiple variables
  • Support conditions and waiting

Disadvantages:

  • Slower due to blocking
  • Can cause deadlocks if misused

When to use which?

  • Use Atomic Variables when:

    • You have simple operations (e.g., increment counter)
    • No need for coordination between multiple variables
  • Use Locks when:

    • Multiple shared variables are involved
    • You need atomic multi-step operations
    • You need waiting/notification mechanisms

🎯 Bonus Task — Example Program

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicDemo {
    static int normalCounter = 0;
    static AtomicInteger atomicCounter = new AtomicInteger(0);

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

        for (int i = 0; i < threads; i++) {
            workers[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    normalCounter++; // NOT thread-safe
                    atomicCounter.incrementAndGet(); // thread-safe
                }
            });
            workers[i].start();
        }

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

        System.out.println("Normal Counter: " + normalCounter);
        System.out.println("Atomic Counter: " + atomicCounter.get());
    }
}

Expected result:

  • atomicCounter will always be correct (10000)
  • normalCounter may be less due to race conditions

🔒 Locks & Concurrent Design


4 - Race-free but poor performance

A program can be completely correct (no race conditions) but still perform poorly due to high contention and synchronization overhead.

Reasons:

  1. High contention

    • Many threads compete for the same lock
    • Threads spend time waiting instead of doing work
  2. Excessive synchronization

    • Overuse of locks reduces parallelism
    • Even independent operations get blocked
  3. Lock granularity issues

    • Coarse-grained locks (one big lock)
    • Prevent multiple threads from working in parallel

5 - Why more threads ≠ better performance

Adding more threads can hurt performance due to:

1. Context Switching

  • CPU switches between threads
  • This has overhead and wastes time

2. Contention

  • Threads compete for shared resources (locks, memory)
  • More threads → more waiting

3. Cache Coherence

  • CPUs maintain consistency of cached data
  • Frequent updates cause cache invalidation
  • Slows down execution

4. Synchronization Overhead

  • Locks and coordination add extra cost
  • More threads → more synchronization

⚠️ Deadlocks


6 - Why deadlocks appear in production

Deadlocks depend on thread scheduling, which is:

  • Non-deterministic
  • Timing-dependent

In testing:

  • Fewer threads
  • Predictable execution

In production:

  • High concurrency
  • Different timing → circular waits may occur

Strategies to expose deadlocks:

  1. Stress testing

    • Run with many threads
    • Increase contention
    • Repeat tests multiple times
  2. Introduce artificial delays

    • Add sleep() between lock acquisitions
    • Makes timing issues more visible
    • Increases probability of deadlock