Skip to content

Multi-Tread Programming

1. What is a thread? Explain the difference between a process and a thread, and compare multiprocessing with multithreading.

The main difference between a process and a thread is memory allocation and independence:

  • A process is an executing program (e.g., web browser, a code editor). Each process has its own private, dedicated memory space. Processes are independent of each other.

  • A thread exists within a process. Multiple threads in the same process share the same memory space, which allows them to communicate easily but also requires careful management to avoid conflicts (by using synchronization).

A thread is the smallest unit of execution within a process. It's a "lightweight process" that has its own execution path but shares memory and resources with other threads in the same process.

Analogy: A process is like a restaurant. A thread is like a single chef working in that restaurant's kitchen. Multiple chefs (threads) can work in the same kitchen (process), sharing the same ingredients and equipment (memory).

Multiprocessing vs. Multithreading

FeatureMultiprocessingMultithreading
DefinitionRunning multiple, independent processes simultaneously.Running multiple threads simultaneously within a single process.
MemoryEach process has its own separate memory space.All threads share the same memory space.
CPU UsageCan utilize multiple CPUs/cores to run different processes.Can utilize multiple CPUs/cores to run different threads of the same process.
ExampleRunning Google Chrome and Microsoft Word at the same time.A word processor using one thread for user input, another for spell-checking, and a third for auto-saving.

2. Explain the Java Thread Model and the life cycle of a thread.

The Java Thread Model is built directly into the language. Every Java program starts with a single thread called the main thread. From this main thread, additional threads can be created and managed.

The java.lang.Thread class and the Runnable interface are the foundation of this model, and the Java Virtual Machine (JVM) handles the complex task of scheduling threads.

Thread life cycle is the various states a thread goes through during its existence.

  • New: The thread object has been created (new Thread()), but its start() method has not yet been called. It is not yet alive.

  • Runnable: The thread is ready to execute. It is waiting for the thread scheduler to allocate CPU time. After the start() method is called, the thread enters this state. This state encompasses both "ready" and "running."

  • Running: Thread is actively executing.

  • Blocked / Waiting : The thread is temporarily inactive and not using any CPU time. Waiting for an external event, such as waiting for I/O to complete. Blocked: Waiting for acquiring a lock on an object. Automatically released when resource becomes available. synchronized(sharedResource){...}

    Waiting: Waiting indefinitely for another thread to perform specific action and notify it. Object.wait(), Thread.join()

    Timed_waiting: Waiting for a limited amount of time. Object.wait(ms), Thread.join(ms), Thread.sleep(ms)

  • Terminated (Dead): The thread has completed its execution because its run() method has finished. Once a thread is terminated, it cannot be restarted.

Part B: Creating and Managing Threads

3. What are the two primary ways to create a thread in Java? Provide a simple program that creates and runs multiple threads.

There are two primary ways to create a thread:

  1. Extending the Thread Class: Create a class that extends the Thread class and overrides its run() method. Call the start() method.

  2. Implementing the Runnable Interface: Create a class that implements the Runnable interface and overrides its run() method. Pass an instance of this class to a Thread object's constructor and call the start() method of the Tread object. This is preferred because it separates the task (the Runnable) from the execution mechanism (the Thread) and allows class to extend another class.

java
class MyRunnable implements Runnable {
    private String threadName;
    
    MyRunnable(String name) {
        this.threadName = name;
    }

    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(threadName 
	            + " is running, count: " + i);
        }
    }
}

class MyThread extends Thread {
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println("MyThread is running, count: " + i);
        }
    }
}

public class CreateThreadDemo {
    public static void main(String[] args) {
        System.out.println("Main thread starting.");

        // Create and run a thread by extending Thread
        MyThread t1 = new MyThread();
        t1.start();

        // Create and run multiple threads by implementing Runnable
        Thread t2 = new Thread(new MyRunnable("Runnable-A"));
        Thread t3 = new Thread(new MyRunnable("Runnable-B"));
        t2.start();
        t3.start();

        System.out.println("Main thread finished.");
    }
}

4. Explain the purpose of the isAlive() and join() methods for managing thread execution. Provide a code example.

  • isAlive(): This method returns a boolean value indicating whether a thread is currently active. It returns true if the thread has been started but has not yet been terminated. It's useful for monitoring the state of another thread.

  • join(): This method makes the currently running thread wait until the thread it is called on finishes its execution. It's essential for coordinating tasks, for example, when the main thread needs to wait for worker threads to complete their work before it can proceed.

java
class WorkerThread implements Runnable {
    public void run() {
        System.out.println("Worker thread starting.");
        try {
            // Simulate some work
            for (int i = 1; i <= 5; i++) {
                System.out.println("Worker working... step " + i);
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            System.out.println("Worker thread interrupted.");
        }
        System.out.println("Worker thread finished.");
    }
}

public class JoinAliveDemo {
    public static void main(String[] args) {
        System.out.println("Main thread starting.");

        Thread worker = new Thread(new WorkerThread());
        worker.start();

        System.out.println("Is worker thread alive? " 
	        + worker.isAlive());

        try {
            System.out.println("Main thread waiting for worker to finish...");
            worker.join(); // Main thread waits here
        } catch (InterruptedException e) {
            System.out.println("Main thread interrupted.");
        }

        System.out.println("Is worker thread alive? " 
	        + worker.isAlive());
        System.out.println("Main thread finished.");
    }
}

5. What are thread priorities and how are they set? What is the default priority?

Thread priorities are integer values that act as a suggestion to the thread scheduler about which threads should be given preference for CPU time. Threads with a higher priority are more important and are generally executed before threads with a lower priority.

However, this is not a guarantee, as the exact behavior depends on the underlying operating system.

Priorities are set using the setPriority(int level) method of the Thread class. The level ranges from 1 to 10. For readability, Java provides three static constants:

  • Thread.MIN_PRIORITY (value 1)
  • Thread.NORM_PRIORITY (value 5)
  • Thread.MAX_PRIORITY (value 10)

The default priority for a thread is Thread.NORM_PRIORITY (5).

A new thread inherits the priority of the thread that created it. Since most threads are created from the main thread (which has a priority of 5), they also start with a priority of 5.

Made with ❤️ for students, by a fellow learner.