Skip to content
Bible, Lee, Data
Engineering

Processes, Threads, and Operating System Execution Management

A program stored on a computer is not yet an active task.

An executable file contains instructions and data, but it does not use the CPU or maintain an execution state until someone runs it. When a user launches the program, the operating system loads it into memory, assigns resources, creates management structures, and prepares it for execution.

The running instance is called a process.

A single process may contain several flows of execution. A web browser can render a page, receive data from the network, respond to user input, and download a file at the same time. A server application may need to handle many client requests concurrently.

Each independently scheduled flow of execution inside a process is called a thread.

The distinction is often summarized as follows:

Process
A program in execution

Thread
A flow of execution within a process

That definition is correct, but it does not explain the whole system.

To understand processes and threads properly, we also need to ask:

  • How does the operating system distinguish running programs?
  • Which resources belong to a process?
  • Why do several programs appear to run simultaneously on one CPU?
  • Which resources do threads share, and which resources remain private?
  • Why is context switching necessary?
  • What happens when several threads modify the same data?
  • How do mutexes and semaphores differ?
  • Under what conditions does a deadlock occur?
  • How do these concepts appear in Java and server applications?

This article follows the complete path from launching a program to scheduling threads, protecting shared data, and resolving execution conflicts.


Programs and processes are not the same thing

A program is a static collection of instructions and data stored on a storage device.

Examples include:

chrome.exe
java
my-server.jar
calculator

Before execution, the program has no current instruction position, CPU register values, runtime stack, or active resources.

A process, by contrast, is a dynamic execution instance managed by the operating system.

Program
Executable code stored on disk

Process
A running instance loaded into memory

Launching the same program several times can create several independent processes.

Suppose Isaac opens a text editor twice.

Text editor program
→ Process P1
→ Process P2

Both processes originate from the same executable, but each has its own execution state and address space.

Editing a document in P1 does not automatically modify the memory of P2. If P1 crashes, P2 may continue running.

A process therefore includes much more than executable code:

  • program instructions
  • current execution state
  • virtual address space
  • CPU register values
  • open files
  • input and output resources
  • security and ownership information
  • scheduling information
  • one or more threads
  • a process identifier

The operating system uses this information to isolate, schedule, pause, resume, and terminate processes.


What the operating system does when a process is created

When a user launches a program, the operating system performs a sequence similar to the following:

1. Locate and validate the executable
2. Allocate a process identifier
3. Create process-management structures
4. Build a virtual address space
5. Map or load program code and data
6. Initialize the stack and heap
7. Connect required files and devices
8. Create the initial thread
9. Place the process in a ready queue
10. Wait for CPU scheduling

Creating a process does not mean it immediately begins executing.

Several processes may already be waiting for the CPU. The scheduler chooses when the new process receives CPU time.


The memory structure of a process

A process address space is commonly described using several logical regions.

Higher addresses
┌──────────────────┐
│      Stack       │
│        ↓         │
├──────────────────┤
│                  │
│    Free Space    │
│                  │
├──────────────────┤
│        ↑         │
│       Heap       │
├──────────────────┤
│    Data / BSS    │
├──────────────────┤
│       Code       │
└──────────────────┘
Lower addresses

The exact layout varies by operating system, runtime, executable format, and architecture, but the conceptual division remains useful.


Code segment

The code segment contains the machine instructions executed by the CPU.

For example:

public int add(int a, int b) {
    return a + b;
}

The compiled instructions for this method ultimately reside in executable memory.

Code pages are often protected as read-only so that normal execution cannot modify them accidentally. Several processes running the same program may share physical code pages while still observing separate virtual address spaces.


Data segment

The data area stores global and static data.

Initialized and uninitialized data may be placed in separate regions depending on the executable format.

public final class ApplicationState {

    private static int requestCount = 0;
}

In Java, the JVM manages class metadata and runtime objects through its own internal memory structures. From the operating system’s perspective, however, all of this still exists inside the JVM process.


Heap

The heap is used for dynamically allocated objects.

In Java, objects created with new are generally stored in the JVM heap.

Order order = new Order();

The heap is shared by threads within the same process.

That sharing is convenient, but it also means that multiple threads may access the same object concurrently. Shared mutable objects therefore require careful synchronization.

Java’s garbage collector reclaims objects that are no longer reachable. This management occurs inside the memory assigned to the JVM process.


Stack

A stack stores information related to method and function calls.

It commonly contains:

  • parameters
  • local variables
  • return addresses
  • saved register values
  • stack frames
public int calculate(int price) {
    int tax = price / 10;
    return price + tax;
}

Variables such as price and tax may be stored in the current execution flow’s stack frame.

Each thread has its own stack.

If two threads call the same method at the same time, each thread maintains its own parameters, local variables, and call sequence.

This is one of the most important differences between shared process memory and thread-specific execution state.


The Process Control Block stores process state

The operating system manages each process through a structure commonly called a Process Control Block, or PCB.

If a process is paused so that another process can use the CPU, the operating system must preserve enough information to continue the first process later.

The PCB stores that information.

Typical PCB contents include:

CategoryExamples

