Modern Java

Introduction to Concurrency and Thread Safety

ReadingPreview

আপনি একটি free preview lesson দেখছেন।

Lesson Overview

এ পর্যন্ত আমাদের বেশিরভাগ Java code sequential ছিল।

Meaning:

একটি operation
তারপর আরেকটি operation
তারপর আরেকটি operation

Example:

loadCourse();

validateCourse();

publishCourse();

কিন্তু real applications-এ অনেক কাজ একসঙ্গে progress করতে পারে।

Examples:

Multiple users একই server ব্যবহার করছে

একাধিক request একই service-এ আসছে

Background task চলছে

File processing হচ্ছে

Message processing হচ্ছে

Network call-এর জন্য অপেক্ষা হচ্ছে

একাধিক task CPU ব্যবহার করছে

এই ধরনের environment-এ আমাদের বুঝতে হয়:

Concurrency
Thread
Shared State
Race Condition
Thread Safety

এই lesson-এ আমরা শিখব:

  • Process কী
  • Thread কী
  • Concurrency কী
  • Parallelism কী
  • Concurrency এবং Parallelism-এর difference
  • Shared mutable state
  • Race condition
  • Atomicity
  • কেন count++ atomic নয়
  • Thread safety
  • Critical section
  • synchronized
  • Intrinsic lock / monitor-এর basic idea
  • Visibility
  • volatile
  • volatile কী solve করে
  • volatile কী solve করে না
  • Immutability
  • Stateless design
  • Thread confinement
  • Compound operations
  • Check-then-act race
  • Thread-safe design-এর practical principles
  • কেন concurrency bugs difficult

Why Learn Concurrency?

Backend application usually এক user-এর জন্য একবার run করে না।

একই সময়:

User A
User B
User C
User D

service-কে request করতে পারে।

একটি server process-এর মধ্যে অনেক request handling task concurrently progress করতে পারে।

তাই code যদি shared state use করে, প্রশ্ন আসে:

একই data দুইটি thread একসঙ্গে change করলে কী হবে?

Concurrency fundamentals না বুঝলে এমন bugs হতে পারে:

lost updates
incorrect counters
duplicate processing
inconsistent state
random failures
hard-to-reproduce bugs

What Is a Process?

একটি running program-কে সাধারণভাবে একটি:

Process

হিসেবে ভাবা যায়।

Example:

Java application start হলো
↓
Operating System একটি process চালাচ্ছে

Process-এর নিজের resources থাকে।

Examples:

memory
open files
network connections
threads

What Is a Thread?

একটি process-এর ভিতরে execution-এর একটি path হলো:

Thread

একটি Java process-এর মধ্যে multiple threads থাকতে পারে।

Conceptually:

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

প্রতিটি thread আলাদা execution flow চালাতে পারে।


Main Thread

Simple Java application:

public class Main {

    public static void main(
            String[] args
    ) {
        System.out.println(
                "Hello"
        );
    }
}

main() method সাধারণত application-এর main thread-এ start হয়।


Creating a Simple Thread

Basic example:

Thread worker =
        new Thread(
                () ->
                        System.out.println(
                                "Running in worker thread"
                        )
        );

worker.start();

এখানে:

start()

নতুন thread-এ task execute করতে দেয়।


Do Not Call run() Directly by Mistake

Given:

Thread worker =
        new Thread(
                () ->
                        doWork()
        );

This:

worker.run();

normal method call-এর মতো current thread-এই execute করবে।

But:

worker.start();

নতুন thread start করে।


Thread Example

public class Main {

    public static void main(
            String[] args
    ) {
        Thread first =
                new Thread(
                        () ->
                                print(
                                        "First"
                                )
                );

        Thread second =
                new Thread(
                        () ->
                                print(
                                        "Second"
                                )
                );

        first.start();
        second.start();
    }

    static void print(
            String name
    ) {
        for (
                int i = 1;
                i <= 5;
                i++
        ) {
            System.out.println(
                    name
                    + " "
                    + i
            );
        }
    }
}

Output order fixed নয়।

Possible:

First 1
First 2
Second 1
First 3
Second 2
...

Another run-এ order different হতে পারে।


Why Is Order Different?

Operating System এবং runtime decide করে:

কোন thread কখন CPU time পাবে।

আপনি সাধারণত assume করতে পারবেন না:

Thread A আগে start করেছি
তাই A আগে finish করবে।

Concurrency

Concurrency মানে multiple tasks-এর execution সময়ের মধ্যে overlap করা।

Conceptually:

Task A starts
Task B starts
Task A progresses
Task B progresses
Task A finishes
Task B finishes

একটি CPU core থাকলেও scheduler tasks-এর মধ্যে switch করে concurrency তৈরি করতে পারে।


Parallelism

Parallelism মানে multiple tasks literally একই সময়ে execute হচ্ছে।

সাধারণত multiple CPU cores available থাকলে।

Conceptually:

Core 1 → Task A
Core 2 → Task B

