No matter what language you use to write your programs, they are all sequential by default. That is, all the instructions we write are executed line by line by the operating system. The next line of code cannot start execution before the current finishes and waits until the current line finishes its execution. If there is an API call to a remote server, the program gets blocked until it receives a response. If a call takes several minutes to finish, then we will wait the program to respond as well. The program just gets blocked and does not respond to our further commands.
Let’s consider some other common scenarios. For example, you are using some social net where you are downloading some videos or images and simultaneously you are chatting with your friends there. You are executing two different tasks at the same time and your program does not get blocked until it finishes one of its tasks. How come? This where multithreading comes into play.
There are many definitions for multithreading and in the next lines a few of them will be outlined.
Multithreading is the ability of a CPU to execute processes or threads independently. All the programs executed by threads. A thread is a lightweight sub-process, the smallest unit of processing.
Multithreading is a process of executing multiple threads simultaneously.
Advantages of multithreading
— we can design more responsive apps — for example, we can do several operations concurrently, e.g. downloading some resources and chatting at the same time;
— we can achieve better resource utilization. By default a Java program is single threaded, there may be several processor cores which can be utilized by applying multithreading;
— overall performance can be increased several times.
Disadvantages of multithreading
— threads manipulate data located on the same memory belong to the same process and we have to deal with synchronization, i.e. make sure that data is consistent
— it is pretty complex to design multithreaded applications and hard to debug in case of bugs
— when there are many threads, a cpu has to switch between the threads. This process is called context switching. Switching between threads is an expensive operations since a cpu has to save the local data of one thread and load the local data of another thread. Ultimately, the overall performance will suffer instead of improving in case too many threads.
Multithreading in Java
In Java, it is very easy to write multithreaded programs. Java provides a basic class for creating threads — Thread class. There are two ways of creating threads either extending Thread class and override run() method or implementing a Runnable interface and pass its implementation to a Thread class as its constructor argument. Let’s consider some example using both ways.
Creating Threads by extending a Thread class
In the code snippet above, we are creating a class Car that extends a Thread class and override its run() method. Inside run() method we just print out the car model and the name of the thread that is executing.
Thread.sleep(1000) — stops this thread for given period of time (in milliseconds). In main method, we are creating two instances (ferrari, bmw) of Car class and calling start() method on each of them. Then print out some message. By default, whenever a java program is run, it is executed by main thread. Running this program gives the following output.
As we see from the output, printing the message is the last command in the program but it is printed out to the console first and it did not wait the execution of method calls — ferrari.start() and bmw.start(). This is the magic of concurrency. No matter how long it takes for methods — ferrari.start() and bmw.start() to execute, we don’t wait for them and just continue executing.
Creating Threads by implementing Runnable interface
The above code snippet does the same logic we discussed above, however it a slightly different than the previous code snippet. We can implement Runnable interface and override its run() method and pass them to Thread class constructor. One of Thread class constructors accepts a Runnable interface as one of its arguments. Running the code snippet will give a similar output.
Considering two ways of creating threads, the second one is thought to be more preferable since multiple inheritance is prohibited in Java, once extending a Thread class you are unable to extend any other class. However, by implementing a Runnable interface you can extend another class as well. This is a small advantage of a second way.
What happens if we call run() method of Thread class?
Let’s consider the above code snippet. In this example, similarly we have created a Car class that extends Thread class, overridden run() method, created two instances of Car class (ferrari, bmw). Instead of calling start() method, we have called run() method on (ferrari, bmw) instances. Running this program gives the following output.
The program executed sequentially, in the order we have written. Calling run() method does not create a new Thread, it behaves as a usual method in Java.
There are several methods of Thread class but most important is start() method. It creates a new thread that executes a given piece of code independently. The other methods — interrupt(), sleep(), setName(), etc.
This was a short introduction to Multithreading in Java. In the next posts, we will explore Life Cycle of Threads, difference between a process and a thread, parallel execution and concurrent execution, etc. A lot more interesting yet to come!