Identification Process ID, parent process ID, user ID
Process state New, ready, running, waiting, terminated
CPU context Program counter, stack pointer, registers
Scheduling data Priority, queue links, CPU usage
Memory data Page-table references, address-space information
I/O data Open files, assigned devices
Accounting data Execution time, resource usage
Relationships Parent-child relationships, signals

When a process loses the CPU, its register values and current instruction position are saved.

When the process is scheduled again, those values are restored, allowing execution to continue from the point where it stopped.


Process states and transitions

A process is not always running on the CPU.

A common five-state model includes:

New
→ Ready
→ Running
→ Waiting
→ Ready
→ Running
→ Terminated

New

The process is being created.

The operating system may be:

  • assigning a PID
  • creating a PCB
  • preparing the address space
  • mapping the executable
  • initializing resources

After initialization, the process moves to the ready state.


Ready

The process has everything it needs to run except CPU time.

Prepared to execute
Waiting for a CPU

Ready processes wait in one or more scheduling queues.

When the scheduler selects a process, it moves to the running state.


Running

The process or one of its threads is currently executing instructions on a CPU core.

At a given instant, one CPU core executes one instruction stream. A multicore processor can execute multiple threads or processes physically in parallel.


Waiting or blocked

A process enters the waiting state when it cannot continue until some event occurs.

Examples include:

  • waiting for a disk read
  • waiting for a network response
  • waiting for user input
  • waiting for a lock
  • waiting for a timer
  • waiting for another process

A blocked process would make no progress even if it were given the CPU.

The operating system therefore schedules another ready process.

When the event completes, the blocked process generally moves back to the ready state. It does not necessarily begin running immediately; it must compete for CPU time again.


Terminated

The process has finished or has been forcibly stopped.

The operating system may then:

  • reclaim memory
  • close files
  • release devices
  • preserve an exit code
  • notify the parent process
  • remove process-management information

Some systems retain limited termination information until the parent process acknowledges it.


Major process-state transitions

TransitionMeaning

New → Ready Process initialization completed
Ready → Running Scheduler assigned a CPU
Running → Ready Time slice expired or process was preempted
Running → Waiting Process requested I/O or another event
Waiting → Ready The required event completed
Running → Terminated Process finished or was stopped

Operating-system exam questions often test whether a transition is possible.

A blocked process normally moves to ready, not directly to running, when its event completes.


A thread is an execution flow inside a process

A process is often described as a unit of resource ownership and protection.

A thread is more closely associated with CPU execution and scheduling.

Every process contains at least one thread.

Process
├── Thread 1
├── Thread 2
└── Thread 3

The first thread is normally created when the process starts. Additional threads can be created as needed.

A web browser might use separate execution flows for:

  • handling input
  • rendering the page
  • performing network communication
  • downloading files
  • running background calculations

A server may use different worker threads for different requests.

Request 1 → Worker thread A
Request 2 → Worker thread B
Request 3 → Worker thread C

While one request waits for database or network I/O, another thread can continue processing useful work.


What processes and threads own

A process owns an address space and operating-system resources.

Threads inside that process share many of those resources, while maintaining their own execution context.

Resources generally associated with the process

  • code
  • global and static data
  • heap
  • open files
  • sockets
  • virtual address space
  • process permissions
  • process-level operating-system resources

Information generally private to each thread

  • program counter
  • CPU registers
  • stack
  • thread identifier
  • scheduling state
  • thread-local storage

The relationship can be visualized as follows:

Process
┌──────────────────────────────┐
│ Code                         │
│ Global data                  │
│ Heap                         │
│ Open files and sockets       │
│                              │
│  Thread A       Thread B     │
│ ┌─────────┐    ┌─────────┐   │
│ │Registers│    │Registers│   │
│ │PC       │    │PC       │   │
│ │Stack    │    │Stack    │   │
│ └─────────┘    └─────────┘   │
└──────────────────────────────┘

The key differences between processes and threads

CriterionProcessThread

Meaning Running program instance Execution flow within a process
Address space Normally isolated Shared with other threads in the process
Heap and global state Separate by process Shared within the process
Stack Each process contains thread stacks Each thread has its own stack
Creation cost Relatively high Relatively low
Context switching Usually heavier Usually lighter within one process
Communication Requires IPC Can communicate through shared memory
Failure isolation Stronger Weaker
Security boundary Clearer Shares process privileges
Synchronization Needed for shared IPC resources Essential for shared mutable state

Processes provide strong isolation, but they are more expensive to create and communicate with.

Threads are lightweight and share data efficiently, but their shared memory makes synchronization more difficult.


Processes are isolated, but they still need to communicate

Separate processes generally cannot directly read or write one another’s private memory.

This protects stability and security. A memory error in one process is less likely to corrupt another process.

Processes still need to cooperate, however.

They use Inter-Process Communication, or IPC.

Common IPC mechanisms include:

  • pipes
  • named pipes
  • message queues
  • shared memory
  • sockets
  • signals
  • memory-mapped files
  • remote procedure calls

Pipes

A pipe transfers the output of one process to the input of another.

For example:

cat access.log | grep ERROR

The output of cat becomes the input of grep.

