Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Multithreading in Java: Core Concepts and Practical Examples

Tech 1

Multithreading Fundamentals

Concurrency vs. Parallelism

  • Parallelism: Refers to multiple events occurring simultaneously at the same instant, typically enabled by multiple CPU cores where each core executes a separate task concurrently.
  • Concurrency: Involves multiple evants happening within the same time interval, but not necessarily at the exact same moment.

Process and Thread

  • Process: An executing application with its own dedicated memory space. Each process represents a program's execution instance and serves as the fundamental unit managed by the operating system. A single application can spawn multiple processes.
  • Thread: An independent execution unit within a process. Multiple threads can operate concurrently inside a single process, sharing resources while maintaining separate execution flows.

Key Differences Between Process and Thread

  • Process: Has isolated memory (heap and stack), operates independently, and contains at least one thread.
  • Thread: Shares heap memory with other threads in the same process but maintains a private stack. Threads are more resource-efficient compared to processes.

Thread Scheduling

With a single CPU, only one instruction executes at any given time. Multithreading involves rapid context switching where threads take turns using the CPU in tiny time slices. The JVM employs preemptive scheduling, leading to unpredictable execution order among threads.

Approaches to Create Threads in Java

Extending the Thread Class

Constructors

  • Thread(): Creates a new thread object.
  • Thread(String name): Creates a named thread object.

Key Methods

  • String getName(): Retrieves the thread's name.
  • void start(): Initiates thread execution by invoking run().
  • void run(): Contains the thread's task logic.
  • static void sleep(long millis): Pauses the current thread for specified milliseconds.
  • static Thread currentThread(): Returns a reference to the currently executing thread.
class WorkerThreadA extends Thread {
    @Override
    public void run() {
        for (int count = 0; count < 40; count++) {
            System.out.println(getName() + " processing task " + count);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class WorkerThreadB extends Thread {
    @Override
    public void run() {
        for (int count = 0; count < 40; count++) {
            System.out.println(getName() + " handling operation " + count);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        WorkerThreadA threadA = new WorkerThreadA();
        WorkerThreadB threadB = new WorkerThreadB();
        threadA.setName("Worker-A");
        threadB.setName("Worker-B");
        threadA.start();
        threadB.start();
        
        for (int i = 0; i < 50; i++) {
            System.out.println("Main thread executing " + i);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Implementing the Runnable Interface

Constructors

  • Thread(Runnable target): Creates a thread with a specified Runnable target.
  • Thread(Runnable target, String name): Creates a named thread with a Runnable target.
class TaskRunnerA implements Runnable {
    @Override
    public void run() {
        for (int idx = 0; idx < 50; idx++) {
            System.out.println(Thread.currentThread().getName() + " executing step " + idx);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class TaskRunnerB implements Runnable {
    @Override
    public void run() {
        for (int idx = 0; idx < 50; idx++) {
            System.out.println(Thread.currentThread().getName() + " performing action " + idx);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class RunnableDemo {
    public static void main(String[] args) {
        TaskRunnerA runnerA = new TaskRunnerA();
        TaskRunnerB runnerB = new TaskRunnerB();
        Thread thread1 = new Thread(runnerA);
        Thread thread2 = new Thread(runnerB, "CustomThread");
        thread1.start();
        thread2.start();
        
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + " running iteration " + i);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Comparing Thread and Runnable

  • Extending Thread limits resource sharing since each instance operates in separate memory.
  • Implementing Runnable facilitates resource sharing by allowing multiple Thread objects to reference the same Runnable instance.

Advantages of Runnable

  1. Enables multiple threads to share common code and resources.
  2. Avoids Java's single inheritance constraint.
  3. Compatible with thread pools that require Runnable or Callable implementations.

Anonymous Inner Class for Thread Creation

public class AnonymousThreadExample {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println("Anonymous thread executing " + i);
                    try {
                        Thread.sleep(400);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        
        for (int j = 0; j < 50; j++) {
            System.out.println(Thread.currentThread().getName() + " processing " + j);
            try {
                Thread.sleep(400);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Thread Lifecycle and States

Threads transition through various states during their lifecycle:

  1. NEW: Thread object created but not started.
  2. RUNNABLE: Thread is ready or actively executing.
  3. BLOCKED: Thread is waiting for a lock or I/O operation.
  4. WAITING: Thread is indefinitely waiting for another thread's signal.
  5. TIMED_WAITING: Thread is waiting with a specified timeout.
  6. TERMINATED: Thread has completed execution or terminated abnormally.

Use Thread.getState() to retrieve the current state.

Main Thread

The main thread is the initial thread that executes the main method. It may spawn child threads and does not necessarily finish last.

Thread Priority

Thread priority ranges from 1 (lowest) to 10 (highest), with a default of 5. Higher priority threads get more CPU time, but lower priority threads can still execute.

  • setPriority(int priority): Sets thread pirority.
  • getPriority(): Returns current priority.

Thread Control Methods

join()

Causes the calling thread to pause until the joined thread completes.

public static void main(String[] args) throws InterruptedException {
    WorkerThreadA worker = new WorkerThreadA();
    worker.setName("Worker-A");
    worker.start();
    worker.join(); // Main thread waits for worker to finish
    for (int i = 0; i < 50; i++) {
        System.out.println("Main thread resuming " + i);
        Thread.sleep(300);
    }
}

yield()

Temporarily pauses the current thread to allow other threads to execute, moving it to the runnable state.

Daemon Threads

Daemon threads (e.g., garbage collector) run in the background and terminate automatically when all non-daemon threads finish. Use setDaemon(true) before starting the thread.

WorkerThreadA daemonWorker = new WorkerThreadA();
WorkerThreadB regularWorker = new WorkerThreadB();
daemonWorker.setName("Daemon");
regularWorker.setName("Regular");
daemonWorker.setDaemon(true);
daemonWorker.start();
regularWorker.start();

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.