Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Java Multithreading Fundamentals and Concurrency Management

Tech May 17 1

Parallelism vs Concurrency

Parallelism refers to multiple threads executing simultaneously across different processors at the exact same moment. Concurrency involves rapid context switching between threads on a single processor, creating the illusion of simultaneous execution.

Thread Lifecycle States

Java threads transition through seven distinct states:

  1. New: Thread instance created via Runnable implementation or Thread extension
  2. Runnable: Thread ready for CPU allocation after start() invocation; also reached when time slice expires or yield() is called
  3. Running: Thread actively executing code upon CPU time slice acquisition
  4. Waiting: Thread enters this state via wait(), join(), or park() calls; requires explicit awakening through notify(), notifyAll(), or unpark()
  5. Timed Waiting: Similar to waiting but with automatic timeout awakening; triggered by wait(timeout), join(timeout), sleep(), or parkUntil()
  6. Blocked: Occurs when thread fails to acquire synchronization lock or makes I/O requests; thread placed in synchronization queue
  7. Terminated: Final state when thread completes execution or terminates due to exceptions

Thread Creation Methods

Four primary approaches exist for thread instantiation:

  1. Thread Extension: Subclass Thread and override run() method
  2. Runnable Implementation: Implement Runnable interface and override run() method
  3. Callable with FutureTask: Implement Callable interface with call() method returning values
  4. Thread Pool Usage: Reuse existing threads through ExecutorService frameworks

Threading Mechanisms

The start() method initiates true multithreading, allowing concurrent execution without blocking the calling thread. Direct run() invocation executes sequentially within the current thread.

Thread Pool Advantages

Thread pools provide resource efficiency through reuse, faster response times, and improved managemant capabilities. They prevent excessive thread creation that could destabilize system performance.

ThreadPoolExecutor Configuration

Manual thread pool creation offers precise control over parameters:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5,           // Core pool size
    10,          // Maximum pool size
    1L,          // Keep alive time
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(100),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

Key parameters include core pool size, maximum capacity, queue configuration, and rejection policies.

Atomic Operations

The java.util.concurrent.atomic package provides thread-safe operations without traditional locking:

  • Basic Types: AtomicInteger, AtomicLong, AtomicBoolean
  • Arrays: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray
  • References: AtomicReference, AtomicStampedReference

AtomicInteger example demonstrating common operations:

AtomicInteger counter = new AtomicInteger(0);
int currentValue = counter.get();                    // Get current value
int previous = counter.getAndSet(5);                 // Set new value
int incremented = counter.getAndIncrement();         // Increment operation
boolean success = counter.compareAndSet(5, 10);      // Compare and swap

Synchronization Primitives

CAS (Compare-And-Swap)

CAS operations prevent ABA issues through version stamping, ensuring data integrity during concurrent modifications.

AQS (Abstract Queued Synchronizer)

AQS forms the foundation for many concurrency utilities:

private volatile int state;

protected final int getState() { return state; }
protected final void setState(int newState) { state = newState; }
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

Synchronized Mechanism

Synchronized blocks utilize monitorenter/monitorexit bytecode instructions for method and block-level locking. Post-JDK 1.6 optimizations include biased, lightweight, and heavyweight lock states.

Example of double-checked locking pattern:

public class Singleton {
    private volatile static Singleton instance;
    
    public static Singleton getSingleton() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Lock Implementations

ReentrantLock provides explicit locking with fairness options:

Lock lock = new ReentrantLock(true);  // Fair lock
lock.lock();
try {
    // Critical section
} finally {
    lock.unlock();
}

Memory Visibility

Volatile variables ensure immediate synchronization with main memory and prevent instruction reordering:

private volatile boolean flag = false;
private volatile int counter = 0;

Thread Communication

Key differences between sleep/yield and wait/join mechanisms:

  • sleep(): Pauses current thread without releasing locks
  • yield(): Temporarily surrenders CPU time slice
  • wait(): Releases lock and waits for notification
  • join(): Waits for target thread completion

Lock Classification

Optimistic vs Pessimistic

Optimistic locking assumes minimal conflict and uses CAS operations. Pessimistic locking anticipates conflicts and employs exclusive access.

Fairness Policies

Fair locks follow FIFO ordering while unfair locks allow opportunistic acquisition:

Lock fairLock = new ReentrantLock(true);
Lock unfairLock = new ReentrantLock(false);

Performance Optimization

Spin Locks

Lightweight synchronization for short-duration critical sections where context switching overhead exceeds spin duration.

Lock Elimination

JVM optimization removes unnecessary synchronization for thread-local objects:

public String concatenate(String s1, String s2) {
    StringBuffer buffer = new StringBuffer();
    buffer.append(s1);
    buffer.append(s2);
    return buffer.toString();  // Synchronization eliminated
}

Thread-Local Storage

ThreadLocal provides per-thread variable isolation:

private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

threadLocal.set("thread-specific-value");
String value = threadLocal.get();
threadLocal.remove();  // Prevent memory leaks

Blocking Queue Implementation

ArrayBlockingQueue demonstrates producer-consumer patterns:

ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
queue.put(1);    // Blocking put
Integer item = queue.take();  // Blocking take

Thread Pool Tuning

Optimal thread pool sizing depends on workload characteristics:

  • CPU-bound tasks: Threads ≈ CPU cores + 1
  • I/O-bound tasks: Threads ≈ 2 × CPU cores + 1

Formula: Thread Count = CPU Cores × Target Utilization × (1 + Wait Time / Service Time)

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

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