Pipes are simple and useful for streaming data between related commands or processes.


Message queues

A sender places messages into a queue, and a receiver consumes them later.

The sender and receiver do not always need to run at exactly the same time.

Message queues provide a clearer communication boundary than direct shared-memory access, though they introduce messaging and serialization overhead.


Shared memory

Shared memory maps the same physical memory into multiple process address spaces.

It can be very fast because it avoids repeated data copying.

The trade-off is that processes can now access the same mutable state. Separate synchronization mechanisms are required to prevent races.


Sockets

Sockets allow processes to communicate either on the same machine or across a network.

Web clients and servers communicate through sockets.

Sockets provide strong architectural separation but require protocol design, data encoding, error handling, and connection management.

IPC is generally more explicit and more expensive than communication between threads in one process. Its advantage is stronger isolation and clearer boundaries.


Multiprocessing and multithreading

A workload can be divided across multiple processes or multiple threads.


Multiprocessing

Multiprocessing uses several processes.

Workload
├── Process A
├── Process B
└── Process C

Advantages include:

  • strong memory isolation
  • better fault containment
  • separate security boundaries
  • support for different runtimes or languages
  • independent resource management

Costs include:

  • higher creation overhead
  • greater memory usage
  • IPC complexity
  • serialization and copying
  • heavier context switches

Modern browsers often separate rendering work into multiple processes. A crash in one page can then be prevented from terminating the entire browser.


Multithreading

Multithreading uses several threads within one process.

Process
├── Thread A
├── Thread B
└── Thread C

Advantages include:

  • lower creation cost
  • shared code and heap
  • simple in-process data exchange
  • useful overlap while one thread waits for I/O
  • lower switching overhead in many cases

Costs include:

  • race conditions
  • synchronization requirements
  • nondeterministic execution order
  • difficult debugging
  • deadlocks
  • starvation
  • weak failure isolation

The correct choice depends on the desired balance between isolation, communication cost, and shared-state complexity.


Concurrency and parallelism are different

Concurrency and parallelism are related but not identical.

Concurrency

Concurrency means that several tasks make progress during overlapping periods of time.

A single CPU core can alternate rapidly between threads.

Time →
Thread A: Running ─ Waiting ─ Running
Thread B: Waiting ─ Running ─ Waiting

Only one thread may execute at a particular instant, but several tasks progress during the same broader interval.

Parallelism

Parallelism means that multiple tasks execute physically at the same moment.

CPU core 1 → Thread A
CPU core 2 → Thread B

A useful distinction is:

Concurrency
A structure for managing overlapping work

Parallelism
Actual simultaneous execution

Concurrency is possible on a single core.

Parallelism normally requires multiple cores or execution units.


Context switching changes the active execution flow

A CPU alternates between processes and threads.

Saving the current execution state and restoring another execution state is called a context switch.

Run P1
→ Save P1's registers and program counter
→ Restore P2's saved state
→ Run P2

A context switch generally includes:

  1. saving the current CPU state
  2. updating scheduling and process information
  3. selecting the next execution unit
  4. restoring the next execution unit’s state
  5. switching memory-management context if necessary
  6. resuming execution

A context switch does not perform application work.

It is management overhead.

Too many context switches can therefore reduce useful CPU throughput.


Why process switches are often more expensive

Switching between processes may require changing the active address space.

Potential costs include:

  • switching page-table context
  • affecting the Translation Lookaside Buffer
  • losing CPU cache locality
  • updating security boundaries
  • changing process-specific kernel data
  • reloading memory-management state

Threads within the same process share an address space, so their switches may avoid some of these costs.

A thread switch is still not free. Registers and stack pointers must be changed, and cache locality may still be reduced.


CPU scheduling selects the next task to run

When several execution units are ready, the operating system must choose one.

This is CPU scheduling.

Common scheduling goals include:

  • high CPU utilization
  • high throughput
  • low turnaround time
  • low waiting time
  • low response time
  • fairness
  • priority support
  • starvation prevention
  • real-time guarantees

These goals can conflict.

An interactive system values quick response, while a batch system may prioritize total throughput.


Preemptive and non-preemptive scheduling

Non-preemptive scheduling

Once a process receives the CPU, it continues until it:

  • terminates
  • blocks for an event
  • voluntarily yields

The operating system does not forcibly remove the CPU during normal execution.

Advantages:

  • fewer context switches
  • simpler implementation
  • more predictable execution

Disadvantages:

  • a long task may monopolize the CPU
  • interactive tasks may respond slowly
  • high-priority work may need to wait

FCFS and non-preemptive SJF are common textbook examples.


Preemptive scheduling

The operating system can interrupt the running task and assign the CPU to another task.

Preemption may occur when:

  • a time slice expires
  • a higher-priority task becomes ready
  • a real-time event occurs
  • the scheduler rebalances CPU usage

Advantages:

  • better interactive response
  • fairer CPU distribution
  • better priority handling

Disadvantages:

  • more context-switch overhead
  • more complex shared-state protection
  • less predictable execution order

Round Robin and preemptive priority scheduling are common examples.


Major CPU scheduling algorithms

