Working with threads in Java

October 15, 2020

How to create, start, pause, stop and join threads in Java programming language.

Start a thread

package dev.bitek.multithreading.example1;

public class Multithreading {
    public static void main(String[] args) {
        Thread thread = new Thread();
        thread.start();
    }
}

This code starts new thread along side the main Java thread that does nothing.
To make the new thread do something we can do it in four ways.

1. Extend the Thread class

package dev.bitek.multithreading.example2;

public class ThreadExample2 {
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
            System.out.println("[MyThread] Start");
            // do hard work here
            System.out.println("[MyThread] Finish");
        }
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread("Thread subclass");
        thread.start();
    }
}

Output:

Thread subclass
[MyThread] Start
[MyThread] Finish

2. Implement the Runnable interface for a class

package dev.bitek.multithreading.example3;

public class ThreadExample3 {
    public static class MyTask implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
            System.out.println("[MyTask] Start");
            // do hard work here
            System.out.println("[MyTask] Finish");
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyTask(), "Thread running MyTask as runnable");
        thread.start();
    }
}

Output:

Thread running MyTask as runnable
[MyTask] Start
[MyTask] Finish

3. Implement the Runnable interface as an anonymous class

package dev.bitek.multithreading.example4;

public class ThreadExample4 {  
    public static void main(String[] args) {
        Runnable myTask = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
                System.out.println("[MyTask] Start");
                // do hard work here
                System.out.println("[MyTask] Finish");
            }
        };

        Thread thread = new Thread(myTask, "Thread running MyTask from anonymous class");
        thread.start();
    }
}

Output:

Thread running MyTask from anonymous class
[MyTask] Start
[MyTask] Finish

4. Implement the Runnable interface with a lambda expression

package dev.bitek.multithreading.example5;

public class ThreadExample5 {
    public static void main(String[] args) {
        Runnable myTask = () -> {
            System.out.println(Thread.currentThread().getName());
            System.out.println("[MyTask] Start");
            // do hard work here
            System.out.println("[MyTask] Finish");
        };

        Thread thread = new Thread(myTask, "Thread running MyTask from lambda func");
        thread.start();
    }
}

Output:

Thread running MyTask from lambda func
[MyTask] Start
[MyTask] Finish

Start multiple threads

package dev.bitek.multithreading.example6;

public class ThreadExample6 {
    public static void main(String[] args) {
        Runnable myTask = () -> {
            String threadName = Thread.currentThread().getName();
            long threadID = Thread.currentThread().getId();

            System.out.println(threadName);
            System.out.printf("[MyTask %d] Start\n", threadID);
            // do hard work here
            System.out.printf("[MyTask %d] Finish\n", threadID);
        };

        Thread thread1 = new Thread(myTask, "Thread running MyTask 1");
        thread1.start();

        Thread thread2 = new Thread(myTask, "Thread running MyTask 2");
        thread2.start();
    }
}

Output:

Thread running MyTask 1
Thread running MyTask 2
[MyTask 15] Start
[MyTask 15] Finish
[MyTask 14] Start
[MyTask 14] Finish

or

Thread running MyTask 2
Thread running MyTask 1
[MyTask 14] Start
[MyTask 14] Finish
[MyTask 15] Start
[MyTask 15] Finish

Because we have no guarantee in which order the CPU will schedule the two runnables to execute,
we cannot determine apriori which thread will run first.

Put a thread to sleep

package dev.bitek.multithreading.example7;

public class ThreadExample7 {
    public static void main(String[] args) {
        Runnable myTask = () -> {
            String threadName = Thread.currentThread().getName();

            System.out.printf("[%s] Started\n", threadName)

            try {
                Thread.sleep(5000)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.printf("[%s] Finished\n", threadName)
        };

        Thread sleepyThread = new Thread(myTask, "Thread running MyTask");
        sleepyThread.start();
    }
}

Output:

[Thread running MyTask] Started
[Thread running MyTask] Finished ---> (after 5 seconds of waiting)

Stop a thread

To be able to stop a thread from running we need to manage the stopping operation ourselves.

We can achieve this using the synchronized keyword on the methods we need exclusive access guarantees.
In this way, a synchronized method can be called by only one thread at the same time on the same Runnable instance.

package dev.bitek.multithreading.example8;

public class ThreadExample8 {
    public static class StoppableTask implements Runnable {
        private boolean stopped = false;

        public synchronized void stop() {
            this.stopped = true;
        }

        public synchronized boolean isStopped() {
            return this.stopped;
        }

        private void sleep(long millis) {
            try {
                Thread.sleep(millis);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            System.out.println("[Stoppable task] Running");
            while (!isStopped()) {
                sleep(1000);
                System.out.println("...zzzZZZzzz...");
            }
            System.out.println("[Stoppable task] Finished");
        }
    }

    public static void main(String[] args) {
        StoppableTask myTask = new StoppableTask();
        Thread thread = new Thread(myTask, "MyTask");
        thread.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myTask.stop();
    }
}

Output:

[Stoppable task] Running
...zzzZZZzzz...
...zzzZZZzzz...
...zzzZZZzzz...
[Stoppable task] Finished

Mark a thread as daemon

By default, a thread that is still running when the main thread finishes all its work
keeps the Java VM alive and the process waits for the other thread to terminate.

package dev.bitek.multithreading.example9;

public class ThreadExample9 {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            while (true) {
                sleep(1000);
                System.out.println("Running");
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        
        sleep(3000);
        System.out.println("Main thread finished work");
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Output:

Running
Running
Main thread finished work
Running
Running
...
...
Running ---> The newly spawned thread keeps the process running even after the main thread finished its work

To prevent this Behavior we need to mark a thread as a daemon before starting it:

Thread thread = new Thread(runnable);
thread.setDaemon(true);
thread.start();

Output:

Running
Running
Main thread finished work
Running ---> Right after main thread finished its work the other thread stops its execution.

Keep in mind that the daemon thread is stopped in an undefined state,
thus the code running inside it can be in the middle of executing some important work
and might leave the work half finished.

Waiting for another thread to terminate

Make a thread wait for another thread to finish before stopping its execution.

Main thread has no other work to do after starting the other thread and it finishes immediately:

package dev.bitek.multithreading.example11;

public class ThreadExample11 {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            for (int i = 0; i < 5; i++) {
                sleep(1000);
                System.out.println("Running");
            }
        };

        Thread thread = new Thread(runnable);
        thread.setDaemon(true);
        thread.start();
        System.out.println("Main thread finished");
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Output:

Main thread finished

To make the main thread wait for the another thread to finish we call the thread.join() method on the other thread:

package dev.bitek.multithreading.example12;

public class ThreadExample12 {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            for (int i = 0; i < 5; i++) {
                sleep(1000);
                System.out.println("Running");
            }
        };

        Thread thread = new Thread(runnable);
        thread.setDaemon(true);
        thread.start();

        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Output:

Running
Running
Running
Running
Running
Main thread finished