Java Thread Basics: Understand Threads, Concurrency and Parallelism

0

Thread Basics in Java

A thread is the smallest unit of execution inside a Java application. It allows a program to perform multiple tasks concurrently instead of making every task wait for the previous one to finish.

For example, imagine a desktop application that is downloading a file while also responding to button clicks. If both operations were performed by a single execution path, a slow download could make the entire interface appear frozen. With multiple threads, the download can run independently while the application remains responsive.

Important: Multithreading does not simply mean "doing many things at exactly the same time." Threads provide multiple independent paths of execution. Whether they actually execute simultaneously depends on the available CPU cores and how the operating system schedules them.

What Is a Thread?

A thread is an independent path of execution within a process. Every Java application starts with at least one thread, commonly called the main thread.

When the JVM starts a Java application, the main thread begins executing the statements inside the main() method. Additional threads can then be created when the application needs other tasks to execute independently.

public class ThreadBasics {

    public static void main(String[] args) {

        System.out.println("Main thread is running");

        System.out.println("Thread name: "
                + Thread.currentThread().getName());
    }
}

The Thread.currentThread() method returns the thread that is currently executing the code. Calling getName() retrieves its name.

When this program starts normally, the output will usually identify the executing thread as main.

Process vs Thread

A process is a running program, while a thread is an execution path inside that process.

Process Thread
Represents a running application. Represents an execution path inside a process.
Usually has its own memory space. Threads within the same process share process memory.
More expensive to create and manage. Generally lighter than creating a separate process.
Processes are isolated from one another. Threads can directly access shared application data.

This shared-memory characteristic makes threads powerful, but it also introduces one of the most important challenges in multithreading: multiple threads can access and modify the same data at the same time.

Why Do We Need Threads?

Threads are useful when an application has tasks that can make progress independently. Common examples include handling user requests, performing background calculations, reading files, communicating with external services, and processing large amounts of data.

  • Keeping applications responsive while background work is performed.
  • Handling multiple client requests in server applications.
  • Performing independent calculations concurrently.
  • Processing input/output operations without blocking unrelated work.
  • Making better use of systems with multiple CPU cores.
public class TaskExample {

    public static void main(String[] args) {

        System.out.println("Task 1 started");
        System.out.println("Task 1 completed");

        System.out.println("Task 2 started");
        System.out.println("Task 2 completed");
    }
}

In this example, the statements execute sequentially. Task 2 does not begin until Task 1 has completed. In a multithreaded design, independent tasks can be assigned to different threads so that their execution can overlap.

Concurrency vs Parallelism

These two terms are related, but they are not identical.

Concurrency means multiple tasks can make progress during the same period. The CPU may switch rapidly between threads, giving the application the ability to handle multiple activities.

Parallelism means multiple tasks are actually executing at the same time, typically on different CPU cores.

Concept Meaning Typical Situation
Concurrency Multiple tasks make progress during the same time period. One CPU rapidly switches between threads.
Parallelism Multiple tasks execute simultaneously. Different CPU cores execute different threads.

Remember: Concurrency is about dealing with multiple tasks at once; parallelism is about executing multiple tasks at the same time.

The Main Thread

Every ordinary Java application begins execution with a thread called the main thread. It is responsible for executing the statements inside the main() method.

public class MainThreadDemo {

    public static void main(String[] args) {

        Thread currentThread = Thread.currentThread();

        System.out.println("Thread Name: "
                + currentThread.getName());

        System.out.println("Thread Priority: "
                + currentThread.getPriority());

        System.out.println("Thread ID: "
                + currentThread.threadId());
    }
}

Here, Thread.currentThread() gives us a reference to the currently executing thread. From that object, we can inspect information such as its name, priority, and unique thread ID.

Thread Execution Model

A useful way to understand threads is to think of each thread as having its own execution path. The JVM can manage several such paths within the same application.

Application
    |
    +-- Main Thread
    |      |
    |      +-- main()
    |
    +-- Worker Thread
    |      |
    |      +-- Background Task
    |
    +-- Another Thread
           |
           +-- Another Task