First Come, First Served

FCFS runs processes in arrival order.

Arrival order
P1 → P2 → P3

Execution order
P1 → P2 → P3

Advantages:

  • simple
  • fair by arrival time
  • no starvation

Disadvantages:

  • a long process can delay many short processes
  • average waiting time may become large
  • poor interactive responsiveness

When a long task causes many short tasks to wait behind it, the result is called the convoy effect.


Shortest Job First

SJF selects the process with the shortest expected CPU burst.

P1 expected burst: 8
P2 expected burst: 2
P3 expected burst: 4

Execution order
P2 → P3 → P1

If future execution times were known exactly, SJF could minimize average waiting time.

In practice, burst duration must be estimated.

Long processes may suffer starvation if shorter processes continue arriving.


Shortest Remaining Time First

SRTF is the preemptive version of SJF.

The process with the shortest remaining execution time runs.

If a new process arrives with a shorter remaining time, it may preempt the current process.

SRTF can improve response and average waiting time, but it increases preemption and may starve long tasks.


Priority scheduling

The scheduler selects the highest-priority process.

Whether a smaller or larger number means higher priority depends on the system.

P1 priority: High
P2 priority: Low
P3 priority: Medium

Execution order
P1 → P3 → P2

Priority scheduling can be preemptive or non-preemptive.

Low-priority tasks may starve.

Aging addresses this by gradually increasing the priority of processes that have waited for a long time.


Round Robin

Round Robin assigns each ready process a fixed time quantum.

When the quantum expires, the running process moves to the back of the ready queue.

Time quantum: 2

P1 → P2 → P3 → P1 → P2 → ...

Advantages:

  • fair CPU sharing
  • good interactive response
  • prevents one process from monopolizing the CPU

Disadvantages:

  • a very small quantum creates too many context switches
  • a very large quantum behaves like FCFS
  • all tasks are treated similarly regardless of workload characteristics

The time-quantum size strongly affects both responsiveness and efficiency.


Multilevel queues and multilevel feedback queues

Operating systems may maintain several ready queues.

High-priority queue
Interactive work

Medium-priority queue
General work

Low-priority queue
Batch work

In a multilevel queue, a process may remain permanently assigned to one queue.

In a multilevel feedback queue, processes can move between queues based on their behavior.

A CPU-intensive process may move downward, while short or I/O-oriented work receives faster service in a higher-priority queue.

Modern operating systems use scheduling policies more complex than any single textbook algorithm, but these foundational models explain the main trade-offs.


CPU-bound and I/O-bound workloads

Processes can be classified by how they use resources.

CPU-bound work

CPU-bound tasks spend long periods performing calculations.

Examples include:

  • video encoding
  • encryption
  • compression
  • scientific simulation
  • large numerical calculations

Their CPU bursts tend to be long.

I/O-bound work

I/O-bound tasks use the CPU briefly and then wait for external operations.

Examples include:

  • HTTP request processing
  • file access
  • database queries
  • user input
  • network communication

Their CPU bursts are short, while their waiting periods may be long.

The operating system overlaps these workloads so that the CPU can process another task while an I/O-oriented task waits.


User-level and kernel-level threads

Threads can also be classified by who manages them.


User-level threads

A runtime or user-space library manages user-level threads.

The kernel may not directly know about every individual user thread.

Advantages:

  • fast creation and switching
  • application-specific scheduling
  • less kernel involvement

Disadvantages:

  • one blocking operation may affect the entire process in some models
  • the kernel may not schedule individual user threads across multiple cores
  • runtime and kernel scheduling may not align perfectly

Kernel-level threads

Kernel-level threads are directly recognized and scheduled by the operating system.

Advantages:

  • if one thread blocks, another thread may continue
  • threads can run on different CPU cores
  • per-thread priorities can be managed by the operating system

Disadvantages:

  • creation and switching require kernel involvement
  • per-thread management cost is higher

Mapping user threads to kernel threads

Many-to-one

Several user threads map to one kernel thread.

User thread A ─┐
User thread B ─┼→ Kernel thread 1
User thread C ─┘

A blocking operation may block all user threads, and true multicore parallelism is limited.


One-to-one

Each user thread maps to one kernel thread.

User thread A → Kernel thread A
User thread B → Kernel thread B
User thread C → Kernel thread C

This model supports parallel execution well, but a very large thread count creates operating-system overhead.

Traditional Java platform threads are commonly understood through this model.


Many-to-many

Many user-level tasks are scheduled over several kernel threads.

Many user threads
↕
Several kernel threads

This model combines a large number of lightweight logical tasks with multicore execution.

It provides useful background for understanding coroutines, fibers, and modern lightweight-thread runtimes.


Java platform threads and virtual threads

A traditional Java Thread is a platform thread closely associated with an operating-system thread.

Thread thread = new Thread(() -> {
    System.out.println("Running task");
});

thread.start();

Creating too many platform threads can increase:

  • stack-memory usage
  • operating-system thread-management cost
  • context-switching overhead
  • scheduler pressure

Virtual threads became a standard Java feature in Java 21.

Thread.startVirtualThread(() -> {
    System.out.println("Running virtual-thread task");
});