same instant-এ কাজ হচ্ছে।


Concurrency vs Parallelism

Important distinction:

Concurrency
→ multiple tasks make progress during overlapping time

Parallelism
→ multiple tasks execute at the same physical time

Concurrency does not automatically require multiple cores।

Parallelism usually does।


Example

Suppose one chef:

boil water
while waiting
cut vegetables
then return to water

এটি concurrency-এর মতো।

Two chefs:

Chef A boils water
Chef B cuts vegetables

same time-এ।

এটি parallelism-এর মতো।


Concurrency Is About Coordination

Concurrency শুধু speed নয়।

অনেক ক্ষেত্রে concurrency-এর challenge:

coordination
shared state
ordering
visibility
correctness

Performance পরে।

Correctness first।


Shared State

Suppose:

class Counter {

    int value;
}

And two threads same object use করছে।

Thread A
→ counter.value

Thread B
→ counter.value

এই Counter object:

shared state

Mutable State

State mutable যখন value change করা যায়।

Example:

counter.value++;

So:

shared mutable state

means:

একই mutable data
multiple threads access করছে

এটাই concurrency bugs-এর major source।


Race Condition

Race Condition হয় যখন result depend করে:

threads কোন order-এ operations execute করেছে

এবং সেই ordering properly controlled নয়।


Simple Counter Example

class Counter {

    private int count;

    void increment() {
        count++;
    }

    int value() {
        return count;
    }
}

Suppose two threads each:

100000 times increment()

call করে।

Expected:

200000

কিন্তু thread-safe না হলে result lower হতে পারে।


Why Can count++ Fail?

এটি দেখতে একটি operation:

count++;

কিন্তু conceptually multiple steps:

1. Read count
2. Add 1
3. Write count back

Suppose count:

10

Thread A:

read 10

Thread B:

read 10

Thread A:

write 11

Thread B:

write 11

Expected after two increments:

12

Actual:

11

একটি update হারিয়ে গেছে।

এটিকে বলা হয়:

Lost Update

Atomic Operation

An operation is atomic when it behaves conceptually as one indivisible action।

Meaning:

another thread cannot observe or interfere
halfway through that operation

count++ is not an atomic increment of a shared int


Thread Safety

Code বা object:

thread-safe

যদি multiple threads concurrently use করলেও তার correctness guarantees maintain হয়।

Thread safety depends on design।

শুধু:

method ছোট

হলেই thread-safe হয় না।


Thread-Safe Counter with synchronized

class Counter {

    private int count;

    synchronized void increment() {
        count++;
    }

    synchronized int value() {
        return count;
    }
}

Now increment() একই object-এর জন্য একবারে একটি thread execute করতে পারবে।


What Does synchronized Do?

High level-এ synchronized:

mutual exclusion
+
visibility guarantees

provide করে।

Mutual exclusion means:

একটি protected critical section
একই lock-এর জন্য
এক সময়ে একটি thread execute করে।

Critical Section

Code-এর যে অংশ shared mutable state access বা modify করে এবং concurrent interference থেকে protect করা দরকার:

critical section

Example:

synchronized void increment() {
    count++;
}

Critical section:

count++;

Synchronized Block

পুরো method synchronize করার প্রয়োজন সবসময় নেই।

void increment() {
    synchronized (
            this
    ) {
        count++;
    }
}

This also synchronizes on:

this

Lock Mental Model

Conceptually:

Thread A acquires lock
↓
Thread A critical section execute করে
↓
Thread A releases lock
↓
Thread B lock পায়

If Thread B আসে lock occupied থাকা অবস্থায়:

wait করতে হবে

Monitor / Intrinsic Lock

Every Java object conceptually synchronization-এর জন্য একটি intrinsic lock বা monitor-এর সাথে associated হতে পারে।

When:

synchronized (
        someObject
)

use করি, সেই object-এর monitor/lock ব্যবহার করি।

Foundation level-এ এটুকু mental model যথেষ্ট।


Same Lock Matters

Suppose:

synchronized (
        lockA
) {
    shared++;
}

and another method:

synchronized (
        lockB
) {
    shared++;
}

If:

lockA != lockB

তাহলে দুইটি block একে অপরকে mutually exclude করে না।

Shared state protect করতে synchronization strategy consistent হতে হবে।


Private Lock Object

Sometimes:

private final Object lock =
        new Object();

Then:

void increment() {
    synchronized (
            lock
    ) {
        count++;
    }
}

এটি synchronization mechanism encapsulate করতে সাহায্য করে।


Synchronize State Invariants, Not Random Lines

Suppose:

class Wallet {

    private long balance;
}

Withdraw:

boolean withdraw(
        long amount
) {
    if (
            balance >= amount
    ) {
        balance -=
                amount;

        return true;
    }

    return false;
}

This has a race।


Check-Then-Act Race

The operation is:

Check:
balance >= amount

Then:
subtract amount

Suppose balance:

100

Two threads withdraw:

80

Thread A sees:

100 >= 80

Thread B also sees:

