Логотип Workflow

Article

Multithreading Basics

Topic 8. Multithreading Basics: core concepts of concurrency and threads

This topic is not Java-only. Concurrency is a universal systems concept used in servers, browsers, mobile apps, databases, and operating systems.

What multitasking means

Multitasking means multiple tasks make progress in overlapping time.

  1. Concurrency: tasks are interleaved and progress together.
  2. Parallelism: tasks execute truly at the same time on multiple CPU cores.

Simple analogy:

  • Concurrency: one cook switches between dishes.
  • Parallelism: several cooks work at once.

Concurrency models

Process vs Thread

  • Process: isolated running program instance with its own memory space.
  • Thread: execution path inside a process.

Threads in one process share memory. This is efficient but introduces shared-state risks.

Why execution appears simultaneous

Even on one CPU core, OS scheduler rapidly rotates threads using time slices.

Time slice scheduling

Each thread gets a short CPU quantum, then scheduler switches to another thread.

Why concurrency is hard

Difficulty is not starting threads; it is preserving correctness with shared data.

Typical failure classes:

  1. Race condition: result depends on timing/interleaving.
  2. Visibility issue: one thread updates value, another thread does not observe it reliably.
  3. Deadlock: threads block each other permanently.
  4. Starvation: one thread rarely gets resources.
  5. Livelock: threads keep reacting but make no real progress.

Race condition example

private int counter = 0;

public void increment() {
    counter++; // not atomic
}

counter++ includes read, compute, write steps. Interleaving between threads can lose updates.

Race vs lock

Synchronization approaches

1. synchronized

private int counter = 0;

public synchronized void increment() {
    counter++;
}

Benefits:

  • mutual exclusion for critical section,
  • visibility guarantees between threads.

2. Lock (ReentrantLock)

private final Lock lock = new ReentrantLock();
private int counter = 0;

public void increment() {
    lock.lock();
    try {
        counter++;
    } finally {
        lock.unlock();
    }
}

Benefits:

  • explicit lock lifecycle,
  • richer APIs (tryLock, timeout patterns).

3. Atomic types

private final AtomicInteger counter = new AtomicInteger(0);

public void increment() {
    counter.incrementAndGet();
}

Useful for simple lock-free counters.

Thread, Runnable, and ExecutorService in Java

Thread + Runnable

Runnable task = () -> System.out.println("Work");
Thread t = new Thread(task);
t.start();

Good for basics, not ideal for scalable production scheduling.

ExecutorService (thread pools)

ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> doWork("A"));
pool.submit(() -> doWork("B"));
pool.shutdown();

Benefits:

  1. avoids creating a fresh thread per task,
  2. stabilizes runtime behavior under load,
  3. centralizes task lifecycle management.

CPU-bound vs IO-bound workloads

  • CPU-bound: dominated by computation.
  • IO-bound: dominated by waiting (network/disk/DB).

Why this matters:

  • CPU-bound workloads usually need limited thread count near core count.
  • IO-bound workloads may benefit from larger pools because many threads wait on I/O.

Safe concurrent design rules

  1. Minimize shared mutable state.
  2. Prefer immutable objects.
  3. Keep critical sections short.
  4. Use concurrent structures when needed (ConcurrentHashMap, BlockingQueue).
  5. Prioritize correctness before optimization.

Debugging workflow

Concurrency bugs are often nondeterministic.

Practical steps:

  1. Run stress tests with repeated concurrent execution.
  2. Log state transitions with thread ids.
  3. Inspect thread dumps on hangs.
  4. Analyze lock contention hotspots.

Key takeaway

Concurrency is mainly a correctness discipline. The most reliable design is usually the one with the least shared mutable state.

Quiz

Check what you learned

Please login to pass quizzes.