Concurrency Essentials in Java

Common Terminology

 

  • Thread : Smallest unit of execution that can be scheduled by Operating System.
  • Process : Group of associated threads that execute in the same, shared environment.
  • Task : Single unit of work performed by thread.

 

A Thread can complete multiple independent tasks

but only one task at a time

Type of Threads

  • A system thread is created by the JVM and run in the background of the application e.g garbage collection.
  • A user defined thread is one created by the application developer to accomplish a specific task.

More on Threads

  • The property of executing multiple threads at the same time is referred to as concurrency
  • Operating System uses Thread scheduler to determine which thread should be currently executing.
  • A Thread scheduler employee strategy e.g Round Robin
  • Context Switch is the Process of storing a thread's current state and later restoring the state of thread to continue execution.

Thread Priority

A Thread Priority is numeric value associated with a thread that is taken into consideration by the thread scheduler when determining which threads should currently be executed

 

Constant Variable Value
Thread.MIN_PRIORITY 1
Thread.NORM_PRIORITY 5
Thread.MAX_PRIORITY 10

Default priority of thread is Thread.NORM

 How to create threads ?

  • Implement Runnable Interface
package com.multithreading.demo;

public class RunnableDemo implements Runnable{
    @Override
    public void run() {
        System.out.println("Running Runnable Thread");
    }


    public static void main(String[] args) {
        new Thread(new RunnableDemo())
                .start();
    }
}
package com.multithreading.demo;

public class ThreadDemo extends Thread{

    @Override
    public void run(){
        System.out.println("Running Thread Demo");
    }

    public static void main(String[] args) {
        new ThreadDemo().start();
    }

}
  • Extending Thread Class

Creating a Thread using anonymous inner class

package com.multithreading.demo;

public class SimpleThread {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Running...");
            }
        }).start();
    }
}

Creating Thread using lambda expression

package com.multithreading.demo;

public class SimpleThread {

    public static void main(String[] args) {
        new Thread(()->{
            System.out.println("Running...");
        }).start();
    }
}

Sleep and Join

  • Sleep : To wait for a specific amount of time.
  • Join : Wait until the Thread finishes.
package com.multithreading.demo;

public class SimpleThread {

    static int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(1000L);
                System.out.println("Running 1st Thread");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread thread2 =new Thread(() -> {
            try {
                Thread.sleep(1000L);
                System.out.println("Running 2nd Thread");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("Ended....");
    }
}

ExecuterService

  • ExecutorService creates and manages thread for you. 
  • You first obtain an instance of an ExecutorService interface and then you Send the Service tasks to be processed
  • Executors factory class can be used to create instance of the ExecutorService object.
package com.multithreading.demo;

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

public class SingleExecutorServiceDemo {

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newSingleThreadExecutor();

        executorService.submit(()-> System.out.println("Thread 1"));
        executorService.submit(()-> System.out.println("Thread 2"));
        executorService.submit(()-> System.out.println("Thread 3"));
        executorService.shutdown();

        System.out.println("End");
    }
}

SingleThreadExecutor

  • With a single thread executor results are guaranteed to be executed in the order in which they are added to the executor service.
  • It is important to call shutdown otherwise your application will not terminate.

Safe way of using executor service

package com.multithreading.demo;

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

public class SingleExecutorServiceDemo {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        try {
            executorService.submit(() -> System.out.println("Thread 1"));
            executorService.submit(() -> System.out.println("Thread 2"));
            executorService.submit(() -> System.out.println("Thread 3"+10/0));
        }finally {
            executorService.shutdown();
        }

        System.out.println("End");
    }
}

Useful method with ExecutorService

  • shutdown() : Rejects New Task submitted to the thread executor while continuing to execute any previously submitted task.
  • shutdownNow() : stops all running  tasks and discards any that have not been started. Returns a list of runnable that were submitted to thread executor but were never started.
  • isShutdown() : Returns true when executer service is shutdown.
  • isTerminated() : Returns true when all active tasks are completed. 
package com.multithreading.demo;

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

public class SingleExecutorServiceDemo {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        try {
            executorService.submit(() -> System.out.println("Thread 1"));
            executorService.submit(() -> System.out.println("Thread 2"));
            executorService.submit(() -> {
                try {
                    Thread.sleep(3000L);
                } catch (InterruptedException e) {
//                    e.printStackTrace();
                }
                System.out.println("Thread 3");
            });
        } finally {
            executorService.shutdownNow();
        }