Virtual threads are lightweight threads managed by the JVM. Many virtual threads can be scheduled over a smaller set of operating-system carrier threads.

They are particularly useful for I/O-oriented code such as:

  • HTTP request handling
  • database access
  • file I/O
  • external API calls

Virtual threads do not create additional CPU cores.

Creating thousands of CPU-bound virtual threads does not make CPU-intensive calculations automatically faster. Those tasks still compete for limited processor resources.

Virtual threads also do not remove shared-memory problems. Shared mutable state still requires safe coordination.


Threads are efficient because they share resources—and dangerous for the same reason

Threads in one process share heap objects and global state.

Consider this counter:

public final class Counter {

    private int value = 0;

    public void increment() {
        value++;
    }

    public int value() {
        return value;
    }
}

Several threads can reference the same Counter.

The expression value++ is not necessarily one indivisible operation.

Conceptually, it performs:

1. Read value
2. Add one
3. Write the result

Suppose the initial value is zero.

Thread A reads 0
Thread B reads 0

Thread A writes 1
Thread B writes 1

Two increments occurred, but the final value is one.

This is a race condition.

The result depends on the unpredictable timing and interleaving of threads.

Race conditions can be especially difficult to debug because the failure may disappear when logging or debugging changes the timing.


A critical section accesses shared mutable state

A critical section is a region of code where concurrent execution can produce an invalid result.

value++;

A correct critical-section solution typically considers three properties.

Mutual exclusion

Only one thread enters the critical section at a time.

Progress

When the critical section is free and threads want to enter, the decision should not be postponed forever.

Bounded waiting

A thread requesting access should not wait indefinitely while other threads repeatedly enter.

Mutual exclusion alone is not always enough. An unfair design may protect the data while starving a particular thread.


A mutex provides mutual exclusion

The word mutex comes from mutual exclusion.

A mutex protects a shared resource so that only one thread uses the critical section at a time.

In Java, synchronized can provide this protection.

public final class Counter {

    private int value = 0;

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

    public synchronized int value() {
        return value;
    }
}

When one thread owns the object monitor, another thread must wait before entering another synchronized section protected by the same monitor.

An explicit lock can also be used:

public final class Counter {

    private final Lock lock =
            new ReentrantLock();

    private int value = 0;

    public void increment() {
        lock.lock();

        try {
            value++;
        } finally {
            lock.unlock();
        }
    }

    public int value() {
        lock.lock();

        try {
            return value;
        } finally {
            lock.unlock();
        }
    }
}

Releasing the lock in finally is essential.

If an exception occurs and the lock remains held, other threads may wait forever.


A semaphore controls the number of concurrent users

A semaphore uses a counter representing available permits.

Suppose only five threads may use a resource concurrently.

Semaphore semaphore =
        new Semaphore(5);

semaphore.acquire();

try {
    useSharedResource();
} finally {
    semaphore.release();
}

At most five threads can hold permits at the same time.

Binary semaphore

A binary semaphore has values corresponding to zero or one permit.

It can be used for mutual-exclusion-like behavior.

Counting semaphore

A counting semaphore supports several permits.

It is useful for limiting access to multiple equivalent resources, such as:

  • database connections
  • network channels
  • worker capacity
  • buffer slots

Mutexes and semaphores compared

CriterionMutexSemaphore

Primary purpose Mutual exclusion Resource-count or coordination control
State Locked or unlocked Permit count
Concurrent access Normally one holder Up to the configured permit count
Ownership Usually released by the owner May not enforce strict ownership
Typical use Protecting shared mutable state Limiting concurrent resource use
Example Updating one shared object Allowing five simultaneous operations

In certification-oriented explanations, a mutex is generally associated with exclusive ownership of one critical section, while a semaphore is associated with signaling or controlling access to a limited number of resources.

Actual APIs and operating-system implementations may differ in detail.


A monitor combines shared state with synchronized operations

A monitor is a higher-level synchronization abstraction that combines:

  • shared data
  • operations on that data
  • mutual exclusion
  • condition waiting and signaling

Java object monitors and synchronized are related to this concept.

A thread may wait until a condition becomes true:

public synchronized void take()
        throws InterruptedException {

    while (queue.isEmpty()) {
        wait();
    }

    queue.remove();
}

Another thread may add data and signal waiting threads:

public synchronized void put(
        Item item
) {
    queue.add(item);
    notifyAll();
}

The condition is checked with while, not just if, because a thread must verify the condition again after waking.

In application code, higher-level tools such as BlockingQueue, CountDownLatch, and Condition are often safer and easier to reason about than manually coordinating wait() and notify().


Atomicity, visibility, and ordering are separate concerns

Concurrent programming is not only about preventing two threads from entering the same code simultaneously.

A change made by one thread must also become visible to another thread at the correct time.

CPU caches, compiler optimizations, and instruction reordering can affect observation order.

Java’s volatile keyword provides visibility guarantees and restricts certain reorderings.

public final class Worker {

    private volatile boolean running = true;

    public void stop() {
        running = false;
    }

    public void run() {
        while (running) {
            doWork();
        }
    }
}