100 >= 80

Both subtract।

Now state can become invalid।


Synchronize the Whole Compound Operation

synchronized boolean withdraw(
        long amount
) {
    if (
            balance < amount
    ) {
        return false;
    }

    balance -=
            amount;

    return true;
}

Important:

check + update

must be protected as one logical operation।


Compound Operations

A compound operation contains multiple steps that must behave atomically relative to other threads।

Examples:

check then act
read modify write
get then remove
if absent then insert

Concurrency reasoning must protect the entire invariant, not just one field write।


Example — Duplicate Registration

Bad pattern:

if (
        !users.containsKey(
                email
        )
) {
    users.put(
            email,
            user
    );
}

If multiple threads execute concurrently:

both may observe missing
both may insert

Even if individual operations are safe in some collection, compound semantics need correct API or synchronization।

Later concurrent collections provide atomic methods for these patterns।


Visibility

Concurrency problem শুধু two threads একই সময়ে write করছে এতটুকু নয়।

আরেকটি issue:

এক thread-এর write
অন্য thread কখন দেখতে পাবে?

এটিকে বলা হয়:

visibility

Simple Visibility Example

class Worker {

    private boolean running =
            true;

    void stop() {
        running =
                false;
    }

    void run() {
        while (
                running
        ) {
            // work
        }
    }
}

One thread:

run()

আরেকটি thread:

stop()

call করে।

Naively আমরা expect করি loop stop করবে।

কিন্তু proper synchronization/visibility guarantee ছাড়া compiler/runtime/CPU behavior-এর কারণে worker thread updated value promptly observe করার guarantee নেই।


Java Memory Visibility

Modern processors এবং compilers performance-এর জন্য:

caching
reordering
register usage
optimizations

করতে পারে।

Concurrency-এর জন্য Java defines a memory model describing when writes become reliably visible between threads।

Foundation level-এ key lesson:

Shared mutable state access-এর জন্য
visibility rules ignore করা যাবে না।

volatile

Simple visibility use case-এর জন্য Java provides:

volatile

Example:

class Worker {

    private volatile boolean running =
            true;

    void stop() {
        running =
                false;
    }

    void run() {
        while (
                running
        ) {
            // work
        }
    }
}

Now writes to:

running

have visibility semantics appropriate for this shared flag pattern।


What volatile Is Good For

volatile useful হতে পারে যখন shared variable:

one independently readable/writable state value

এবং operation does not require compound atomicity।

Examples:

stop flag
configuration reference
latest immutable snapshot reference

depending on design।


volatile Does Not Make count++ Atomic

Important:

volatile int count;

Then:

count++;

still conceptually:

read
add
write

Multiple threads still race।

So:

volatile
≠ general-purpose thread safety

Wrong Counter

class Counter {

    private volatile int count;

    void increment() {
        count++;
    }
}

Still not thread-safe।


Why?

volatile improves visibility of reads/writes।

It does not combine:

read + modify + write

into one atomic increment।


synchronized vs volatile

Very simplified mental model:

synchronized
→ protect compound/shared critical sections
→ mutual exclusion + visibility

volatile
→ visibility for a shared variable
→ no mutual exclusion for compound logic

This is not the full Java Memory Model, but it is a useful foundation।


Immutability and Thread Safety

One of the easiest concurrency strategies:

Do not mutate shared data.

Example:

record CourseSummary(
        String code,
        String title
) {
}

If all components are immutable, multiple threads can read the same object safely without synchronization for mutation because there is no mutation।


Immutable Objects Simplify Concurrency

Mutable object:

Who can change it?
When?
While someone else reads?
Do I need a lock?

Immutable object:

Construct once
Then read

Much easier to reason about।


But Remember Shallow Immutability

Record:

record Data(
        List<String> values
) {
}

with mutable list may still expose shared mutation।

Better:

record Data(
        List<String> values
) {

    Data {
        values =
                List.copyOf(
                        values
                );
    }
}

for stable immutable collection structure।


Stateless Code

Another strong thread-safety strategy:

Do not keep shared mutable fields.

Example:

final class PriceCalculator {

    long calculate(
            long basePrice,
            long discount
    ) {
        return basePrice
                - discount;
    }
}

No mutable instance fields।

Multiple threads can call:

calculate(...)

without interfering with each other।


Stateless Services

A service can be largely stateless:

final class CourseValidator {

    boolean isValid(
            Course course
    ) {
        ...
    }
}

Inputs come through parameters।

Temporary values stay local।

No shared mutable request-specific fields।

This is a common reason stateless service designs scale well under concurrency।


Local Variables and Thread Safety

Method-local variables are generally confined to that invocation।

Example:

int total =
        0;

inside a method call।

Another thread calling same method has its own local total

This reduces interference।


Thread Confinement

If mutable data is used by only one thread and never shared, synchronization may not be needed।

This is called:

thread confinement

Example:

List<String> localResult =
        new ArrayList<>();

created, used, and discarded entirely within one thread's task।