Although these threads have separate execution paths, threads belonging to the same process share important resources such as the application's heap memory. Each thread, however, has its own execution stack.

Stack and Heap in Multithreading

This distinction becomes extremely important when learning synchronization and thread safety.

  • Each thread has its own stack, which contains its method calls and local variables.
  • Threads within the same process share the heap, where objects and instance data are stored.
  • Shared heap data can be accessed by multiple threads.
  • Unsafe modification of shared data can produce race conditions.

Industry insight: Many multithreading bugs are difficult to reproduce because their outcome depends on timing. Code may work correctly hundreds of times and then fail when two threads happen to interleave their operations differently.

Example: Two Threads Working Independently

public class ThreadBasics {

    public static void main(String[] args) {

        Thread firstThread = new Thread(() -> {

            for (int i = 1; i <= 3; i++) {
                System.out.println("First thread: " + i);
            }
        });

        Thread secondThread = new Thread(() -> {

            for (int i = 1; i <= 3; i++) {
                System.out.println("Second thread: " + i);
            }
        });

        firstThread.start();
        secondThread.start();
    }
}

The two threads are started independently. Their output may appear in different orders because the JVM and operating system decide when each thread gets CPU time.

For example, the output could look like this:

First thread: 1
Second thread: 1
First thread: 2
First thread: 3
Second thread: 2
Second thread: 3

But another execution could produce a different ordering. This is normal. Unless the program explicitly coordinates the threads, you should not assume that one thread will always finish before another.

Why Thread Output Order Can Change

Calling start() does not mean "execute this thread immediately from beginning to end." It requests the JVM to make the thread eligible for execution. The operating system's scheduler ultimately determines when the thread gets CPU time.

Therefore, this code:

firstThread.start();
secondThread.start();

does not guarantee that all work in firstThread will execute before secondThread begins.

Key idea: Calling start() starts a new thread of execution. Calling a thread's run() method directly does not create a new thread; it simply executes that method on the current thread.

Common Beginner Mistakes

  • Assuming thread execution order is guaranteed.
  • Thinking that creating a Thread object automatically starts execution.
  • Calling run() when the intention is to start a new thread.
  • Assuming concurrency always makes a program faster.
  • Ignoring shared data when multiple threads access the same objects.
  • Assuming multiple threads always execute simultaneously on separate CPU cores.

Best Practices

  • Use threads when tasks can genuinely benefit from concurrent execution.
  • Keep shared mutable state to a minimum.
  • Never depend on an accidental thread execution order.
  • Use proper synchronization or concurrency utilities when shared state must be protected.
  • Prefer higher-level concurrency mechanisms such as executors for production applications rather than manually creating large numbers of threads.

Interview Insights

A common interview question is: "What is a thread?" A strong answer is: A thread is an independent path of execution within a process. Multiple threads can execute concurrently and share the process's resources, while each thread maintains its own execution stack.

Another common question is: "What is the difference between start() and run()?" The key distinction is that start() asks the JVM to begin execution on a new thread, whereas calling run() directly executes the method on the thread that called it.

Quick Revision

Concept Key Point
Thread An independent path of execution within a process.
Main Thread The thread that normally begins execution of the main() method.
Concurrency Multiple tasks make progress during the same period.
Parallelism Multiple tasks execute simultaneously, often on different CPU cores.
Shared Heap Threads in the same process can access common objects and data.
Thread Stack Each thread maintains its own execution stack and local variables.
start() Begins execution through a new thread.
run() Executes the method directly on the current thread when called normally.

The most important idea to carry forward is this: a thread gives a Java application another path of execution, allowing independent work to make progress concurrently. That power comes with responsibility because threads can share application data and execute in unpredictable orders. Once this foundation is clear, concepts such as thread creation, lifecycle, synchronization, race conditions, and thread safety become much easier to understand.

Post a Comment

0Comments
Post a Comment (0)