Fundamentals 11 min read

Understanding Java Multithreading: Threads, Concurrency, and Atomic Operations

This article introduces Java multithreading by explaining the relationship between processes and threads, the benefits of concurrency, common pitfalls like race conditions, and how to safely manage shared variables using ThreadPoolExecutor, AtomicInteger, volatile, and CAS operations.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Understanding Java Multithreading: Threads, Concurrency, and Atomic Operations

00. Origin of the Story

"Second brother, how was the response to the previous article ‘Collections’?" Third sister asks about the proposed ‘Teach Sister Java’ column.

"The view count of this article is much higher than the first ‘Generics’ article."

"That’s good news, more people are accepting second brother’s work," Third sister exclaims.

"Maybe the comparison isn’t meaningful."

"You’re being modest, but honestly, it’s amazing that anyone still reads it," Third sister jokes.

"Are you looking for a beating?"

"No, I’m saying second brother’s readers are lucky because they see higher‑quality articles," Third sister continues.

"Yes, it’s much better than before, but I’ll work even harder. This time the topic is ‘Multithreading’, are you ready?"

"I’m ready. Let’s keep the Q&A going," Third sister says eagerly.

01. What is a Thread?

To understand threads you first need to understand processes, because a thread is a unit of a process. On a computer you may have many processes running simultaneously – an input method, a browser, a music player, etc.

Within a single process multiple threads can run concurrently, each handling a different task, such as scrolling lyrics while playing audio.

Every process must have at least one thread; in Java the main method runs on the main thread.

The operating system schedules threads much like it does processes, rapidly switching between them to improve efficiency.

In simple terms: “A process is the parent that manages many thread children.”

02. Why Use Multithreading?

Multithreading provides many benefits.

First, it reduces application response time by allowing short‑running tasks to proceed while I/O or network operations are pending.

Second, it fully utilizes multi‑core CPUs; the OS can run different threads on different cores, and even when threads exceed core count, thread pools minimize context‑switch overhead.

Third, compared with multiple processes, multithreading is more efficient because threads share the same memory space, avoiding inter‑process communication.

However, if multiple threads modify the same object without proper coordination, problems arise. The following example demonstrates this.

public class Cmower {
    public static int count = 0;
    public static int getCount() {
        return count;
    }
    public static void addCount() {
        count++;
    }
    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(10, 1000, 60L, TimeUnit.SECONDS,
                new ArrayBlockingQueue
(10));
        for (int i = 0; i < 1000; i++) {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    Cmower.addCount();
                }
            };
            executorService.execute(r);
        }
        executorService.shutdown();
        System.out.println(Cmower.count);
    }
}

The thread pool executes 1,000 tasks, each incrementing Cmother.addCount() . The expected result is 1,000, but the actual output is often something like 998, 997, etc., because of race conditions.

03. Why Isn’t the Result 1000?

During execution the CPU caches a copy of count in its fast cache, performs count++ , and then writes the updated value back to main memory. On multi‑core CPUs each thread may have its own cache, so two threads can read the same stale value, increment it, and both write back the same result, losing one increment.

Such shared variables need protection.

04. How to Protect Shared Variables?

Replace the plain int with an AtomicInteger that provides atomic operations.

public static AtomicInteger count = new AtomicInteger();

public static int getCount() {
    return count.get();
}

public static void addCount() {
    count.incrementAndGet();
}

AtomicInteger uses the unsafe class’s CAS (compare‑and‑swap) method to update the value atomically.

private static final Unsafe unsafe = Unsafe.getUnsafe();

The CAS method signature is:

public final native boolean compareAndSwapInt(Object o, long offset,
                                               int expected, int x);

It atomically changes the value at offset from expected to x only if the current value matches expected . This ensures that concurrent updates do not overwrite each other.

The AtomicInteger class also contains a volatile int value field, guaranteeing visibility of updates across threads.

Note that volatile ensures visibility but not atomicity; it must be combined with atomic operations like CAS for safe concurrent modifications.

05. The Story Continues

"Second brother, let’s stop here for now; I can’t absorb more," says Third sister.

"Sure," replies second brother.

They discuss future article plans and the challenges of gaining readership.

Author Bio

Silence King Two, author of “Web Full‑Stack Development Advancement”, a programmer who also writes engaging articles. Contact on WeChat: qing_gee .

Recommended Reading

Teach Sister Java: The Vast Potential of Collections

Teach Sister Java: The Obscure Generics

< END >

JavaConcurrencythreadpoolMultithreadingCASAtomicInteger
Full-Stack Internet Architecture
Written by

Full-Stack Internet Architecture

Introducing full-stack Internet architecture technologies centered on Java

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.