The Problem Starts When Reference Escapes

Suppose local mutable object is returned or placed into shared state:

sharedCache.put(
        key,
        localResult
);

Now other threads may access it।

It is no longer thread-confined।


Prefer Local State Over Shared Fields

Bad:

class CourseProcessor {

    private final List<String> currentTitles =
            new ArrayList<>();

    List<String> process(
            List<Course> courses
    ) {
        currentTitles.clear();

        for (
                Course course
                : courses
        ) {
            currentTitles.add(
                    course.title()
            );
        }

        return currentTitles;
    }
}

If same service instance used concurrently, requests interfere।


Better

class CourseProcessor {

    List<String> process(
            List<Course> courses
    ) {
        List<String> titles =
                new ArrayList<>();

        for (
                Course course
                : courses
        ) {
            titles.add(
                    course.title()
            );
        }

        return List.copyOf(
                titles
        );
    }
}

Now request-specific mutable state is local।


Shared Mutable Singleton-Style State Is Dangerous

Backend frameworks often reuse service objects across requests।

Therefore avoid fields like:

private String currentUser;
private List<Course> currentCourses;
private int currentRequestCount;

unless synchronization/concurrency semantics intentionally designed।


Safe Publication — Basic Idea

Constructing an object is not the end of concurrency reasoning।

Other threads need to receive the reference through a safe mechanism if visibility matters।

Java concurrency utilities, synchronization, volatile references, and properly constructed immutable objects can participate in safe publication patterns।

We will not go deeply into Java Memory Model in this foundation course।

Key lesson:

How an object becomes shared also matters.

Thread Interleaving

Suppose:

System.out.println(
        "A1"
);

System.out.println(
        "A2"
);

in Thread A।

And:

System.out.println(
        "B1"
);

System.out.println(
        "B2"
);

in Thread B।

You may see:

A1
B1
B2
A2

Threads can interleave।

Never assume cross-thread sequence without explicit coordination।


Thread.sleep() Is Not Synchronization

A common beginner mistake:

Thread.sleep(
        100
);

and assume:

অন্য thread নিশ্চয় finish করেছে।

This is not a correctness guarantee।

Sleep only delays a thread approximately according to scheduling semantics।

It does not establish the business condition you need।


Waiting for a Thread with join()

If one thread must wait until another finishes:

worker.start();

worker.join();

After successful join() completion, current thread continues after worker terminates।


InterruptedException

join() can require handling:

InterruptedException

Example:

try {
    worker.join();
} catch (
        InterruptedException exception
) {
    Thread.currentThread()
            .interrupt();

    throw new IllegalStateException(
            "Interrupted while waiting.",
            exception
    );
}

Why Restore Interrupt Status?

This pattern:

Thread.currentThread()
        .interrupt();

preserves interruption signal for higher-level code after catching InterruptedException when you're not fully handling the cancellation yourself।

We will not go deep into interruption policy here, but silently ignoring interruption is usually a bad habit।


Complete Race Condition Example

public class Main {

    public static void main(
            String[] args
    ) throws InterruptedException {
        Counter counter =
                new Counter();

        Thread first =
                new Thread(
                        () ->
                                incrementManyTimes(
                                        counter
                                )
                );

        Thread second =
                new Thread(
                        () ->
                                incrementManyTimes(
                                        counter
                                )
                );

        first.start();
        second.start();

        first.join();
        second.join();

        System.out.println(
                counter.value()
        );
    }

    static void incrementManyTimes(
            Counter counter
    ) {
        for (
                int i = 0;
                i < 100_000;
                i++
        ) {
            counter.increment();
        }
    }

    static final class Counter {

        private int count;

        void increment() {
            count++;
        }

        int value() {
            return count;
        }
    }
}

Expected:

200000

But result may be lower due to race condition।


Thread-Safe Version

public class Main {

    public static void main(
            String[] args
    ) throws InterruptedException {
        Counter counter =
                new Counter();

        Thread first =
                new Thread(
                        () ->
                                incrementManyTimes(
                                        counter
                                )
                );

        Thread second =
                new Thread(
                        () ->
                                incrementManyTimes(
                                        counter
                                )
                );

        first.start();
        second.start();

        first.join();
        second.join();

        System.out.println(
                counter.value()
        );
    }

    static void incrementManyTimes(
            Counter counter
    ) {
        for (
                int i = 0;
                i < 100_000;
                i++
        ) {
            counter.increment();
        }
    }

    static final class Counter {

        private int count;

