Monday, June 1, 2020

Multi-threading

The process of executing multiple tasks (also called threads) simultaneously is called multithreading. The
primary purpose of multithreading is to provide simultaneous execution of two or more parts of a program to make maximum use of CPU time. A multithreaded program contains two or more parts that can run concurrently. It enables programmers to write in a way where multiple activities can proceed simultaneously within a single application.

The main purpose of multithreading is to provide simultaneous execution of two or more parts of a program to maximum utilize the CPU time. A multithreaded program contains two or more parts that can run concurrently. ... RUNNABLE – A thread executing in the Java virtual machine is in this state.
  • Thread is a light weight sub process
  • It is the smallest independent unit of a program
  • Contains a separate path of execution
  • Every java program contains at least one thread
  • A thread is created and controlled by by the java.lang.Thread class
Executing more than one thread at a time is known multithreading. In multithreading, each and every thread is separate independent part of the same application.

Advantages of Multithreading:
  • Enable programmers to do multiple things  at one time.
  • Programmers can divide long program into threads and execute them in parallel which will eventually increase speed of program execution
  • Improves performance and concurrency
  • Simultaneous access to multiple applications
There are two approaches to create a thread.
  1. Extending Thread class
  2. Implementing Runnable 
What is ThreadPool in Java?
A thread pool reuses previously created threads to execute current tasks and offers a solution to the problem of thread cycle overhead and resource thrashing.Since the thread is already existing when the request arrives, the delay introduced by thread creation is eliminated, making the application more responsive.
  • Java provides the Executor framework which is centered around the Executor interface, its sub-interface –ExecutorService and the class-ThreadPoolExecutor, which implements both of these interfaces. By using the executor, one only has to implement the Runnable objects and send them to the executor to execute.
  • They allow you to take advantage of threading, but focus on the tasks that you want the thread to perform, instead of thread mechanics.
  • To use thread pools, we first create a object of ExecutorService and pass a set of tasks to it. ThreadPoolExecutor class allows to set the core and maximum pool size.The runnables that are run by a particular thread are executed sequentially.

CountDownLatch in Java

CountDownLatch is used to make sure that a task waits for other threads before it starts. To understand its application, let us consider a server where the main task can only start when all the required services have started.
Working of CountDownLatch:
When we create an object of CountDownLatch, we specify the number of threads it should wait for, all such thread are required to do count down by calling CountDownLatch.countDown() once they are completed or ready to the job. As soon as count reaches zero, the waiting task starts running.
package com.cerotid.multithreading;
package com.cerotid.multithreading;
package com.udemy.multithreading;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class Processor implements Runnable {

    private CountDownLatch latch;

    public Processor(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {
        System.out.println("Started");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        latch.countDown();
    }

}

public class App {

    public static void main(String[] args) {

        CountDownLatch latch = new CountDownLatch(3);

        ExecutorService executor = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 3; i++) {
            executor.submit(new Processor(latch));

        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Completed");
    }
}

Producer-Consumer solution using threads in Java

In computing, the producer–consumer problem (also known as the bounded-buffer problem) is a classic example of a multi-process synchronization problem. The problem describes two processes, the producer and the consumer, which share a common, fixed-size buffer used as a queue.
  • The producer’s job is to generate data, put it into the buffer, and start again.
  • At the same time, the consumer is consuming the data (i.e. removing it from the buffer), one piece at a time.
Problem
To make sure that the producer won’t try to add data into the buffer if it’s full and that the consumer won’t try to remove data from an empty buffer.
Solution 
The producer is to either go to sleep or discard data if the buffer is full. The next time the consumer removes an item from the buffer, it notifies the producer, who starts to fill the buffer again. In the same way, the consumer can go to sleep if it finds the buffer to be empty. The next time the producer puts data into the buffer, it wakes up the sleeping consumer.
An inadequate solution could result in a deadlock where both processes are waiting to be awakened.
package com.udemy.multithreading;

import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class App {

    private static BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(10);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    producer();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    consumer();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();
    }

    private static void producer() throws InterruptedException {
        Random random = new Random();
        while (true) {
            queue.put(random.nextInt(100));
        }
    }

    private static void consumer() throws InterruptedException {
        Random random = new Random();
        while (true) {
            Thread.sleep(100);

            if (random.nextInt(10) == 0) {
                Integer value = queue.take();

                System.out.println("Taken value: " + value + "; Queue size is: " + queue.size());
            }
        }
    }
}
What is Polling and what are problems with it?
The process of testing a condition repeatedly till it becomes true is known as polling.
Polling is usually implemented with the help of loops to check whether a particular condition is true or not. If it is true, certain action is taken. This waste many CPU cycles and makes the implementation inefficient.
For example, in a classic queuing problem where one thread is producing data and other is consuming it.
How Java multi threading tackles this problem?
To avoid polling, Java uses three methods, namely, wait(), notify() and notifyAll().
All these methods belong to object class as final so that all classes have them. They must be used within a synchronized block only.
  • wait()-It tells the calling thread to give up the lock and go to sleep until some other thread enters the same monitor and calls notify().
  • notify()-It wakes up one single thread that called wait() on the same object. It should be noted that calling notify() does not actually give up a lock on a resource.
  • notifyAll()-It wakes up all the threads that called wait() on the same object.