When one thread sets running to false, another thread reading the volatile field can observe the change according to Java’s memory-model guarantees.

volatile does not make a compound update atomic.

private volatile int count;

public void increment() {
    count++;
}

count++ still contains a read-modify-write sequence and can lose updates.

An atomic class can be used instead:

public final class Counter {

    private final AtomicInteger value =
            new AtomicInteger();

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

    public int value() {
        return value.get();
    }
}

Concurrent design must distinguish among:

  • mutual exclusion
  • atomicity
  • visibility
  • ordering
  • progress guarantees

No single keyword or synchronization primitive solves every category.


Thread pools reuse worker threads

Creating a new platform thread for every small task repeatedly pays thread creation and destruction costs.

A thread pool creates workers and reuses them.

Task queue
├── Task 1
├── Task 2
└── Task 3

Thread pool
├── Worker A
├── Worker B
└── Worker C

Java provides ExecutorService.

ExecutorService executor =
        Executors.newFixedThreadPool(4);

executor.submit(() -> {
    processRequest();
});

executor.shutdown();

Advantages include:

  • lower thread-creation overhead
  • controlled concurrency
  • task queuing
  • centralized lifecycle management
  • separation of task submission from execution

Pool sizing matters.

A pool that is too small creates long queues.

A pool that is too large may increase:

  • memory usage
  • context switching
  • CPU competition
  • database connection pressure
  • load on external services

CPU-bound pools are often sized in relation to available cores.

I/O-bound tasks can tolerate more concurrent workers because many wait, but downstream limits such as database connection pools and API rate limits must also be considered.


More threads do not always make a program faster

Parallelizing work introduces costs:

  • thread creation
  • stack memory
  • context switching
  • synchronization
  • cache invalidation
  • lock waiting
  • work partitioning
  • result merging
  • resource contention
  • scheduler overhead

Creating hundreds of CPU-bound threads on one CPU core does not increase computational capacity.

The threads simply compete for the same processor, potentially spending more time switching than performing useful work.

Parallel execution is beneficial when:

  • the workload can be partitioned
  • tasks are sufficiently independent
  • there are enough CPU resources
  • coordination cost is smaller than the parallel speedup
  • shared-resource contention remains manageable

Thread safety means correct behavior under concurrent use

An object is thread-safe when concurrent access does not violate its intended state or behavior.

Several techniques can help.

Immutability

An immutable object does not change after construction.

public record Money(
        long amount,
        String currency
) {
}

Immutable data is easier to share because no thread can modify it after publication.

Thread confinement

Mutable state is restricted to one thread.

Method-local variables and request-specific objects often follow this approach.

Synchronization

Locks protect shared mutable state.

Atomic classes

Classes such as AtomicInteger, AtomicLong, and AtomicReference provide atomic operations.

Concurrent collections

Structures such as ConcurrentHashMap and BlockingQueue are designed for concurrent access.

Reduced sharing

The simplest synchronization problem is the one that does not exist.

Reducing shared mutable state is often more effective than adding increasingly complex locks.


Threads can deadlock

Deadlock occurs when threads hold resources while waiting for resources held by one another.

Consider this transfer method:

public void transfer(
        Account from,
        Account to,
        long amount
) {
    synchronized (from) {
        synchronized (to) {
            from.withdraw(amount);
            to.deposit(amount);
        }
    }
}

Thread A transfers money from Isaac to Sophie.

Thread A
Lock Isaac's account
→ Request Sophie's account lock

Thread B transfers money from Sophie to Isaac.

Thread B
Lock Sophie's account
→ Request Isaac's account lock

The following interleaving causes deadlock:

Thread A locks Isaac's account
Thread B locks Sophie's account

Thread A waits for Sophie's account
Thread B waits for Isaac's account

Neither thread can continue.


Four necessary conditions for deadlock

Four conditions are commonly required for deadlock.

Mutual exclusion

A resource can be held by only one execution flow at a time.

Hold and wait

A thread holds one resource while waiting for another.

No preemption

A resource cannot be forcibly taken from its owner.

Circular wait

Threads wait in a cycle.

Thread A waits for a resource held by Thread B
Thread B waits for a resource held by Thread C
Thread C waits for a resource held by Thread A

Preventing at least one condition prevents the deadlock.


Preventing and handling deadlock

Use a consistent lock order

Every thread acquires resources in the same order.

For example, always lock the account with the smaller ID first.

Account first =
        from.id().compareTo(to.id()) < 0
                ? from
                : to;

Account second =
        first == from
                ? to
                : from;

synchronized (first) {
    synchronized (second) {
        from.withdraw(amount);
        to.deposit(amount);
    }
}

Consistent ordering removes circular wait.

Acquire required resources together

When practical, determine and acquire all necessary resources before beginning the operation.

This may reduce deadlock risk but can also reduce concurrency.

Use timeout-based locking

A thread can give up when a lock is not available within a defined period.

if (lock.tryLock(
        1,
        TimeUnit.SECONDS
)) {
    try {
        process();
    } finally {
        lock.unlock();
    }
}

The application must then decide whether to retry, fail, or compensate.

