Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Java Multithreading Explained

Notes 1

Threads and Processes

  • A process is an instance of a running program, representing a self-contained execution environment with its own memory space and resources.
  • A thread is the smallest unit of execution scheduled by the OS. Threads exist within a process, sharing its memory and resources but having their own stack and program counter.

A process is the OS’s basic unit of resource allocation, while a thread is the basic unit of CPU scheduling. A process contains atleast one thread, and multiple threads within a process can execute concurrently, sharing data and performing different tasks.

Java programs are inherently multithreaded: launching a Java application starts at least a main thread and a garbage collection thread, with threads executing tasks in parallel.

Parallelism, Serial, and Concurrency

  • Parallelism: On a multi-core CPU, multiple threads execute simultaneously (no resource contention).
  • Serial: On a single-core CPU, a single thread executes tasks sequentially (no parallel execution).
  • Concurrency: Multiple threads compete for a single CPU, executing in an interleaved manner (CPU switches rapidly, appearing simultaneous to users).
Single Thread Multi-Thread
Single CPU Serial Concurrent
Multi-CPU Serial Parallel

Synchronization vs. Asynchronization

  • Synchronization: Tasks execute in sequence (e.g., using synchronized or Lock to ensure ordered access to shared resources).
  • Asynchronization: Tasks execute independently (e.g., via callbacks, Future, or CompletableFuture).
Single Thread Multi-Thread
Sync Sequential execution Coordinated execution
Async Simulated via event loops Parallel execution via thread pools

Daemon Threads

Daemon threads (e.g., garbage collection) serve other threads and terminate when no user threads remain. Example:

public class DaemonDemo {
    public static void main(String[] args) {
        int i = 0;
        while (true) {
            Thread daemon = new Thread(() -> {
                System.out.println("Thread started: " + Thread.currentThread().getName());
            });
            daemon.setDaemon(i == 3);
            daemon.start();
            boolean isDaemon = daemon.isDaemon();
            System.out.println("Is daemon? " + isDaemon);
            if (isDaemon) break;
            i++;
        }
    }
}

Thread Safety

Thread safety ensures shared resources are accessed/modified without data corruption. Issues arise from:

  • Atomicity: Interrupted operations (e.g., count++ is non-atomic).
  • Visibility: Stale cache values (e.g., non-volatile variables).
  • Ordering: Reordered instructions (e.g., JVM/CPU optimizations).

Thread States

Java threads have 6 states (via Thread.State):

  1. NEW: Created but not started.
  2. RUNNABLE: Executing or ready to execute.
  3. BLOCKED: Waiting for a monitor lock.
  4. WAITING: Waiting indefinitely (e.g., Object.wait(), Thread.join()).
  5. TIMED_WAITING: Waiting with a timeout (e.g., Thread.sleep()).
  6. TERMINATED: Completed execution.

Thread Methods

Key methods:

  • start(): Launches a thread (calls run()).
  • run(): Defines thread logic (override or pass via Runnable).
  • sleep(): Pauses execution (no lock release).
  • join(): Waits for a thread to finish.
  • wait()/notify(): Coordinates thread execution (requires synchronization).

Creating Threads

Thread Class

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread running via Thread class");
    }
}
// Usage:
MyThread t = new MyThread();
t.start();

Runnable Interface

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

Callable and Future

FutureTask<String> future = new FutureTask<>(() -> {
    Thread.sleep(1000);
    return "Callable result";
});
new Thread(future).start();
String result = future.get(); // Blocks until complete

Thread Pool

ExecutorService pool = new ThreadPoolExecutor(
    2, 5, 1, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(3),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.CallerRunsPolicy()
);
pool.execute(() -> System.out.println("Task in pool"));
pool.shutdown();

Locks

ReentrantLock (Fair/Non-Fair)

ReentrantLock fairLock = new ReentrantLock(true);
ReentrantLock nonFairLock = new ReentrantLock(false);

ReadWriteLock

ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();

CAS (Compare-And-Swap)

AtomicInteger atomic = new AtomicInteger(0);
atomic.compareAndSet(0, 1); // Atomically updates if current value is 0

Java Memory Model (JMM)

JMM ensures atomicity, visibility, and ordering:

  • Atomicity: Operations like synchronized blocks are uninterruptible.
  • Visibility: volatile variables are immediately visible to other threads.
  • Ordering: volatile and synchronized prevent instruction reordering.

volatile Keyword

volatile boolean flag = false;
// Ensures visibility and ordering

synchronized Keyword

synchronized void syncMethod() {
    // Thread-safe block
}

J.U.C. Package

AQS (AbstractQueuedSynchronizer)

Used by ReentrantLock, CountDownLatch, etc., to manage thread queues.

CountDownLatch

CountDownLatch latch = new CountDownLatch(3);
// Threads call latch.countDown();
latch.await(); // Waits for count to reach 0

CyclicBarrier

CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All threads ready"));
// Threads call barrier.await();

Semaphore

Semaphore sem = new Semaphore(2); // Allows 2 concurrent threads
sem.acquire(); // Blocks if no permits
sem.release(); // Releases a permit

ThreadLocal

ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Default");
threadLocal.set("Thread-specific");
String value = threadLocal.get();
threadLocal.remove(); // Prevent memory leaks

Thread-Safe Collections

CopyOnWriteArrayList

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("Thread-safe");

ConcurrentHashMap

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.