        System.out.println(executorService.isShutdown());
        System.out.println(executorService.isTerminated());
        System.out.println("End");
    }
}

execute and submit in ExecutorService

  • execute : takes a Runnable instance and complete the task asynchronously. It does not tell anything about the result of the task.
  • submit : like execute complete task asynchronously but returns a Future object that can be used to determine if a task is completed.

Future

  • Future is used to determine the result of an asynchronous task .
  • Some of the useful methods which are oftenly used with Future are isDone(), isCancelled() and get().
package com.multithreading.demo;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class SubmitDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        Future<Integer> integerFuture = executorService.submit(() -> 2);

        executorService.shutdown();

        if (integerFuture.isDone()) {
            System.out.println(integerFuture.get());
        }

        if(integerFuture.isCancelled()){
            System.out.println("Your task has been cancelled");
        }
    }
}

Working with Future

Submitting List of callable using invoke all

 

package com.multithreading.demo;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecuterServiceInvokeAll {

    public static void main(String[] args) throws InterruptedException {
        List<Callable<Integer>> runnableList = new ArrayList<>();

        runnableList.add(() -> 1);
        runnableList.add(() -> 2);
        runnableList.add(() -> 3);
        runnableList.add(() -> 4);

        ExecutorService executorService = Executors
                .newSingleThreadExecutor();

        List<Future<Integer>> futureList = executorService.invokeAll(runnableList);
        futureList.forEach((e)->{
            if(e.isDone()){
                try {
                    System.out.println(e.get());
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                } catch (ExecutionException e1) {
                    e1.printStackTrace();
                }
            }
        });

    }
}

awaitTermination() 

This method waits the specified time to complete all tasks, returning sooner if all tasks finish.

package com.multithreading.demo;

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

public class ExecuterServiceAwaitTermination {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.submit(()->{
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        executorService.shutdown();

        executorService.awaitTermination(2000L,TimeUnit.MILLISECONDS);

        if(executorService.isTerminated()){
            System.out.println("Terminated");
        }else{
            System.out.println("On or more tasks still remaining");
        }
    }
}

Scheduling tasks

  • schedule() : Creates and executes a callable task after the given delay
package com.multithreading.demo;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ExecuterServiceSchedulingTasks {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

        executorService
                .schedule(() -> System.out.println("Task executed after 1 second"),
                        1,
                        TimeUnit.SECONDS);

        executorService.shutdown();

    }
}

Regular Task Scheduling after fixed interval

  • scheduleAtFixedRate method creates a new task and submit it to the executor every period, regardless of whether or not the previous task finished.
  • scheduleAtFixedDelay creates a new task after the previous task has finished e.g if the task runs at 12:00 and takes 5 minutes to finish, with a period of 2 minutes, then task will starts at 12:07.

 