        synchronized void increment() {
            count++;
        }

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

Now shared counter access protected।


Why Synchronize value() Too?

Suppose writes synchronized but reads unsynchronized।

Then visibility/coordination contract becomes inconsistent।

If field access is guarded by one lock, a strong design rule is:

all access to that guarded mutable state
uses the same synchronization strategy.

Guarded-by Mental Model

Think:

count
is guarded by
this object's lock

Then every:

read
write
compound operation

for count goes through that lock।

Consistency makes thread-safety reasoning much easier।


Synchronization Has Cost

Locks are not free।

Possible costs:

contention
waiting
context switching
reduced parallelism

But correctness comes first।

Do not remove synchronization just because:

locks are slow

without measurement and a safe alternative।


Coarse-Grained vs Fine-Grained Locking

High level:

Coarse-grained
→ larger region under one lock
→ simpler correctness
→ potentially more contention

Fine-grained
→ smaller/multiple locks
→ potentially more concurrency
→ much harder reasoning

Foundation code should generally prefer:

simple correctness

over clever locking।


Deadlock — Basic Preview

Suppose Thread A holds Lock 1 and waits for Lock 2।

Thread B holds Lock 2 and waits for Lock 1।

Now neither can proceed।

Thread A:
has A
waits B

Thread B:
has B
waits A

This is:

Deadlock

We will not teach advanced deadlock analysis here।

Important lesson:

multiple locks increase coordination complexity.

Why Concurrency Bugs Are Difficult

Sequential bug:

same input
→ often same failure

Concurrency bug may depend on timing:

Thread A happened here

exactly when

Thread B happened there

Tiny scheduling differences can change outcome।


Heisenbug-Like Behavior

Sometimes adding:

System.out.println(...)

changes timing enough that bug disappears।

This makes concurrency bugs particularly frustrating।


Never Reason from "It Worked 100 Times"

This is not proof:

I ran it many times
and result looked correct.

Race condition can remain latent।

Thread safety must come from:

design
happens-before relationships
synchronization
safe concurrent APIs
immutability

not luck।


Shared Collections

A normal:

ArrayList
HashMap
HashSet

should not automatically be assumed safe for arbitrary concurrent mutation।

Later আমরা শিখব:

ConcurrentHashMap
CopyOnWriteArrayList
BlockingQueue

and other concurrency tools।


Collections.synchronizedList()

Java also provides synchronized wrappers।

Example:

List<String> values =
        Collections.synchronizedList(
                new ArrayList<>()
        );

But even synchronized collections do not make every multi-step compound operation automatically correct।

Example:

if (
        !values.contains(
                value
        )
) {
    values.add(
            value
    );
}

Still requires reasoning about the operation as a whole।


Thread-Safe Component ≠ Thread-Safe Workflow

Suppose every individual repository call is thread-safe।

But business operation:

check inventory
then reserve inventory

spans multiple calls।

The overall workflow can still race।

Thread safety must protect:

business invariant

not merely each method individually।


Example Business Invariant

Requirement:

Course capacity = 1
Only one learner may enroll

Bad:

check remaining seats
then create enrollment

two concurrent requests may both pass the check।

This becomes a wider consistency problem that may require:

atomic operation
transaction
database constraint
lock
compare-and-set

depending on architecture।

In this foundation course, key lesson:

Concurrency correctness often lives at invariant boundaries.

In-Memory Lock Does Not Protect Other Processes

Very important backend concept।

Suppose:

synchronized

protects data in one JVM।

If application runs:

Instance A
Instance B
Instance C

then each JVM has its own lock।

A lock in Instance A does not automatically lock Instance B।


Distributed Systems Boundary

Therefore:

synchronized

solves:

threads sharing one JVM object's lock

It does not solve:

cross-process
cross-server
distributed consistency

Those need different mechanisms।

We do not go deeper in this foundation course।


Thread Safety by Avoiding Sharing

Often best concurrency solution:

Do not share mutable state in the first place.

Strategies:

immutable objects
local variables
message passing
copying
stateless services
thread confinement

These can be safer than locking everything।


Example — Immutable Configuration

record AppConfig(
        String environment,
        int timeoutSeconds
) {
}

Create once।

Then all threads read।

No mutation।

This is much simpler than a mutable configuration object with synchronized getters/setters।


Volatile Immutable Snapshot Pattern

High-level example:

class ConfigHolder {

    private volatile AppConfig current;

    AppConfig current() {
        return current;
    }

    void update(
            AppConfig config
    ) {
        current =
                config;
    }
}

If AppConfig itself is immutable, replacing one reference can be simpler than mutating many fields individually।

Do not generalize this pattern without understanding update consistency requirements, but it illustrates why immutable snapshots combine well with visibility mechanisms।


Multiple Mutable Fields Need One Invariant

Suppose:

class Range {

