Common Terminology
A Thread can complete multiple independent tasks
but only one task at a time
Type of Threads
More on Threads
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 ?
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();
}
}
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
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
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
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
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
Future
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
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
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
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
Synchronizing Data Access
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
public void incrementCount() {
synchronized(this) {
count++;
}
}
Atomic class
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
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:
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
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);
}
}
}
Understanding Blocking 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
CountDownLatch
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
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
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();
}
}
}