Keep critical sections small

A lock should protect only the code that truly requires exclusive access.

Long operations, network calls, and unrelated calculations should generally not occur while holding a lock.

Reduce nested locking

Holding several locks simultaneously increases the number of possible waiting relationships.

Prefer higher-level concurrency utilities

Tested queues, concurrent collections, executors, and synchronization components are usually safer than manually coordinating several low-level locks.


Deadlock, starvation, and livelock

These concepts are related but different.

Deadlock

Several threads wait permanently for one another’s resources.

Circular waiting
→ No participating thread progresses

Starvation

A particular thread never receives enough CPU time or resource access.

Other threads continue progressing.

Examples include:

  • low-priority tasks repeatedly losing to high-priority tasks
  • unfair locks continually favoring other threads
  • long jobs repeatedly delayed by short jobs

Livelock

Threads remain active and repeatedly change state, but useful work does not progress.

A yields to B
B yields to A
A yields again

A deadlocked system is stuck without movement.

A livelocked system moves without making progress.


Process termination and thread termination have different effects

When a process terminates, its threads and process-level resources are normally released.

Process termination
→ Internal threads stop
→ Address space is released
→ Open resources are cleaned up

When one thread exits normally, other threads in the same process may continue.

A severe thread failure can still affect the whole process because threads share the same address space and resources.

In Java, an uncaught exception often terminates the thread in which it occurred rather than automatically terminating the entire JVM.

Process-wide effects may still occur through:

  • out-of-memory conditions
  • native-code crashes
  • explicit process termination
  • corruption of shared state
  • loss of a critical management thread
  • unrecoverable runtime errors

This is why thread-level fault isolation is weaker than process-level isolation.


Processes and threads in a server application

Running a Spring Boot application normally creates a JVM process containing many threads.

JVM process
├── Main thread
├── HTTP worker threads
├── Garbage collector threads
├── Scheduler threads
├── Database-related threads
└── Application background threads

A typical request path looks like this:

HTTP request
→ Worker thread assigned
→ Controller
→ Service
→ Repository
→ Response returned
→ Worker thread reused

Several request threads may invoke the same Spring Bean concurrently.

Because the default Spring Bean scope normally uses one shared instance per application context, storing request-specific mutable state in an instance field is dangerous.

Incorrect example:

@Service
public class OrderService {

    private String currentCustomer;

    public void process(
            String customer
    ) {
        currentCustomer = customer;

        save(currentCustomer);
    }
}

While Isaac’s request is being processed, Sophie’s request may overwrite currentCustomer.

The first request might then save Sophie’s value.

Request-specific values should usually remain in local variables or request-scoped objects.

@Service
public class OrderService {

    public void process(
            String customer
    ) {
        String currentCustomer =
                customer;

        save(currentCustomer);
    }
}

A local variable belongs to the current method invocation and is generally stored in the current thread’s execution context.

The object referenced by a local variable can still be shared, however. A local reference does not automatically make the underlying object thread-safe.


Asynchronous execution creates a new execution context

Spring’s @Async or an executor may move work to another thread.

@Async
public void sendNotification(
        Long orderId
) {
    // May run on another thread
}

When the thread changes, the following context may not automatically follow:

  • database transaction
  • security context
  • request attributes
  • logging context
  • thread-local values
Request thread
→ Starts transaction
→ Submits asynchronous task

Async worker
→ Runs in a separate execution context

If the asynchronous task starts before the original transaction commits, it may not observe the newly written data.

An asynchronous design should define:

  • when the task begins
  • whether it must run before or after commit
  • how failures are retried
  • which context must be propagated
  • whether duplicate execution is safe
  • how completion is observed

Changing threads is not merely changing where code runs.

It creates a boundary between execution contexts.


Execution units continue to become lighter

Earlier operating-system descriptions often treated the process as both the resource unit and the execution unit.

Modern systems separate these ideas more clearly.

Process
Address-space and resource boundary

Thread
Kernel-scheduled CPU execution unit

Coroutine, virtual thread, or task
Lighter runtime-managed execution unit

The kernel schedules the execution units it recognizes.

A language runtime may multiplex many logical tasks over a smaller number of kernel threads.

Several coroutines, for example, may alternate on one operating-system thread.

This distinction helps explain two important statements:

Creating many logical tasks
≠
Creating the same number of operating-system threads

Creating many operating-system threads
≠
Running every thread in parallel

Actual parallelism depends on:

  • CPU core count
  • scheduler decisions
  • thread states
  • runtime implementation
  • workload characteristics

Key concepts for certification exams

Program and process

Program
A static instruction set stored on secondary storage

Process
A program loaded into memory and currently executing

PCB

Process identifier
Process state
Program counter
CPU registers
Scheduling information
Memory-management information
I/O status information

Process states

New
Ready
Running
Waiting
Terminated

Process and thread

Process
Resource ownership and protection boundary
Independent address space

Thread
CPU execution and scheduling flow
Shares process code, data, and heap
Owns its stack and register state

Context switching

Save the current execution state
Restore the next execution state
Change the active CPU task

Preemptive and non-preemptive scheduling

Preemptive
The operating system may take the CPU from a running task