package com.multithreading.demo;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ExecuterServiceSchedulingTasks {

    public static void main(String[] args){

        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

        
        executorService
                .scheduleWithFixedDelay(() -> {
                            try {
                                Thread.sleep(2000L);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println("ScheduleWithFixedDelay Scheduled Task to executed after fixed interval");
                        },
                        0,
                        1,
                        TimeUnit.SECONDS);

        executorService
                .scheduleAtFixedRate(() -> {
                            try {
                                Thread.sleep(2000L);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println("ScheduleAtFixedRate Scheduled Task to executed after fixed interval");
                        },
                        0,
                        1,
                        TimeUnit.SECONDS);


    }
}

Increasing Concurrency with Pools

  • newCachedThreadPool() : Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available.
  • newFixedThreadPool() : Creates a thread pool that reuses a fixed number number of threads.
package com.multithreading.demo;

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

class Process implements Runnable{
    int id;

    public Process(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        System.out.println("Thread name::"+Thread.currentThread().getName()+" Start :"+id);
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Thread name::"+Thread.currentThread().getName()+" End :"+id);
    }
}

public class ExecuterServiceDemo {
    public static void main(String[] args) {

//        ExecutorService executorService= Executors.newFixedThreadPool(3);
        ExecutorService executorService= Executors.newCachedThreadPool();

        for (int i = 0; i <= 30; i++) {
            executorService.submit(new Process(i));
        }
        executorService.shutdown();
    }
}

Exercise 1

  • Use a singleThreadExecutor to submit multiple threads.
  • Try shutdown() and shutdownNow() and observe the difference.
  • Use isShutDown() and isTerminate() with ExecutorService.
  • Return a Future from ExecutorService by using callable and use get(), isDone(), isCancelled() with the Future object to know the status of task submitted.
  • Submit List of tasks to ExecutorService and wait for the completion of all the tasks.
  • Schedule task using schedule(), scheduleAtFixedRate() and scheduleAtFixedDelay()
  • Increase concurrency with Thread pools using newCachedThreadPool() and newFixedThreadPool().

Synchronizing Data Access

  • Thread safety in the property of an Object that gaurantees safe execution by multiple threads at the same time.
  • Multiple Threads capable of accessing the same objects, we have to make sure to organize our access to this data such that we don't end up witn invalid or unexpected results.

 

package com.multithreading.demo;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

public class SynchronizeDemo {

    int count;

    public void incrementCount() {
        count++;
    }

    public void worker1() {
        for (int i=1;i<=1000;i++){
            incrementCount();
        }
    }

    public void worker2() {
        for (int i=1;i<=1000;i++){
            incrementCount();
        }
    }

    public static void main(String[] args) throws InterruptedException {

        SynchronizeDemo synchronizeDemo = new SynchronizeDemo();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronizeDemo.worker1();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronizeDemo.worker2();
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(synchronizeDemo.count);
    }

}

Synchronizing methods

We can add synchronize modifier to any instance method to synchronize automatically on the object itself.

package com.multithreading.demo;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

public class SynchronizeDemo {

    int count;

   synchronized public void incrementCount() {
        count++;
    }

    public void worker1() {
        for (int i=1;i<=1000;i++){
            incrementCount();
        }
    }

    public void worker2() {
        for (int i=1;i<=1000;i++){
            incrementCount();
        }
    }

    public static void main(String[] args) throws InterruptedException {

        SynchronizeDemo synchronizeDemo = new SynchronizeDemo();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronizeDemo.worker1();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronizeDemo.worker2();
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(synchronizeDemo.count);
    }

}

Synchronized Blocks

  • Synchronize block in such a way that each thread that arrives will first check if any threads are in block.
  • In this manner a thread acquires the lock.
  • If the lock is available, a single Thread will enter the block and acquire the lock and preventing all other threads from entering.
 public void incrementCount() {
        synchronized(this) {
            count++;
        }
    }

Atomic class

  • Atomic is the property of an operation to be carried out as a single unit of execution without any interference by another Thread.
  • The concurrency API includes numerous useful classes that are conceptually the same as primitive classes but that support atomic operation e.g AtomicBoolean, AtomicInteger, AtomicLong
package com.multithreading.demo;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

public class SynchronizeDemo {

    AtomicInteger count= new AtomicInteger();



    public void worker1() {
        IntStream.range(1, 1001).forEach(e -> count.incrementAndGet());
    }

    public void worker2() {
        IntStream.range(1, 1001).forEach(e -> count.incrementAndGet());
    }

    public static void main(String[] args) throws InterruptedException {

        SynchronizeDemo synchronizeDemo = new SynchronizeDemo();
        Thread thread1 = new Thread(synchronizeDemo::worker1);
        Thread thread2 = new Thread(synchronizeDemo::worker2);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(synchronizeDemo.count);
    }
}

Wait and Notify

  • wait() forces the current thread to wait until some other thread invokes notify() or notifyAll() on the same object.
  • notify() for all threads waiting on this object’s monitor (by using any one of the wait() method), the method notify()notifies any one of them to wake up arbitrarily.
  • notifyAll() This method simply wakes all threads that are waiting on this object’s monitor.
  • wait and notify requires a lock so make sure that you use them under some sync mechanism e.g synchronize block.
package com.multithreading.demo;

public class Demo {

    public void worker1(){
        synchronized (this) {
            System.out.println("Worker1 Started");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Worker1 Done");
        }
    }



    public void worker4(){
        synchronized (this) {
            System.out.println("Worker 4 Started");
            notify();
            System.out.println("Worker 4 Done");
        }
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        new Thread(demo::worker1).start();
        new Thread(demo::worker4).start();
    }
}

Wait with notify

package com.multithreading.demo;

public class Demo {

    public void worker1(){
        synchronized (this) {
            System.out.println("Worker1 Started");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Worker1 Done");
        }
    }

    public void worker2(){
        synchronized (this) {
            System.out.println("Worker 2 Started");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Worker 2 Done");
        }
    }

    public void worker3(){
        synchronized (this) {
            System.out.println("Worker 3 Started");
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Worker 3 Done");
        }
    }



    public void worker4(){
        synchronized (this) {
            System.out.println("Worker 4 Started");
            notifyAll();
            System.out.println("Worker 4 Done");
        }
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        new Thread(demo::worker1).start();
        new Thread(demo::worker2).start();
        new Thread(demo::worker3).start();
        new Thread(demo::worker4).start();
    }
}

Wait with notifyAll

Producer Consumer using wait and notify

package com.multithreading.demo;


import java.util.ArrayList;
import java.util.List;
import java.util.Random;


public class Demo {

    List list = new ArrayList();
    final int MAX = 10;

    void produce() throws InterruptedException {
        Random random = new Random();
        while (true) {
            synchronized (this) {
                while (MAX == list.size()) {
                    wait();
                }
                list.add(random.nextInt(10));
                System.out.println(list);
                notify();
            }
        }
    }

    void consume() throws InterruptedException {
        while (true) {
            synchronized (this) {
                while (list.size() == 0) {
                    wait();
                }
                list.remove(0);
                System.out.println(list);
                notify();
            }
            Thread.sleep(1000L);
        }
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        new Thread(() -> {
            try {
                demo.produce();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                demo.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

Reentract Lock

Reebtract Lock is similar to locking provided by synchronized keyword with extended features. Few of them are as follows:

  • Fairness property provides lock to longest waiting thread, in case of contention.
  • tryLock() obtains the lock only if the lock is available. This reduce blocking of thread.

 

package com.multithreading.demo;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.IntStream;

public class Demo {

    Lock lock = new ReentrantLock(true);
    int count;

    void increment(){
        lock.lock();
        count++;
        lock.unlock();
    }

    public void worker1(){
        IntStream.range(1,1001).forEach(e->increment());
    }

    public void worker2(){
        IntStream.range(1,1001).forEach(e->increment());
    }

    public static void main(String[] args) throws InterruptedException {
        Demo demo = new Demo();
        Thread thread1 = new Thread(demo::worker1);
        Thread thread2 = new Thread(demo::worker2);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();

        System.out.println(demo.count);
    }
}

Deadlock

package com.multithreading.demo;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {

    Lock lock1 = new ReentrantLock(true);
    Lock lock2 = new ReentrantLock(true);

    public void worker1(){
        lock1.lock();
        System.out.println("lock 1 worker 1");
        lock2.lock();
        System.out.println("lock 2 worker 1");
        lock2.unlock();
        lock1.unlock();
    }

    public void worker2(){
        lock2.lock();
        System.out.println("lock 1 worker 2");
        lock1.lock();
        System.out.println("lock 2 worker 2");
        lock2.unlock();
        lock1.unlock();
    }

    public static void main(String[] args) throws InterruptedException {
        Demo demo = new Demo();
        Thread thread1 = new Thread(demo::worker1);
        Thread thread2 = new Thread(demo::worker2);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
    }
}

Solving Deadlock

1st way : Preserve the order

package com.multithreading.demo;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {

    Lock lock1 = new ReentrantLock(true);
    Lock lock2 = new ReentrantLock(true);

    public void worker1(){
        lock1.lock();
        System.out.println("lock 1 worker 1");
        lock2.lock();
        System.out.println("lock 2 worker 1");
        lock2.unlock();
        lock1.unlock();
    }

    public void worker2(){
        lock2.lock();
        System.out.println("lock 1 worker 2");
        lock1.lock();
        System.out.println("lock 2 worker 2");
        lock2.unlock();
        lock1.unlock();
    }

    public static void main(String[] args) throws InterruptedException {
        Demo demo = new Demo();
        Thread thread1 = new Thread(demo::worker1);
        Thread thread2 = new Thread(demo::worker2);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
    }
}

2nd way : Use tryLock

package com.multithreading.demo;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {

    Lock lock1 = new ReentrantLock(true);
    
    
    Lock lock2 = new ReentrantLock(true);
    
    public void acquireLock(Lock lock1, Lock lock2){

        boolean acquireLock1= lock1.tryLock();
        boolean acquireLock2= lock2.tryLock();

        if(acquireLock1 && acquireLock2){
            return;
        }

        if(acquireLock1){
            lock1.unlock();
        }

        if(acquireLock2){
            lock2.unlock();
        }

    }

    public void worker1(){
        acquireLock(lock1,lock2);
        System.out.println("lock 1 worker 1");

        System.out.println("lock 2 worker 1");
        lock2.unlock();
        lock1.unlock();
    }

    public void worker2(){
        acquireLock(lock2,lock1);
        System.out.println("lock 1 worker 2");
        System.out.println("lock 2 worker 2");
        lock2.unlock();
        lock1.unlock();
    }

    public static void main(String[] args) throws InterruptedException {
        Demo demo = new Demo();
        Thread thread1 = new Thread(demo::worker1);
        Thread thread2 = new Thread(demo::worker2);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
    }
}

signal, signalAll and await

package com.multithreading.demo;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {

    Lock lock = new ReentrantLock(true);
    Condition condition = lock.newCondition();

    public void worker1() {
        try {
            lock.lock();
            System.out.println("worker 1 Started");
            condition.await();
            System.out.println("worker 1 Finished");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void worker2() {
        try{
            lock.lock();
            System.out.println("worker 2 Started");
            condition.signal();
            System.out.println("worker 2 Finished");
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Demo demo = new Demo();
        Thread thread1 = new Thread(demo::worker1);
        Thread thread2 = new Thread(demo::worker2);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
    }
}

Concurrent Class

  • Using Concurrent collections is extremely convenient in practice.
  • It prevents us from introducing mistake in own custom implementation such as forgot to synchronize some method. 
  • e.g ConccurentHashMap, ConcurrentLinkedQueue, ConcurrentLinkedDeque etc

Memory Consistency Error

Following code will throw a ConcurrentModificationException at runtime since the keyset() is not updated after the First element is removed.

package com.multithreading.demo;

import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) {
        Map<String, Object> map = new HashMap<>();
        map.put("One", 1);
        map.put("Two", 2);
        for (String key : map.keySet()) {
                map.remove(key);
        }
    }
}

Fixing the memory consistency error

package com.multithreading.demo;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Demo {
    public static void main(String[] args) {
        Map<String, Object> map = new ConcurrentHashMap<>();
        map.put("One", 1);
        map.put("Two", 2);
        for (String key : map.keySet()) {
                map.remove(key);
        }
    }
}
  • ConcurrentHashMap in ordering read/write access such that all access to the class is consistent.
  • In the code above the iterator created by KeySet() is updated as soon as an object is removed from the Map.

Understanding Blocking Queue

  • A blocking queue is a queue that blocks when you try to dequeue from it and the queue is empty, or if you try to enqueue items to it and the queue is already full.
  • A thread trying to dequeue from an empty queue is blocked until some other thread inserts an item into the queue.

Producer Consumer with LinkedBlockingQueue

LinkedBlockingQueue maintains a linked list between elements. Following is the implementation of producer consumer problem via LinkedBlocking Queue.

 

package com.multithreading.demo;


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

public class ProducerConsumerDemo {

    static BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(10);

    public static void main(String[] args) {
        new Thread(()->produce()).start();
        new Thread(()->consume()).start();

    }

    public static void produce() {

        Random random = new Random();
        while (true) {
                blockingQueue.put(random.nextInt(100));
        }
    }


    public static void consume() {
        Random random = new Random();
        while (true){
            try {
                Thread.sleep(1000L);
                Integer element = blockingQueue.poll();
                System.out.println("Element :: "+element+" removed current size is ::"+blockingQueue.size());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

Exercise 2

  • Use Synchronize method to enable synchronization between multiple threads trying to access method at same time.
  • Use Synchronize block to enable synchronization between multiple threads trying to access method at same time.
  • Use Atomic Classes instead of Synchronize method and blocks.
  • Coordinate 2 threads using wait() and notify().
  • Coordinate mulitple threads using wait() and notifyAll()
  • Use Reentract lock for coordinating 2 threads with signal(), signalAll() and wait().
  • Create a deadlock and Resolve it using tryLock().

CountDownLatch

  • A java.util.concurrent.CountDownLatch is a concurrency construct that allows one or more threads to wait for a given set of operations to complete.
  • A CountDownLatch is initialized with a given count. This count is decremented by calls to thecountDown() method.
  • Threads waiting for this count to reach zero can call one of the await() methods. Calling await() blocks the thread until the count reaches zero.
package com.multithreading.demo;


import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

class Processor implements Runnable{

    CountDownLatch countDownLatch;
    String name;

    public Processor(CountDownLatch countDownLatch, String name) {
        this.countDownLatch = countDownLatch;
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("name :: "+name);
        countDownLatch.countDown();
    }
}


public class Demo {
    
    public static void main(String[] args) throws InterruptedException {
        
        CountDownLatch countDownLatch = new CountDownLatch(3);

        Processor processor1 = new Processor(countDownLatch,"Worker 1") ;
        Processor processor2 = new Processor(countDownLatch,"Worker 2") ;
        Processor processor3 = new Processor(countDownLatch,"Worker 3") ;
        new Thread(processor1).start();
        new Thread(processor2).start();
        new Thread(processor3).start();
        countDownLatch.await(2,TimeUnit.SECONDS);
        System.out.println("Ended");

    }
}

Cyclic Barrier

  • Cyclic Barrier helps to coordinate among concurrent tasks. 
  • It helps by pausing between the end of one set of tasks and then start of the next.

Coordination without Cyclic Barrier

package com.multithreading.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

class AnimalCageManager{

    void removeAnimals(){
        System.out.println("Removing animals");
    }

    void addAnimals(){
        System.out.println("Adding Animals");
    }

    void cleanCage(){
        System.out.println("Cleaning Cage");
    }

    public void performTasks(){
        removeAnimals();
        cleanCage();
        addAnimals();
    }

}


public class CyclicBarrier {

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(4);

        AnimalCageManager animalCageManager = new AnimalCageManager();
        IntStream.range(1,5).forEach((e)->{
            executorService.submit(()->animalCageManager.performTasks());
        });

        executorService.shutdown();
    }
}
package com.multithreading.demo;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

class AnimalCageManager{

    void removeAnimals(){
        System.out.println("Removing animals");
    }

    void addAnimals(){
        System.out.println("Adding Animals");
    }

    void cleanCage(){
        System.out.println("Cleaning Cage");
    }

    public void performTasks(CyclicBarrier cyclicBarrier1, CyclicBarrier cyclicBarrier2) throws BrokenBarrierException, InterruptedException {
        removeAnimals();
        cyclicBarrier1.await();
        cleanCage();
        cyclicBarrier2.await();
        addAnimals();
    }
}


public class CyclicBarrierDemo {

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier1 = new CyclicBarrier(4,()-> System.out.println("Removed all animals !!!!"));
        CyclicBarrier cyclicBarrier2 = new CyclicBarrier(4,()-> System.out.println("Cleaned the cage !!!!"));

        ExecutorService executorService = Executors.newFixedThreadPool(4);

        AnimalCageManager animalCageManager = new AnimalCageManager();
        IntStream.range(1,5).forEach((e)->{
            executorService.submit(()-> {
                try {
                    animalCageManager.performTasks(cyclicBarrier1,cyclicBarrier2);
                } catch (BrokenBarrierException e1) {
                    e1.printStackTrace();
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            });
        });

        executorService.shutdown();
    }
}

Coordinate using CyclicBarrier

Semaphore

  • A semaphore controls access to a shared resource through the use of a counter.
  • If the counter is greater than zero, then access is allowed.
  • If it is zero, then access is denied.
  • acquire( ) before accessing the resource to acquire the lock.
  • When the thread is done with the resource, it must call release( ) to release lock.
package com.multithreading.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.stream.IntStream;

class Connection {
    Semaphore semaphore = new Semaphore(3);

    void getConnection(Integer id) {
        try {
            semaphore.acquire();
            Thread.sleep(2000L);
            System.out.println("id :: " + id + " using the connection");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
        }

    }

}

public class SemaphoreDemo {


    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        Connection connection = new Connection();
        try {

            IntStream.range(1, 30).forEach(e->{
                executorService.execute(()->connection.getConnection(e));
            });
        } finally {
            executorService.shutdown();
        }
    }
}

Concurrency Essentials in Java

By Pulkit Pushkarna

Concurrency Essentials in Java

  • 1,075