Common Terminology
A Thread can complete multiple independent tasks
but only one task at a time
Type of Threads
More on Threads
Thread Life Cycle
When we create a new Thread object using new operator, thread state is New Thread. At this point, thread is not alive and it’s a state internal to Java programming.
Concurrency
When an application is capable of executing two tasks virtually at same time, we call it concurrent application. Though here tasks run looks like simultaneously, but essentially they may not. They take advantage of CPU time-slicing feature of operating system where each task run part of its task and then go to waiting state. When first task is in waiting state, CPU is assigned to second task to complete it’s part of task.
Parallelism
It literally physically run parts of tasks OR multiple tasks, at the same time using multi-core infrastructure of CPU, by assigning one core to each task or sub-task.
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(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000L);
System.out.println("Running 1st Thread");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
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();
try {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("Thread 1");
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("Thread 2");
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
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");
}
}
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(new Runnable() {
@Override
public void run() {
System.out.println("Thread 1");
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("Thread 2");
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
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(new Runnable() {
@Override
public void run() {
System.out.println("Thread 1");
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("Thread 2");
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000L);
System.out.println("Thread 3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
} 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(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 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(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 1;
}
});
runnableList.add(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 2;
}
});
runnableList.add(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 3;
}
});
runnableList.add(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 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);
System.out.println("Thread Running");
} 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(new Runnable() {
@Override
public void run() {
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(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000L);
System.out.println("ScheduleWithFixedDelay Scheduled Task to executed after fixed interval");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
,
0,
1,
TimeUnit.SECONDS);
executorService
.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000L);
System.out.println("ScheduleAtFixedRate Scheduled Task to executed after fixed interval");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
,
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();
}
}
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.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 incrementCount() {
synchronized (this) {
count.incrementAndGet();
}
}
public void worker1() {
for (int i = 1; i <= 1000; i++) {
count.incrementAndGet();
}
}
public void worker2() {
for (int i = 1; i <= 1000; i++) {
count.incrementAndGet();
}
}
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);
}
}
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");
System.out.println("Worker 4 Done");
notify();
}
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.worker1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
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");
System.out.println("Worker 4 Done");
notifyAll();
}
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.worker1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
demo.worker2();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
demo.worker3();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
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(new Runnable() {
@Override
public void run() {
try {
demo.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
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;
public class Demo {
Lock lock = new ReentrantLock(true);
int count;
void increment(){
lock.lock();
count++;
lock.unlock();
}
public void worker1(){
for (int i = 1; i <= 1000; i++) {
increment();
}
}
public void worker2(){
for (int i = 1; i <= 1000; i++) {
increment();
}
}
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
demo.worker1();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
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(new Runnable() {
@Override
public void run() {
demo.worker1();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
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(){
lock1.lock();
System.out.println("lock 1 worker 2");
lock2.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(new Runnable() {
@Override
public void run() {
demo.worker1();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
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");
lock1.unlock();
lock2.unlock();
}
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
demo.worker1();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
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");
System.out.println("worker 2 Finished");
condition.signal();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
demo.worker1();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
demo.worker2();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
Concurrent Collection
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.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class ProducerConsumerDemo {
static BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(10);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
produce();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
consume();
}
}).start();
}
public static void produce() {
Random random = new Random();
while (true) {
try {
blockingQueue.put(random.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Produce :: " + blockingQueue);
}
}
public static void consume() {
Random random = new Random();
while (true) {
try {
Thread.sleep(1000L);
Integer element = blockingQueue.take();
System.out.println("Consume :: " + blockingQueue);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Exercise
Exercise (Cont.)