    private int min;
    private int max;
}

Invariant:

min <= max

Updating them independently with separate volatile fields does not automatically preserve a consistent pair for readers।

Sometimes multiple fields must be protected together as one state transition।


Atomicity Is About Logical State

Concurrency design should ask:

What must appear to happen together?

Maybe:

balance check + deduction

or:

min + max update

or:

check absent + insert

That logical operation should determine synchronization boundary।


Thread Safety Documentation

A class should have a clear concurrency policy।

Examples:

Immutable and thread-safe

Not thread-safe; caller must confine instance

All state guarded by private lock

Designed for concurrent access

Ambiguous ownership creates bugs।


Avoid Exposing Lock Objects

If synchronization uses:

private final Object lock =
        new Object();

keeping it private prevents external code from unexpectedly participating in your lock protocol।

Encapsulation applies to concurrency mechanisms too।


Avoid Synchronizing on Mutable Public Values

Avoid designs like:

synchronized (
        somePublicString
)

or publicly shared objects you do not control।

Use a dedicated private lock when appropriate।


Thread-Safe Does Not Mean Fast

A design can be:

correct but slow

or:

fast but incorrect

First achieve correctness।

Then measure contention and optimize with better primitives if necessary।


Thread Safety and APIs

Method signatures alone may not communicate thread-safety fully।

Example:

void add(
        Course course
);

You still need documentation/design context to know:

Can multiple threads call it?
Does it synchronize?
Does caller own synchronization?

Concurrency and Testing

Concurrency tests can increase confidence but cannot replace reasoning।

Useful approaches may include:

repeated execution
stress tests
many tasks
controlled synchronization
invariant checks

But:

No failure observed

does not prove absence of a race।


Common Mistake 1 — Thinking count++ Is Atomic

It is a read-modify-write operation।

Multiple threads can lose updates।


Common Mistake 2 — Using volatile as a Lock

volatile int count;

does not make:

count++;

safe।


Common Mistake 3 — Synchronizing Only Half the Invariant

Wrong:

synchronized boolean hasBalance(
        long amount
) {
    return balance >=
            amount;
}

void deduct(
        long amount
) {
    balance -=
            amount;
}

The check এবং deduction together form the operation।


Common Mistake 4 — Different Locks for Same State

If readers/writers use different unrelated locks, mutual exclusion may fail।


Common Mistake 5 — Using sleep() for Coordination

Thread.sleep(...)

is not a correctness guarantee that another task finished।


Common Mistake 6 — Sharing Request-Specific Fields

Backend singleton-style service:

private User currentUser;

can cause concurrent requests to overwrite each other।

Keep request-specific state local।


Common Mistake 7 — Assuming Immutable Reference Means Immutable Object

final List<String> values

means reference cannot be reassigned।

List contents may still mutate।


Common Mistake 8 — Overusing Locks

Lock every method without understanding shared invariants can create unnecessary contention and even deadlock risk।

Prefer simple, explicit concurrency boundaries।


Common Mistake 9 — Assuming Thread Safety Solves Distributed Concurrency

A JVM lock protects threads within that lock domain।

It does not coordinate multiple application instances।


Common Mistake 10 — Swallowing InterruptedException

Avoid:

catch (
        InterruptedException exception
) {
}

Interruption often represents cancellation/control signal।

Handle it deliberately।


Practical Example — Unsafe Inventory

final class Inventory {

    private int available =
            1;

    boolean reserve() {
        if (
                available == 0
        ) {
            return false;
        }

        available--;

        return true;
    }
}

Two threads can both see:

available == 1

and both reserve।


Thread-Safe In-Memory Version

final class Inventory {

    private int available =
            1;

    synchronized boolean reserve() {
        if (
                available == 0
        ) {
            return false;
        }

        available--;

        return true;
    }

    synchronized int available() {
        return available;
    }
}

Now:

check + decrement

protected together।


Practical Example — Stop Flag

final class Worker {

    private volatile boolean running =
            true;

    void stop() {
        running =
                false;
    }

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

    private void doWork() {
        // Small unit of work
    }
}

This illustrates visibility-oriented use of volatile


Do Not Extend This Example Blindly

Real worker cancellation may involve:

blocking I/O
interrupts
executors
resource cleanup

A boolean flag alone may not be enough।

This example only demonstrates visibility semantics।


Practical Example — Stateless Service

final class DiscountCalculator {