Non-preemptive
The task runs until it blocks, exits, or yields

Major scheduling algorithms

FCFS
Execute in arrival order

SJF
Execute the shortest expected job first

SRTF
Execute the job with the shortest remaining time

Priority
Execute the highest-priority job first

Round Robin
Rotate tasks using fixed time slices

Synchronization

Critical section
Code that accesses shared state and may race

Mutex
Allows one execution flow at a time

Semaphore
Controls access using a defined number of permits

Necessary deadlock conditions

Mutual exclusion
Hold and wait
No preemption
Circular wait

Choosing between processes and threads

Neither processes nor threads are universally better.

Processes may be preferable when:

  • strong fault isolation is required
  • security permissions must be separated
  • components use different languages or runtimes
  • memory must remain isolated
  • one component’s crash must not affect another

Threads may be preferable when:

  • data must be shared frequently
  • execution-unit creation should be inexpensive
  • other work should continue during I/O waits
  • one application needs internal parallelism
  • the same process resources should be reused

Important questions include:

  • How much state must be shared?
  • How strong must failure isolation be?
  • How often must data cross the boundary?
  • Is the workload CPU-bound or I/O-bound?
  • How much parallelism is actually needed?
  • Can the synchronization complexity be managed?
  • Is a security boundary required?
  • What are the creation and switching costs?

Processes create execution environments; threads perform work inside them

Processes and threads should not be reduced to “large tasks” and “small tasks.”

A process creates an independent environment for a running program.

That environment contains:

  • address space
  • code and data
  • heap
  • files and sockets
  • permissions
  • operating-system resources
  • one or more threads

A thread is an execution flow that performs instructions inside that environment.

Each thread has its own program counter, register state, and stack, while sharing the process heap and resources.

This sharing enables efficient cooperation.

It also creates race conditions, visibility problems, and deadlocks.

Process strength
Isolation and independence

Process cost
Creation and communication overhead

Thread strength
Lightweight execution and fast sharing

Thread cost
Synchronization and failure-propagation complexity

Operating-system execution management divides limited CPU time

A computer usually has more ready work than available CPU cores.

The operating system must distribute limited processing time among processes and threads.

It tracks:

  • which tasks are ready
  • which tasks are waiting for events
  • which task currently owns each CPU
  • task priorities
  • CPU time already consumed
  • when preemption is necessary
  • which core should run each task
  • how to preserve cache and memory locality

The scheduler changes the running task through context switching.

When one task waits for I/O, another can use the CPU.

On a multicore processor, several threads may execute physically in parallel.

Shared resources require mutexes, semaphores, monitors, or other coordination mechanisms.

Incorrect lock ordering can create deadlock.

The operating system therefore balances goals such as:

Fast response
High throughput
Fair CPU allocation
Low context-switching overhead
High resource utilization
Safe shared-resource access
Prevention of starvation and deadlock

No single scheduling or synchronization method is ideal for every workload.


Understanding execution units explains application behavior

Processes and threads are not merely abstract exam topics.

They explain real application behavior, including:

  • how a server handles multiple requests
  • why mutable fields in shared Beans can conflict
  • why transaction context may disappear in asynchronous work
  • why thread-pool size affects performance
  • why requests can wait while CPU usage remains low
  • why one lock can slow an entire service
  • why one service process can crash without stopping another
  • why running more containers consumes more memory
  • why virtual threads help with blocking I/O
  • why multithreading does not guarantee multicore speedup

Code does not execute by itself.

The operating system creates processes, assigns memory, schedules threads, saves CPU state, and restores that state later.

That execution-management structure is what allows many programs to appear active at the same time and allows one server to handle many clients concurrently.


The distinction between processes and threads is a distinction between resource and execution boundaries

A process is a running program and a boundary within which the operating system allocates and protects resources.

A thread is an execution flow that performs instructions inside the process.

Processes normally have separate address spaces, giving them stronger fault and memory isolation.

Threads in the same process share code, heap objects, and open resources. That makes communication efficient, but it requires careful synchronization.

The operating system stores execution state in process and thread management structures.

The scheduler chooses a ready execution unit and assigns it to a CPU.

When a time slice expires or a task waits for I/O, its state changes and a context switch occurs.

On multicore processors, several execution flows can run in parallel.

Shared state creates the need for synchronization, and incorrect synchronization creates races, starvation, livelock, and deadlock.

A complete understanding of processes and threads requires asking:

Is this resource private to a process, or shared among threads?

Is this task currently using the CPU, or waiting for an event?

Can several threads access this object safely?

Is the benefit of parallel execution greater than the cost of synchronization and context switching?

When a failure occurs, does it affect one thread, one process, or the whole system?

These questions connect operating-system theory directly to server and application design.

The central distinction can be summarized as follows:

A process provides an independent resource environment in which a program can run, while threads perform the actual flows of work inside that environment.

The operating system distributes limited CPU time across those flows, preserves interrupted state, and resumes execution when resources become available.

That is why several applications can appear to run at once—and why one server process can coordinate many concurrent requests.

References

Isaac S. Lee
Faith, software, data, and everyday life.