Concurrency & Parallelism in Java


  • Concurrency and Parallelism are often used with respect to multithreaded programs and both have different meanings in this context.

  • Concurrency is about processing more than one task at same time but not necessarily simultaneously, It is applied to reduce the response time of the system by using the single processing unit.
  • In a concurrent application, two tasks can start, run, and complete in overlapping time periods i.e Task-2 can start even before Task-1 gets completed.
  • Therefore Concurrent programming means factoring a program into independent modules or units of concurrency. 
  • In java you achieve concurrency by making a class java.lang.Thread class in one of these 3 ways
    • By implementing the Runnable interface.
    • By implementing the Callable interface.
    • By extending the Thread
  • In the below simple program, we have 3 Threads T1, T2, T3 and the runtime does not execute this sequentially and switches between them
public class ConcurrentThreadDemo {
    public static void main(String[] args) {
        
        Thread t1 = new Thread(new ConcurrentTask(), "First Thread (T1)");
        Thread t2 = new Thread(new ConcurrentTask(), "Second Thread (T2)");
        Thread t3 = new Thread(new ConcurrentTask(), "Third Thread (T3)");

        // now, let's start all three threads
        t1.start();
        t2.start();
        t3.start();
    }
    private static class ConcurrentTask implements Runnable {

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " is running now");
        }
    }
}
  • The output also shows the behavior of the above program and depicts how it is different as compared to executing this in a sequential manner
Third Thread (T3) is running now
First Thread (T1) is running now
Second Thread (T2) is running now

Process finished with exit code 0
  • Concurrency promises to perform certain tasks faster as these tasks can be divided into subtasks and these subtasks can be executed at same time.
  • 2 issues which needs to be handled with such programs are
    • Thread interference errors: It occurs when the multiple threads try to read or write a shared variable concurrently, and these read and write operations overlap in execution
    • Memory consistency errors: It occurs when different threads have inconsistent views of the same data.
  • Concurrent or Multithreaded programs helps to achieve Higher throughput, Build more responsive applications and utilize resources efficiently

  • With Parallelism we can break a program into smaller tasks and can execute them at exact same time in parallel by leveraging the multiple CPU cores.
  • It is devised for the purpose of increasing the computational speed by using multiple processors and involves several independent computing processing units or computing devices which are operating in parallel and performing tasks in order to increase computational speed-up and throughput.
  • To achieve parallelism your application must have more than one thread running – and each thread must run on separate CPUs / CPU cores .
  • The diagram below illustrates a task which is being split up into 4 subtasks. These 5 subtasks are being executed by 5 different threads, which run on 4 different CPUs. This means, that parts of these subtasks are executed concurrently (those executed on the same CPU), and parts are executed in parallel (those executed on different CPUs).
  • In Java, the fork/join framework provides support for parallel programming by splitting up a task into smaller tasks to process them using the available CPU cores
  • Java 8’s parallel streams  under the hood uses the fork/join framework to execute parallel tasks.
public class ParallelExample {

    public static void main(String[] args) {

        List<String> stringList = List.of("Ananya", "Jocelyn", "Julia", "Laila", "Riddhima", "Trisha");

        System.out.println("With || ism");
        stringList.parallelStream().map(String::toUpperCase).forEach(s ->
        {
            System.out.println(s + " " + Thread.currentThread().getName());
        });

        System.out.println("Without || ism");
        stringList.stream().map(String::toUpperCase).forEach(s ->
        {
            System.out.println(s + " " + Thread.currentThread().getName());
        });

    }

}
  • The above program converts strings to upper case and prints the string and thread name on console. The output of parallel stream shows 4 threads working and the output without parallel stream method shows single thread acting on it
With || ism

ANANYA ForkJoinPool.commonPool-worker-7
JULIA ForkJoinPool.commonPool-worker-7
RIDDHIMA ForkJoinPool.commonPool-worker-7
TRISHA ForkJoinPool.commonPool-worker-5
LAILA main
JOCELYN ForkJoinPool.commonPool-worker-3

Without || ism

ANANYA main
JOCELYN main
JULIA main
LAILA main
RIDDHIMA main
TRISHA main
  • The fork/join framework was designed to speed up the execution of tasks that can be divided into other smaller subtasks, executing them in parallel by leveraging multiple CPU cores and then combining their results to get a single one.
  • ForkJoinPool design is based on the “Divide-And-Conquer” algorithm; each task is split into subtasks until they cannot be split anymore, they get executed in parallel and once they’re all completed, the results get combined.
  • When we create a new ForkJoinPool, the default level of parallelism (number of threads) will be by default the number of available processors in our system, a number that gets returned by the method Runtime.availableProcessors().
  • Below is an example of a long computation program which uses parallel method
import java.util.ArrayList;
import java.util.List;

public class ParallelLongComputation {

public static void main(String[] args) {

long currentTime=System.currentTimeMillis();
List<Integer> data=new ArrayList<Integer>();
for (int i = 0; i < 100000; i++) {
data.add(i);
}

long sum=data.stream()
.parallel()
.map(i ->(int)Math.sqrt(i))
.map(number->performSummation(number))
.reduce(0,Integer::sum);

System.out.println(sum);

}

public static int performSummation(int number)
{
int sum=0;
for (int i = 1; i < 1000000; i++) {
int div=(number/i);
sum+=div;

}
return sum;
}
}
  • The CPU Activity shows how the 4 cores in my laptop was engaged
  • Parallel processing not always give a better output and it depends on what you are using this feature for. There are cases where this has caused severe performance degradation.
  • In some cases code tweaking has to be performed for different target architectures for improved performance. They are very powerful if used in the correct context.

Concurrency is about executing multiple threads of same task correctly and efficiently by controlling access to shared resource

Parallelism is about breaking down large tasks into smaller tasks and running them simultaneously which may lead to have the same start and end time for the sub tasks.

Thank you . Share your comments & feedback.

7 thoughts on “Concurrency & Parallelism in Java

  1. Hey, this was simple and very well articulated.Helped me to understand the concepts Wasn’t aware of the parallel methods in collection. Will read about this more. Thank you for sharing

  2. A very well covered topic in short and concise manner..
    I really find Parallel streams good enough to speed up requests for large Lists processing though there is an overhead in them.
    One must always use them if this overhead can be negotiated..

Leave a Reply