    long discountedPrice(
            long price,
            int percentage
    ) {
        if (
                percentage < 0
                || percentage > 100
        ) {
            throw new IllegalArgumentException(
                    "Invalid percentage."
            );
        }

        long discount =
                price
                * percentage
                / 100;

        return price
                - discount;
    }
}

No shared mutable state।

Concurrent calls do not interfere through fields।


Practice 1 — Process or Thread?

A running Java application is primarily a:

Answer

Process

এর ভিতরে one or more:

Threads

থাকে।


Practice 2 — Concurrency or Parallelism?

One CPU core alternates between Task A and Task B।

Answer

Concurrency

There may be no physical simultaneous execution।


Practice 3

Two CPU cores execute A and B at the same instant।

Answer

Parallelism

Practice 4 — Shared State

Two threads access same mutable Counter object।

Is it shared mutable state?

Answer

Yes।


Practice 5 — count++

Is:

count++;

atomic for shared concurrency purposes?

Answer

No।

Conceptually:

read
modify
write

Practice 6 — Race Condition

Counter starts:

10

Two threads both read 10, increment, and write 11।

Expected:

12

Actual:

11

What happened?

Answer

Lost update caused by race condition.

Practice 7 — Synchronization Boundary

Which must be protected together?

check balance
then deduct balance

Answer

Both steps as one compound operation।


Practice 8 — Volatile

Does:

volatile int count;

make:

count++;

thread-safe?

Answer

No।


Practice 9 — Volatile Use Case

A simple shared stop flag where one thread writes and another observes।

Could volatile be appropriate?

Answer

Yes, as a basic visibility-oriented pattern, assuming no wider coordination requirement exists।


Practice 10 — Immutable Object

Multiple threads only read a deeply immutable value object।

Do they normally need synchronization just for those reads?

Answer

No mutation means read-only sharing is much easier to make safe।

Proper construction/publication still matters, but no lock is required merely to protect nonexistent mutation।


Practice 11 — Local State

Each method call creates its own:

ArrayList

and does not share it।

What strategy is this close to?

Answer

Thread confinement / local state.

Practice 12 — sleep()

Can:

Thread.sleep(
        100
);

guarantee worker completed?

Answer

No।

Use an actual coordination mechanism।


Practice 13 — join()

What does:

worker.join();

conceptually do?

Answer

Current thread waits for worker to terminate, subject to interruption handling।


Practice 14 — Multiple Application Instances

Does:

synchronized

in JVM A automatically block the same method in JVM B?

Answer

No।

Their locks are in separate processes/JVMs।


Practice 15 — Safer Design

Which is easier to reason about concurrently?

Shared mutable object

Immutable object

Answer

Usually:

Immutable object

True or False

  1. A Java process may contain multiple threads.
  2. Concurrency always means two tasks are physically executing at the same instant.
  3. Parallelism usually involves actual simultaneous execution.
  4. Shared mutable state can create race conditions.
  5. count++ is an atomic shared-state increment.
  6. synchronized can provide mutual exclusion.
  7. Compound operations may need one synchronization boundary.
  8. volatile makes every compound operation atomic.
  9. volatile can help with visibility of a shared flag.
  10. Immutable data is generally easier to share safely.
  11. Method-local request state is often safer than shared mutable fields.
  12. Thread.sleep() is a reliable synchronization mechanism.
  13. join() can wait for another thread to finish.
  14. A JVM lock automatically coordinates every server instance in a cluster.
  15. Thread-safe code should protect business invariants, not just isolated statements.

Answers

1. True
2. False
3. True
4. True
5. False
6. True
7. True
8. False
9. True
10. True
11. True
12. False
13. True
14. False
15. True

Knowledge Check

Question 1

Process এবং Thread-এর difference কী?

Question 2

Concurrency এবং Parallelism-এর difference কী?

Question 3

Shared mutable state কী?

Question 4

Race condition কী?

Question 5

কেন count++ thread-safe নয়?

Question 6

Atomicity বলতে কী বোঝায়?

Question 7

synchronized high level-এ কী provide করে?

Question 8

Critical section কী?

Question 9

Visibility problem কী?

Question 10

volatile এবং synchronized-এর basic difference কী?

Question 11

কেন immutability concurrency সহজ করে?

Question 12

Thread confinement কী?

Question 13

কেন check-then-act operation dangerous হতে পারে?

Question 14

কেন sleep() coordination mechanism নয়?

Question 15

কেন in-memory synchronized distributed lock নয়?


Knowledge Check Answers

Answer 1

Process হলো running program-এর execution environment।

একটি process-এর ভিতরে multiple Threads থাকতে পারে।

Thread হলো process-এর ভিতরে একটি execution path।

Answer 2

Concurrency-তে multiple tasks overlapping সময়ের মধ্যে progress করে।

Parallelism-এ multiple tasks একই physical instant-এ execute করতে পারে।

Answer 3

একই mutable object বা state multiple threads access করলে সেটি shared mutable state।

Answer 4

যখন program result uncontrolled thread interleaving/order-এর উপর depend করে এবং incorrect result possible হয়, সেটি race condition।

Answer 5

কারণ এটি এক conceptual machine-level atomic action নয়।

এতে:

read
increment
write

steps আছে।

Multiple threads interleave করে lost update করতে পারে।

Answer 6

একটি operation অন্য thread-এর দৃষ্টিতে indivisible logical action-এর মতো behave করলে সেটিকে atomic বলা যায়।

Answer 7

Foundation level-এ:

mutual exclusion
+
memory visibility guarantees

provide করে।

একই lock-এর protected code এক সময়ে একটি thread execute করে।

Answer 8

Shared mutable state-এর যে code region concurrent interference থেকে protect করা দরকার।

Answer 9

এক thread state update করলেও অন্য thread সেই updated value reliably observe করবে কি না সেই concern।

Answer 10

synchronized critical section-এর জন্য mutual exclusion এবং visibility provide করে।

volatile shared variable-এর visibility/order semantics provide করতে পারে, কিন্তু compound read-modify-write operation-এর জন্য mutual exclusion দেয় না।

Answer 11

কারণ state change হয় না।

তাই multiple threads-এর মধ্যে:

who writes
when writes
partial update
lost update

এসব coordination problem থাকে না।

Answer 12

Mutable state শুধু একটি thread-এর মধ্যে সীমাবদ্ধ রাখা এবং অন্য threads-এর সাথে share না করা।

Answer 13

কারণ check এবং action-এর মাঝখানে অন্য thread state change করতে পারে।

তাই:

check
+
act

এক logical atomic operation হিসেবে protect করতে হতে পারে।

Answer 14

Sleep শুধু current thread delay করে।

এটি অন্য thread-এর completion বা required state change-এর guarantee দেয় না।

Answer 15

synchronized একটি specific JVM-এর object/monitor lock-এর মধ্যে কাজ করে।

Different JVM/process-এর memory এবং locks আলাদা।


Practical Thread-Safety Checklist

Shared state দেখলে প্রশ্ন করুন:

এই state mutable কি?

কতগুলো thread access করতে পারে?

একাধিক thread write করতে পারে কি?

Read এবং write-এর visibility guarantee কী?

কোন operations logically atomic হওয়া দরকার?

একটি shared invariant আছে কি?

সব accesses একই lock strategy use করছে কি?

এই state local করা যায় কি?

Immutable করা যায় কি?

Concurrent collection ব্যবহার করা উচিত কি?

এই lock কি শুধু one JVM protect করছে?

Concurrency Design Preference

Possible হলে এই order-এ ভাবুন:

1. Avoid shared mutable state

2. Prefer immutable data

3. Keep request/task state local

4. Use thread-safe abstractions

5. Synchronize clear invariants when needed

6. Measure before optimizing lock strategy

Core Mental Model

Concurrency problem দেখলে শুধু ভাববেন না:

Two threads একই line execute করছে কি?

Better question:

What shared state exists?

What invariant must remain true?

Which operations must appear atomic?

How do threads see each other's updates?

Example:

if (
        balance >= amount
) {
    balance -= amount;
}

The important unit is not:

balance -= amount

alone।

The logical operation is:

Check sufficient balance
+
Deduct balance

Concurrency safety must protect that whole decision।


Lesson Summary

এই lesson-এ আমরা Java concurrency এবং thread safety-এর foundation শিখেছি।

আমরা শিখেছি:

