In Java programming What is concurrency, parallelism, multi-threading ? [Everything you need to know]

  • Updated : 22-06-2024 00:36
  • By : Phosphataz
  • Introduction


    Humans can :

    • - drive a car and talk at the same time
    • - read a book or post and eat or have a coffee at the same time
    • - cook and talk
    • - etc.. you get the idea.

    We can do multiple tasks at the same time, we are multitask species.


    Nowadays computers also are multitasks machines, they can play music, when you are writing an article or you send a message using email while downloading a file from internet...etc...thanks to operating systems who help managing these task.

    Back in the days computers didn't have operating system, they execute one program at a time and the program had access to all the machine's resources (ram, network, disk ) until the end of it's execution.

    Now operating system manage programs execution by allocating cpu time to each program running, when time elapse the operating system has the power to stop the program and switch to another program.


    Processes


    The operating system run programs as processes, not the binary it self. A process is an instance of a program that is running and have cpu time, and access to other resources (network, files, socket, memory).

    In java program we can manage a process using the process abstract class. To create a process we can use the ProcessBuilder class, it has a method start() to start a new process and return us an instance of Process class

    For example on Ubuntu to spawn a new process in java :

    /**
     * ProcessExample
     */
    public class ProcessExample {
        public static void main(String[] args)
        {
            try {
                // create a new process to run vscode on  
                ProcessBuilder builder = new ProcessBuilder("code");
                Process vscodeProcess = builder.start();
            }
            catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    

    To compile and run :

    // to compile
    javac ProcessExample.java
    
    // to run
    java ProcessExample
    


    We can manage the vscode app process with the vscodeProcess variable :

    • - get process pid :
    // get vscode app process pid
    long vscodePid = vscodeProcess.pid();
    
    // print the result
    System.out.println("vscodeProcess pid : " + vscodePid);
    


    • - destroy the process
    // destroy the vscode app process after 10 sec
    Thread.sleep(10000); // sleep 10 sec
    vscodeProcess.destroyForcibly(); // destroyForcibly() method destroy the Process instance
    


    • - get the exit value
    // we can get the exit value of the process after destroyed
    int vscodeExitValue = vscodeProcess.exitValue();
    System.out.println("vscodeProcess exit value : " + vscodeExitValue); 
    


    Complete code :

    /**
     * ProcessExample
     */
    public class ProcessExample {
        public static void main(String[] args)
        {
            try {
                // create a new process to run vscode on  
                ProcessBuilder builder = new ProcessBuilder("code");
                Process vscodeProcess = builder.start();
    
                // get vscode app process pid
                long vscodePid = vscodeProcess.pid();
                System.out.println("vscodeProcess pid : " + vscodePid);
                
                // destroy the vscode app process after 10 sec
                Thread.sleep(10000); // sleep 10 sec
                vscodeProcess.destroyForcibly(); // destroyForcibly() method destroy the Process instance
    
                // we can get the exit value of the process after destroyed
                int vscodeExitValue = vscodeProcess.exitValue();
                System.out.println("vscodeProcess exit value : " + vscodeExitValue);
    
            }
            catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    



    Threads


    In a process we can have multiple theads. Thread is stream of a program, it's the basic or smallest unit of sequence of instructions to which the operating system can allocate cpu time.

    They are also called lightweight process, because all the threads share the same wide resource (memory space, file handles, network cpu time etc..) of the process in which they are running.

    They are widely used :

    • in GUI programs (throughout libraries such as awt package) : to enhance responsiveness for example.
    • in web apps to handle multiple requests.


    Threads have advantages and inconveniences :

    • advantages :
    • - improve performance of processes that can be split into multiple independent unit by running these unit concurrently.
    • - Also on multiprocessor systems threads can exploit hardware parallelism by scheduling simultaneously multiple threads on multiple cpu


    • inconveniences :
    • - risk of race conditions : since threads within the same process share the same memory space, there is a risk that multiple threads try to access to the same data at the same time or even modify the same data.
    • - base performance : in the case of process with multiple threads the context switches can waste significant time pausing one thread and activating another thread instead of running the actual code.


    Create thread in Java

    In java when you the JVM run a program, it start a default thread that run the entry point method (main method). After that you can create a thread by :

    • 1 - extending the Thread class : This class can handle thread, so any class extending this Thread class become threadable.
    • by extending this class you should override the run() method.
    • Using this class : we have access to many methods we can call on the thread object.
    • For example we can create and start a thread : in the main method we start the InnerThreadExample class that extend Thread class.
    public class ThreadExample {
        public static void main(String[] args)
        {
            // create thread object 
            InnerThreadExample iExample = new InnerThreadExample();
    
            // start the thread
            iExample.start();
            
        }
    }
    
    
    /**
     * InnerThreadExample ==> extend Thread Class
     */
    class InnerThreadExample extends Thread{
    
        // override the run method of Thread
        public void run(){
            System.out.println("Running InnerThreadExample thread...");
        }
        
    }
    
    
    



    • 2 - implementing the Runnable interface : This is often the preferred method because, after implementing the Runnable interface we can still extend classes, and also it's more intuitive since we are trying to use the thread functionality , not extending.
    • With this approche, to create a thread we :
    • - create an object of type Runnable
    • - pass that object to the thread class it selft.
    • - then we use the thread class directly this time.
    • For example a runnable object that print 10 time hello, world. The we pass that object to Thread that run it.
    public class ThreadExample {
        public static void main(String[] args)
        {
           
            // create thread with Runnable type
            PrintHelloWorld pWorld = new PrintHelloWorld();
            
            // create a thread that will use the runnable object
            Thread pThread = new Thread(pWorld);
            
            // start the thread
            pThread.start();
            
        }
    }
    
    
    
    /**
     * PrintHelloWorld ==> implement runnable interface
     */
    class PrintHelloWorld implements Runnable{
    
        // override Runnable run method
        public void run(){
            print();
        }
    
    
        private void print(){
            for(int i=0; i<10; i++){
                System.out.println(i + " - Hello, world");
            }
        }
    
    
    }
    


    To interact with threads, in either met we can use : .

    • - .start() method to start the thread :
    // start the thread
    pThread.start();
    
    • - .isAlive() method to check if a method is still alive :
    // check if the thread is still alive
    System.out.println("thread is alive  ? : " + pThread.isAlive());
    
    • - .getState() method to get a thread state :
    // get the thread state
    State st = pThread.getState();
    System.out.println(st.name());
    
    • - and many more methods : .sleep(), .wait() etc... Check the doc for more.


    Complete code is here :

    import java.lang.Thread.State;
    
    
    public class ThreadExample {
        public static void main(String[] args)
        {
            // create thread object with Thread type
            InnerThreadExample iExample = new InnerThreadExample();
    
    
            // start the thread
            iExample.start();
    
    
            // create thread with Runnable type
            PrintHelloWorld pWorld = new PrintHelloWorld();
            
            // create a thread that will use the runnable object
            Thread pThread = new Thread(pWorld);
    
    
            // start the thread
            pThread.start();
    
    
            // get the thread state
            State st = pThread.getState();
            System.out.println(st.name());
            
            // check if the thread is still alive
            System.out.println("thread is alive  ? : " + pThread.isAlive());
    
    
        }
    }
    
    
    /**
     * InnerThreadExample ==> extend Thread Class
     */
    class InnerThreadExample extends Thread{
        // override the run method of Thread
        public void run(){
            System.out.println("Running InnerThreadExample thread...");
        }
        
    }
    
    
    /**
     * PrintHelloWorld ==> implement runnable interface
     */
    class PrintHelloWorld implements Runnable{
    
        // override Runnable run method
        public void run(){
            print();
        }
        private void print(){
            for(int i=0; i<10; i++){
                System.out.println(i + " - Hello, world");
            }
        }
    }
    


    To compile and run :

    // to compile
    javac ThreadExample.java
    
    // to run
    java ThreadExample
    


    Multi-threading


    Is the process of running multiple threads in the same process. For example a GUI application we can have an event thread that user input input event and IO thread that handle the io task


    Concurrency


    Is the act of processing multiple task at nearly the same time. It require context switching between tasks. This is what humans does by the way.


    Parallelism


    Is the act of processing multiple tasks on multiple CPUs at the same time.


    Conclusion


    Concurrency is dealing with a lot of things at once.

    Parallelism is doing a lot of thing at once


    Image that resume process, thread and operating system (OS) :

    Process A : has 3 threads running

    Process B : has 1 thread running

    Process C : has 2 thread running


    At each time on a cpu the operating manage to only run process and one thread inside that process.