package com.udemy.multithreading;

import java.util.Scanner;

public class Processor {

    public void produce() throws InterruptedException {
        synchronized (this) {
            System.out.println("Producer thread running............");
            wait();
            System.out.println("Resumed.");
        }
    }

    public void consume() throws InterruptedException {
        
        Scanner scanner = new Scanner(System.in);
        
        Thread.sleep(2000);
        
        synchronized (this) {
            System.out.println("Waiting for return key.");
            scanner.nextLine();
            System.out.println("Return key pressed.");
            notify();
            Thread.sleep(5000);
            
        }
    
    }
}



By Navin Reddy:
Example I: by extending Thread class
package com.cerotid.multithreading;

class Hi extends Thread{
    public void run() {                 //run() is the internal method of Thread
        for (int i = 0; i <= 5; i++) {
            System.out.println("Hi");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

class Hello extends Thread{
    public void run() {
        for (int i = 0; i <= 5; i++) {
            System.out.println("Hello");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

public class ThreadDemo {

    public static void main(String[] args) {

        Hi obj1 = new Hi();
        Hello obj2 = new Hello();

        obj1.start();
        try {
            Thread.sleep(10); // add delay of time between two threads
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        obj2.start();
    }
}
Output:
Hi
Hello
Hi
Hello
Hi
Hello
Hi
Hello
Hi
Hello
Hi
Hello


Example II: By implementing Runnable interface
package com.cerotid.multithreading;

class Hi implements Runnable{
    public void run() {                 //run() is the internal method of Thread
        for (int i = 0; i <= 5; i++) {
            System.out.println("Hi");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

class Hello implements Runnable{
    public void run() {
        for (int i = 0; i <= 5; i++) {
            System.out.println("Hello");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

public class ThreadDemo {

    public static void main(String[] args) {

        Runnable obj1 = new Hi();
        Runnable obj2 = new Hello();

        Thread t1 = new Thread(obj1); //create the object of thread and pass the object of Runnable
        Thread t2 = new Thread(obj2);
        
        t1.start();
        try {
            Thread.sleep(10); // add delay of time between two threads
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        t2.start();
        
    }
}
Output:
Hi
Hello
Hi
Hello
Hi
Hello
Hi
Hello
Hi
Hello
Hi
Hello

Udemy Examples:

package com.udemy.multithreading;

class Runner extends Thread {

    public void run() {

        for (int i = 0; i < 10; i++) {
            System.out.println("Hello " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}

public class App {

    public static void main(String[] args) {
        Runner runner1 = new Runner();
        runner1.start();
        
        Runner runner2 = new Runner();
        runner2.start();
    }

}
output:
Hello 0
Hello 0
Hello 1
Hello 1
Hello 2
Hello 2
Hello 3
Hello 3
Hello 4
Hello 4
Hello 5
Hello 5
Hello 6
Hello 6
Hello 7
Hello 7
Hello 8
Hello 8
Hello 9
Hello 9

By implementing Runnable interface:
package com.udemy.multithreading;

class R implements Runnable{

    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Hello " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
}
public class App2 {

    public static void main(String[] args) {
        Thread t1 = new Thread(new R());
        Thread t2 = new Thread(new R());
        
        t1.start();
        t2.start();
    }

}
Synchronization:
  • Used to solve data inconsistency problem
  • Synchronized is the modifier applicable only for methods and blocks
  • can not apply for classes and variables
  • Synchronized keyword in java creates a block of code known as critical section
  • To enter a critical section, a thread needs to obtain the corresponding object's lock
Use of synchronized keyword:
package com.udemy.multithreading;

public class App {

    private int count = 0;

    private synchronized void increment() {
        count++;
    }

    public static void main(String[] args) {

        App app = new App();
        app.doWork();
    }

    private void doWork() {
        Thread t1 = new Thread(new Runnable() {

            public void run() {
                for (int i = 0; i < 10000; i++) {
                    increment();
                }
            }

        });
        Thread t2 = new Thread(new Runnable() {

            public void run() {
                for (int i = 0; i < 10000; i++) {
                    increment();
                }
            }
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println("Count is: " + count);
    }

}

No comments:

Post a Comment