  • একটি process-এর ভিতরে multiple threads থাকতে পারে
  • Thread একটি execution path
  • Concurrency এবং Parallelism একই concept নয়
  • Concurrent tasks-এর execution interleave করতে পারে
  • Execution order সাধারণত assume করা যায় না
  • Shared mutable state concurrency bugs-এর major source
  • Race condition thread scheduling/interleaving-এর উপর incorrect result তৈরি করতে পারে
  • count++ atomic নয়
  • Lost update একটি common race condition
  • Thread-safe code concurrent access-এর মধ্যেও correctness maintain করে
  • synchronized mutual exclusion এবং visibility provide করতে পারে
  • Critical section shared state-এর protected operation
  • একই shared state-এর জন্য consistent locking গুরুত্বপূর্ণ
  • Compound operations যেমন check-then-act একসঙ্গে protect করতে হয়
  • Visibility concurrency-এর আলাদা concern
  • volatile simple shared-state visibility use cases-এ useful
  • volatile compound operations atomic করে না
  • Immutability thread-safety reasoning অনেক সহজ করে
  • Stateless design shared mutable state কমায়
  • Method-local state thread confinement তৈরি করতে পারে
  • Mutable state shared হলে synchronization strategy প্রয়োজন হতে পারে
  • Thread.sleep() coordination guarantee নয়
  • join() thread completion-এর জন্য wait করতে পারে
  • Interruption deliberately handle করা উচিত
  • Concurrency bugs timing-dependent হওয়ায় difficult to reproduce
  • Thread-safe component থাকলেও overall workflow race করতে পারে
  • Business invariant concurrency boundary determine করতে পারে
  • synchronized one JVM-এর threads coordinate করে, distributed systems নয়
  • Simpler concurrency design সাধারণত clever locking-এর চেয়ে safer

সবচেয়ে important principle:

Concurrency-এর সবচেয়ে সহজ bug fix
অনেক সময় lock যোগ করা নয়।

Shared mutable state কমানো।

আর concurrency reasoning-এর core question:

What state is shared,
what can change,
and what must remain true
while multiple tasks execute?

Next Lesson

পরবর্তী lesson:

Executors, Callable, Future, and CompletableFuture

আমরা শিখব:

  • Raw Thread তৈরি করার limitation
  • Task এবং Thread-এর difference
  • ExecutorService
  • Thread pools
  • execute()
  • submit()
  • Runnable
  • Callable<T>
  • Future<T>
  • Waiting for results
  • Exceptions from asynchronous tasks
  • Executor shutdown
  • CompletableFuture
  • supplyAsync()
  • runAsync()
  • thenApply()
  • thenAccept()
  • thenCompose()
  • thenCombine()
  • Error handling
  • Blocking বনাম asynchronous composition
  • Common executor mistakes