Fundamentals of Concurrent Programming in Java

Process vs Thread

A process represents an executing instance of a program and forms the basic unit of execution managed by the operating system. It encompasses creation, execution, and termination phases. A thread, smaller than a process, operates within a process. Multiple threads can run concurrently inside one process and share its heap and method area, while each maintains its own program counter, JVM stack, and native method stack. In Java, threads act as the minimal scheduling units, whereas processes serve as resource containers.

Creating Threads in Java

Threads can be instantiated by extending java.lang.Thread and overriding run(), or by implementing java.lang.Runnable and passing it to a Thread constructor. Another approach uses Callable with FutureTask for tasks that return results.

Common Thread Operations

  • pauseAndYield:

    • Thread.sleep(millis) moves the current thread from running to timed waiting. It retains any held locks and may throw InterruptedException if interrupted. After waking, execution timing depends on the scheduler.
    • Thread.yield() transitions the thread from running to ready state, allowing other threads a chance to run; actual preemption relies on OS scheduling.
  • joinExample: Invoking targetThread.join(timeout) blocks the caller until targetThread finishes or the timeout elapses. If the target completes early, the caller proceeds immediately.

Daemon Threads

Daemon threads do not prevent JVM termination. When only daemon threads remain active, the process exits. Examples include the garbage collector and certain Tomcat acceptor/poller threads that cease when a shutdown command is issued.

Thread Lifecycle States

  1. New – Thread object created in JVM, not yet bound to OS thread.
  2. Runnable – Bound and eligible for CPU scheduling.
  3. Running – Actively executing on CPU.
  4. Blocked – Suspended due to blocked I/O or lock contention; not considered for scheduling until condition changes.
  5. Terminaetd – Execution finished, cannot transition further.

Differences Between sleep and wait

  • sleep belongs to Thread; wait belongs to Object.
  • sleep does not require synchronization; wait must be called within synchronized context.
  • sleep holds object locks; wait releases them.
  • Both can result in TIMED_WAITING when given a duration.

join Implementation Insight

The join method waits for the referenced thread’s completion:

public final synchronized void join(long timeoutMillis) throws InterruptedException {
    long start = System.currentTimeMillis();
    long elapsed = 0;
    if (timeoutMillis < 0) {
        throw new IllegalArgumentException("Timeout must be non-negative");
    }
    if (timeoutMillis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long remaining = timeoutMillis - elapsed;
            if (remaining <= 0) break;
            wait(remaining);
            elapsed = System.currentTimeMillis() - start;
        }
    }
}

A zero timeout causes indefinite waiting via wait(0). A positive timeout ensures that spurious wake-ups do not waste already-waited time by recomputing remaining wait period.

State Transition Scenarios

  • New → Runnable: Via start().
  • Runnable ↔ Waiting:
    • Enter waiting using synchronized(obj){ obj.wait(); }. Exit when obj.notify(), obj.notifyAll(), or thread interrupt occurs and lock is acquired; otherwise move to blocked.
    • Enter via join(): current thread waits on target’s monitor; exit when target ends or interrupted.
    • Enter via LockSupport.park(). Exit via unpark(target) or interrupt.
  • Runnable ↔ Timed_Waiting:
    • From wait(timeout), join(timeout), sleep(timeout), or LockSupport.parkNanos/parkUntil. Exit upon timeout, notification, interrupt, or unpark.
  • Runnable ↔ Blocked: Occurs on failure to acquire monitor in synchronized. On monitor release, contending threads re-compete; successful acquisition moves to runnable, others stay blocked.

Volatile Keyword

Applied to fields, volatile ensures reads bypass thread-local caches and fetch directly from main memory, guaranteeing visibility. Write operations insert a store barrier; reads insert a load barrier, preventing instruction reordering across these points.

Thread Pool Mechanics

State Representation

ThreadPoolExecutor encodes state and worker count in a single atomic integer ctl: upper 3 bits for state, lower 29 bits for pool size.

State Bits Accepts New Tasks Processes Queue Meaning
RUNNING 111 Yes Yes Normal operation
SHUTDOWN 000 No Yes No new tasks; queue processed
STOP 001 No No Interrupts running tasks; discards queued ones
TIDYING 010 All tasks done; workers zero; preparing to terminate
TERMINATED 011 Fully terminated

Construction Parameters

ThreadPoolExecutor(int coreThreads,
                    int maxThreads,
                    long keepAlive,
                    TimeUnit unit,
                    BlockingQueue<Runnable> taskQueue,
                    ThreadFactory factory,
                    RejectedExecutionHandler rejectPolicy)
  • coreThreads: Baseline number kept alive.
  • maxThreads: Upper limit of threads.
  • keepAlive: Idle time before reducing excess threads.
  • taskQueue: Holds pending tasks.
  • factory: Creates threads with custom naming.
  • rejectPolicy: Defines behavior when saturated.

Initially no threads exist; first task creates one. Once coreThreads busy, additional tasks enter taskQueue. If bounded queue fills, up to maxThreads - coreThreads extra threads spawn. Beyond that, rejectPolicy applies:

  • AbortPolicy: Throws RejectedExecutionException (default).
  • CallerRunsPolicy: Executes task in caller thread.
  • DiscardPolicy: Silently drops task.
  • DiscardOldestPolicy: Drops oldest queued task, enqueues new.

Custom strategies exist in frameworks like Dubbo (logs + thread dump), Netty (spawns new thread), ActiveMQ (retry with timeout), and PinPoint (policy chain).

Excess threads beyond coreThreads terminate after keepAlive inactivity.

Inter-thread Communication Techniques

Threads interact primarily through shared memory:

  • Shared Variables: Direct access requires synchronization to avoid race conditions. Files can also serve as shared communication medium.
  • Synchronization Primitives:
    • synchronized with wait/notify/notifyAll enables conditional coordination.
    • ReentrantLock with Condition provides similar signaling.
    • BlockingQueue implements producer-consumer patterns safely.
    • CountDownLatch lets threads await completion of multiple operations.
    • Semaphore restricts concurrent access to resources.
    • volatile enforces visibility and ordering guarantees.

Tags: java Concurrency multithreading Thread Pool Synchronization

Posted on Tue, 15 Sep 2026 16:25:34 +0000 by ruben-