Threads.

Concurrency needs a mechanism. Something that executes code independently. On most systems, that mechanism is threads. Today we understand what threads are and how they work.

A thread is a sequence of instructions that can execute independently.

Your program starts with one thread: the main thread. This thread executes your code from the beginning.

You can create additional threads. Each thread has its own instruction pointer. It knows where it is in the code. Each thread executes its own sequence of instructions.

Multiple threads can run concurrently. While one thread waits, another runs. On multiple cores, multiple threads can run simultaneously.

Think of threads as workers. Each worker follows instructions. Multiple workers can work at the same time on different tasks.

In Kotlin and Java, you create a thread by providing code to execute.

val thread equals Thread with a lambda printing "Hello from thread".

The code inside the braces runs in the new thread.

Call thread dot start to begin execution. The thread runs concurrently with your main thread.

You can also use the thread builder: thread with a lambda printing "Hello". This creates and starts the thread in one step.

When you create a thread, you define what work it will do. When you start it, that work begins executing.

A thread goes through states.

New: the thread is created but not yet started.

Runnable: the thread is ready to run. It might be running or waiting for CPU time.

Blocked or Waiting: the thread is waiting for something. A lock, I/O completion, or a sleep duration.

Terminated: the thread has finished its work. It cannot be restarted.

You cannot go backward. A terminated thread stays terminated. To do similar work, create a new thread.

The lifecycle matters because you interact with threads at different states. Join waits for termination. Interrupt signals a waiting thread.

Often you need to wait for a thread to finish.

thread dot join blocks the current thread until the target thread completes.

Start three threads. Call join on each. Your main thread waits for all three to finish.

This is coordination. The main thread cannot proceed until background work is done.

You can join with a timeout: thread dot join 1000 waits up to one second. If the thread is still running, join returns anyway.

Join is how you synchronize completion. One thread waits for another.

All threads in a process share the same memory.

One thread creates a variable. Another thread can read and write it.

This is powerful. Threads communicate through shared data. No need to copy or send messages.

This is also dangerous. Two threads writing the same variable can corrupt it. Two threads reading and writing can see inconsistent values.

Shared memory is the source of most concurrency bugs. Race conditions happen because threads share state.

Understanding this is crucial. Threads do not have isolated memory. Everything is shared unless you are careful.

Sometimes you want data that is not shared.

Thread-local storage gives each thread its own copy of a variable.

In Kotlin: val threadLocal equals ThreadLocal of Int. Each thread can set and get its own value.

Thread-local is useful for context that should not leak between threads. Transaction IDs. User sessions. Request context.

Use thread-local when sharing would cause problems.

Threads are not free.

Memory: each thread needs a stack. Typically hundreds of kilobytes to a few megabytes. A thousand threads means gigabytes of memory just for stacks.

Creation time: starting a thread takes time. The OS allocates resources, sets up the stack. Creating thousands quickly is slow.

Context switching: when the CPU switches between threads, it saves and restores state. Too many threads means too much switching overhead.

Because of these costs, you cannot have millions of threads. Hundreds to thousands is typical.

Thread pools help. Reuse threads instead of creating new ones. But the fundamental cost remains.

Use threads for CPU-bound parallel work. Split computation across cores.

Use threads for blocking I/O. One thread can wait for network while others work.

Do not use threads for everything. They are heavy. For massive concurrency, lighter abstractions exist.

Threads are the foundation. Higher-level tools like coroutines are built on threads. Understanding threads helps you understand everything built on them.

Let us summarize.

A thread is an independent sequence of execution.

Create with Thread with code block and start with start method.

Threads go through lifecycle: new, runnable, waiting, terminated.

Use join to wait for completion.

Threads share memory. This enables communication but causes problems.

Threads have costs: memory, creation time, context switching.

Threads are the fundamental concurrency mechanism. Know them well.