DEV Community

Akanksha Sharma
Akanksha Sharma

Posted on

Multi threading

What are threads?

a thread is a part of a process under execution. When we run a java program, we often just run the process of JVM. JVM triggers the the 'main' thread of the program under execution for us and creates a call stack beginning with main().

We can have multiple threads doing different things in, what appears to be the same time.

Why would I use multiple threads?

using multiple threads enable us to perform multiple tasks within the same application. For example,

  • While using an application like a text editor, a thread caters to editing tasks, another can maintain cache, another can run spell check etc.
  • On a server, we can have multiple threads catering to multiple clients
  • While playing games, we can have dedicated threads for user control while other threads can implement game logic
  • We can have multiple threads doing numerous asynchronous tasks for any application

How to Use Multiple Threads?

There are two ways of implementing multi threading:

  1. Implement Runnable Interface
  2. Extend Thread Class

The Runnable Interface and the Thread class are provided in the, java.langs package, therefore, are implicitly accessible in all java programs.

For understanding the implementation of multi threading. Lets take an example of teaching at a school. Bob is a teacher, he teaches. Sally is a worker, she works.

Implementing Runnable vs extending Thread

Bob is a specialized worker (a teacher), whereas Sally is a generalized worker who needs a job.

In order to make a bob thread, we first need to define a teacher

class Teacher extends Thread
{
    @Override
    public void run()
    {
        System.out.println("teach teach 👨‍🏫");
    }
}
Enter fullscreen mode Exit fullscreen mode

the Thread class (with a capital T) defines a worker, a thread is an instance of the Thread class which is a worker. Instead of defining the teacher, we can also define the job of teaching

class Teach implements Runnable
{
    @Override
    public void run() {
        System.out.println("teach teach 👨‍🏫");
    }
}
Enter fullscreen mode Exit fullscreen mode

Here we implement the Runnable interface. In both cases, we must override the method run(). run() contains the set of instructions for getting the job done.

And finally we can get Bob and Sally to work

public class School
{
    public static void main(String[] args) {
        Teacher bob=new Teacher();
        bob.start();
        Thread sally=new Thread(new Teach());
        sally.start();
    }
}
Enter fullscreen mode Exit fullscreen mode

we instantiate the teacher bob and the call the method start(), this creates a separate call stack that starts with run() defined in the class Teacher. Similarly, we instantiate the thread sally, and pass an instance of the job Teach in its constructor. On calling start(), another call stack beginning with the run() defined in class Teach is created.

When we use the Runnable interface, we essentially define a job and then assign it to a thread. Whereas, when we extend the Thread class, we are defining a worker.

When to use either?

Apart from the conceptual difference, there's also a practical difference between the two. After extending class Thread, class Teacher cannot extend any other classes, whereas class Teach can still extend other classes.

So if you wish to extend other classes, you must implement the Runnable interface.

Now we have 3 call stack, a main, one crated by bob, and one created by sally. All working at the apparent same time.

Apparent, huh?

Even though we are using multiple threads, They are all part of the same process, the JVM. Even though your PC might have multiple processor, the JVM must run on only one of them. Therefore, all threads have access to that same processor. Even though it looks like the JVM allows multiple threads to execute concurrently, in reality all threads take turns using the processor.

Allocation of computing resources is done by java's thread scheduler. It is some sort of a gate keeper who gets to decide which thread will get to use which resources, and for how long.

Thread Scheduler

The thread scheduler works at will, and there's no way for us to control or predict how it should schedule the threads. Although we can influence its decision, but we cannot know for certain when will it schedule a thread for execution. It may even preempt certain threads from completing and let other threads execute first. Or it may starve other threads.

How do multiple threads run?

Threads exist in 3 different states. Runnable (ready), Running, and Asleep.

3 states of threads

In the above example, Teacher bob=new Teacher(); and Thread sally=new Thread(new Teach()); creates 2 new thread objects. bob.start(); and sally.start(); bring them in ready state. Then its upto the Thread Scheduler to pick them and let them execute their jobs, or interrupt them and put them to sleep. Ultimately, they will finish their tasks as their call stacks fall out of existence.

We can time an interrupt using the sleep() method. It guarantees that for the specified period of time, the thread shall not execute. Let's redefine the teacher's job to teach 5 lectures in a day.

class Teacher extends Thread {
    public void teach() {
        System.out.println(this.getName()+" teach");
    }

    @Override
    public void run() {
        for(int i=0; i<5; i++) {
            teach();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now lets instantiate bob and sally and run the program.

public class School
{
    public static void main(String[] args) {
        Teacher bob=new Teacher();
        bob.setName("Bob");
        Teacher sally=new Teacher();
        sally.setName("Sally");

        bob.start();
        sally.start();
    }
}
Enter fullscreen mode Exit fullscreen mode

here are the results from 3 runs I made:

experiment 1

you can see preemption in the first 2 cases. However, in the third case, Sally was made to wait until Bob finishes.

Lets make an explicit interrupt and make Sally and Bob take a 5 sec (or 5000 milliseconds) nap after every lecture.

class Teacher extends Thread {
    public void teach() {
        System.out.println(this.getName()+" teach");
    }

    @Override
    public void run() {
        for(int i=0; i<5; i++) {
            teach();
            try {
                sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

sleep() throws InterruptedException, although its unlikely to be interrupted. However, the exception must be handled.

results from the run:

experiment 2

here Bob and Sally did go to sleep after every lecture. In Run 3, we can see Sally going to sleep, waking up and even teaching again even before Bob got the chance to teach.

Further Reading

Now multi threading seems great. We can use it for anything and everything. Right? githR?
problem

Top comments (0)