Suresh Sencha
CPU
The CPU, often referred to as the brain of the computer, is
responsible for executing instructions from programs. It performs
basic arithmetic, logic, control, and input/output operations
specified by the instructions.
Core
A core is an individual processing unit within a CPU. Modern CPUs
can have multiple cores, allowing them to perform multiple tasks
simultaneously.
A quad-core processor has four cores, allowing it to perform four
tasks simultaneously. For instance, one core could handle your web
browser, another your music player, another a download manager,
and another a background system update.
Program
A program is a set of instructions written in a programming
language that tells the computer how to perform a specific task
Microsoft Word is a program that allows users to create and edit
documents.
Process
Suresh Sencha
A process is an instance of a program that is being executed. When a
program runs, the operating system creates a process to manage its
execution.
When we open Microsoft Word, it becomes a process in the
operating system.
Thread
A thread is the smallest unit of execution within a process. A process
can have multiple threads, which share the same resources but can
run independently.
A web browser like Google Chrome might use multiple threads for
different tabs, with each tab running as a separate thread.
Multitasking
Multitasking allows an operating system to run multiple processes
simultaneously. On single-core CPUs, this is done through time-
sharing, rapidly switching between tasks. On multi-core CPUs, true
parallel execution occurs, with tasks distributed across cores. The
OS scheduler balances the load, ensuring efficient and responsive
system performance.
Example: We are browsing the internet while listening to music
and downloading a file.
Suresh Sencha
Multitasking utilizes the capabilities of a CPU and its cores. When an
operating system performs multitasking, it can assign different tasks
to different cores. This is more efficient than assigning all tasks to a
single core.
Multithreading
Multithreading refers to the ability to execute multiple threads
within a single process concurrently.
A web browser can use multithreading by having separate threads
for rendering the page, running JavaScript, and managing user
inputs. This makes the browser more responsive and efficient.
Multithreading enhances the efficiency of multitasking by breaking
down individual tasks into smaller sub-tasks or threads. These
threads can be processed simultaneously, making better use of the
CPU’s capabilities.
In a single-core system:
Both threads and processes are managed by the OS scheduler
through time slicing and context switching to create the illusion of
simultaneous execution.
In a multi-core system:
Suresh Sencha
Both threads and processes can run in true parallel on different
cores, with the OS scheduler distributing tasks across the cores to
optimize performance.
Time Slicing
• Definition: Time slicing divides CPU time into small intervals
called time slices or quanta.
• Function: The OS scheduler allocates these time slices to
different processes and threads, ensuring each gets a fair share of
CPU time.
• Purpose: This prevents any single process or thread from
monopolizing the CPU, improving responsiveness and enabling
concurrent execution.
Context Switching
• Definition: Context switching is the process of saving the state
of a currently running process or thread and loading the state of
the next one to be executed.
• Function: When a process or thread’s time slice expires, the OS
scheduler performs a context switch to move the CPU’s focus to
another process or thread.
• Purpose: This allows multiple processes and threads to share
the CPU, giving the appearance of simultaneous execution on a
single-core CPU or improving parallelism on multi-core CPUs.
Suresh Sencha
Multitasking can be achieved through multithreading where each
task is divided into threads that are managed concurrently.
While multitasking typically refers to the running of multiple
applications, multithreading is more granular, dealing with
multiple threads within the same application or process.
Multithreading in Java
Java provides robust support for multithreading, allowing
developers to create applications that can perform multiple tasks
simultaneously, improving performance and responsiveness.
In Java, multithreading is the concurrent execution of two or more
threads to maximize the utilization of the CPU. Java’s
multithreading capabilities are part of the [Link] package, making
it easy to implement concurrent execution.
In a single-core environment, Java’s multithreading is managed by
the JVM and the OS, which switch between threads to give the
illusion of concurrency.
The threads share the single core, and time-slicing is used to manage
thread execution.
In a multi-core environment, Java’s multithreading can take full
advantage of the available cores.
Suresh Sencha
The JVM can distribute threads across multiple cores, allowing true
parallel execution of threads.
A thread is a lightweight process, the smallest unit of processing.
Java supports multithreading through its [Link] class and
the [Link] interface.
When a Java program starts, one thread begins running
immediately, which is called the main thread. This thread is
responsible for executing the main method of a program.
public class Test {
public static void main(String[] args) {
[Link]("Hello world !");
}
}
To create a new thread in Java, you can either extend the Thread
class or implement the Runnable interface.
Method 1: extend the Thread class
1. A new class World is created that extends Thread.
2. The run method is overridden to define the code that constitutes
the new thread.
3. start method is called to initiate the new thread.
public class Test {
public static void main(String[] args) {
World world = new World();
Suresh Sencha
[Link]();
for (; ; ) {
[Link]("Hello");
}
}
}
public class World extends Thread {
@Override
public void run() {
for (; ; ) {
[Link]("World");
}
}
}
Method 2: Implement Runnable interface
1. A new class World is created that implements Runnable.
2. The run method is overridden to define the code that constitutes
the new thread.
3. A Thread object is created by passing an instance of World.
4. start method is called on the Thread object to initiate the new
thread.
public class Test {
public static void main(String[] args) {
World world = new World();
Thread thread = new Thread(world);
[Link]();
for (; ; ) {
[Link]("Hello");
}
}
}
public class World implements Runnable {
@Override
public void run() {
for (; ; ) {
[Link]("World");
}
}
}
Suresh Sencha
Thread Lifecycle
The lifecycle of a thread in Java consists of several states, which a
thread can move through during its execution.
• New: A thread is in this state when it is created but not yet
started.
• Runnable: After the start method is called, the thread becomes
runnable. It’s ready to run and is waiting for CPU time.
• Running: The thread is in this state when it is executing.
• Blocked/Waiting: A thread is in this state when it is waiting
for a resource or for another thread to perform an action.
• Terminated: A thread is in this state when it has finished
executing.
public class MyThread extends Thread{
@Override
public void run() {
[Link]("RUNNING"); // RUNNING
try {
[Link](2000);
} catch (InterruptedException e) {
[Link](e);
}
}
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
[Link]([Link]()); // NEW
[Link]();
[Link]([Link]()); // RUNNABLE
[Link](100);
[Link]([Link]()); // TIMED_WAITING
[Link]();
[Link]([Link]()); // TERMINATED
}
}
Suresh Sencha
Runnable vs Thread
Use Runnable when you want to separate the task from the thread,
allowing the class to extend another class if needed. Extend Thread
if you need to override Thread methods or if the task inherently
requires direct control over the thread itself, though this limits
inheritance.
Thread methods
1. start( ): Begins the execution of the thread. The Java Virtual
Machine (JVM) calls the run() method of the thread.
2. run( ): The entry point for the thread. When the thread is
started, the run() method is invoked. If the thread was created
using a class that implements Runnable, the run() method will
execute the run() method of that Runnable object.
3. sleep(long millis): Causes the currently executing thread to
sleep (temporarily cease execution) for the specified number of
milliseconds.
4. join( ): Waits for this thread to die. When one thread calls
the join() method of another thread, it pauses the execution of
the current thread until the thread being joined has completed its
execution.
5. setPriority(int newPriority): Changes the priority of the
thread. The priority is a value between Thread.MIN_PRIORITY (1)
and Thread.MAX_PRIORITY (10).
Suresh Sencha
public class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
[Link]("Thread is Running...");
for (int i = 1; i <= 5; i++) {
for (int j = 0; j < 5; j++) {
[Link]([Link]().getName() + " -
Priority: " + [Link]().getPriority() + " - count: " + i);
try {
[Link](1000);
} catch (InterruptedException e) {
[Link]();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread l = new MyThread("Low Priority Thread");
MyThread m = new MyThread("Medium Priority Thread");
MyThread n = new MyThread("High Priority Thread");
[Link](Thread.MIN_PRIORITY);
[Link](Thread.NORM_PRIORITY);
[Link](Thread.MAX_PRIORITY);
[Link]();
[Link]();
[Link]();
}
}
6. interrupt(): Interrupts the thread. If the thread is blocked in a
call to wait(), sleep(), or join(), it will throw
an InterruptedException.
7. yield(): [Link]() is a static method that suggests the
current thread temporarily pause its execution to allow other
threads of the same or higher priority to execute. It’s important to
Suresh Sencha
note that yield() is just a hint to the thread scheduler, and the actual
behavior may vary depending on the JVM and OS.
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
[Link]([Link]().getName() + " is
running...");
[Link]();
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
[Link]();
[Link]();
}
}
8. [Link](boolean): Marks the thread as either a
daemon thread or a user thread. When the JVM exits, all daemon
threads are terminated.
public class MyThread extends Thread {
@Override
public void run() {
while (true) {
[Link]("Hello world! ");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
[Link](true); // myThread is daemon thread ( like
Garbage collector ) now
MyThread t1 = new MyThread();
[Link](); // t1 is user thread
[Link]();
[Link]("Main Done");
}
}
Suresh Sencha
Synchronisation
Let’s see an example where two threads are incrementing same
couter.
class Counter {
private int count = 0; // shared resource
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class MyThread extends Thread {
private Counter counter;
public MyThread(Counter counter) {
[Link] = counter;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
[Link]();
}
}
public static void main(String[] args) {
Counter counter = new Counter();
MyThread t1 = new MyThread(counter);
MyThread t2 = new MyThread(counter);
[Link]();
[Link]();
try {
[Link]();
[Link]();
}catch (Exception e){
}
[Link]([Link]()); // Expected: 2000, Actual
will be random <= 2000
}
}
Suresh Sencha
The output of the code is not 2000 because the increment method in
the Counter class is not synchronized. This results in a race
condition when both threads try to increment the count variable
concurrently.
Without synchronization, one thread might read the value
of count before the other thread has finished writing its incremented
value. This can lead to both threads reading the same value,
incrementing it, and writing it back, effectively losing one of the
increments.
We can fix this by using synchronized keyword
class Counter {
private int count = 0; // shared resource
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
By synchronizing the increment method, you ensure that only one
thread can execute this method at a time, which prevents the race
condition. With this change, the output will consistently be 2000.
Locks
The synchronized keyword in Java provides basic thread-safety but
has limitations: it locks the entire method or block, leading to
Suresh Sencha
potential performance issues. It lacks a try-lock mechanism, causing
threads to block indefinitely, increasing the risk of deadlocks.
Additionally, synchronized doesn't support multiple condition
variables, offering only a single monitor per object with basic
wait/notify mechanisms. In contrast, explicit locks (Lock interface)
offer finer-grained control, try-lock capabilities to avoid blocking,
and more sophisticated thread coordination through multiple
condition variables, making them more flexible and powerful for
complex concurrency scenarios.
import [Link];
import [Link];
import [Link];
public class BankAccount {
private int balance = 100;
private final Lock lock = new ReentrantLock();
public void withdraw(int amount) {
[Link]([Link]().getName() + " attempting
to withdraw " + amount);
try {
if ([Link](1000, [Link])) {
if (balance >= amount) {
try {
[Link]([Link]().getName() + " proceeding with
withdrawal");
[Link](3000); // Simulate time taken to
process the withdrawal
balance -= amount;
[Link]([Link]().getName() + " completed
withdrawal. Remaining balance: " + balance);
} catch (Exception e) {
[Link]().interrupt();
} finally {
[Link]();
}
} else {
[Link]([Link]().getName() +
" insufficient balance");
}
} else {
[Link]([Link]().getName() + "
could not acquire the lock, will try later");
Suresh Sencha
}
} catch (Exception e) {
[Link]().interrupt();
}
}
}
public class Main {
public static void main(String[] args) {
BankAccount sbi = new BankAccount();
Runnable task = new Runnable() {
@Override
public void run() {
[Link](50);
}
};
Thread t1 = new Thread(task, "Thread 1");
Thread t2 = new Thread(task, "Thread 2");
[Link]();
[Link]();
}
}
Reentrant Lock
A Reentrant Lock in Java is a type of lock that allows a thread to
acquire the same lock multiple times without causing a deadlock. If
a thread already holds the lock, it can re-enter the lock without being
blocked. This is useful when a thread needs to repeatedly enter
synchronized blocks or methods within the same execution flow.
The ReentrantLock class from
the [Link] package provides this functionality,
offering more flexibility than the synchronized keyword, including
try-locking, timed locking, and multiple condition variables for
advanced thread coordination.
public class ReentrantExample {
private final Lock lock = new ReentrantLock();
public void outerMethod() {
[Link]();
try {
Suresh Sencha
[Link]("Outer method");
innerMethod();
} finally {
[Link]();
}
}
public void innerMethod() {
[Link]();
try {
[Link]("Inner method");
} finally {
[Link]();
}
}
public static void main(String[] args) {
ReentrantExample example = new ReentrantExample();
[Link]();
}
}
Methods of ReentrantLock
lock()
• Acquires the lock, blocking the current thread until the lock is
available. It would block the thread until the lock becomes
available, potentially leading to situations where a thread waits
indefinitely.
• If the lock is already held by another thread, the current thread
will wait until it can acquire the lock.
tryLock()
• Tries to acquire the lock without waiting. Returns true if the lock
was acquired, false otherwise.
Suresh Sencha
• This is non-blocking, meaning the thread will not wait if the lock
is not available.
tryLock(long timeout, TimeUnit unit)
• Attempts to acquire the lock, but with a timeout. If the lock is not
available, the thread waits for the specified time before giving up.
It is used when you want to attempt to acquire the lock without
waiting indefinitely. It allows the thread to proceed with other
work if the lock isn't available within the specified time. This
approach is useful to avoid deadlock scenarios and when you
don't want a thread to block forever waiting for a lock.
• Returns true if the lock was acquired within the
timeout, false otherwise.
unlock()
• Releases the lock held by the current thread.
• Must be called in a finally block to ensure that the lock is always
released even if an exception occurs.
lockInterruptibly()
• Acquires the lock unless the current thread is interrupted. This is
useful when you want to handle interruptions while acquiring a
lock.
Suresh Sencha
Read Write Lock
A Read-Write Lock is a concurrency control mechanism that allows
multiple threads to read shared data simultaneously while
restricting write access to a single thread at a time. This lock type,
provided by the ReentrantReadWriteLock class in Java, optimizes
performance in scenarios with frequent read operations and
infrequent writes. Multiple readers can acquire the read lock without
blocking each other, but when a thread needs to write, it must
acquire the write lock, ensuring exclusive access. This prevents data
inconsistency while improving read efficiency compared to
traditional locks, which block all access during write operations.
import [Link];
import [Link];
import [Link];
public class ReadWriteCounter {
private int count = 0;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = [Link]();
private final Lock writeLock = [Link]();
public void increment() {
[Link]();
try {
count++;
[Link](50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
[Link]();
}
}
public int getCount() {
[Link]();
try {
return count;
} finally {
[Link]();
}
}
Suresh Sencha
public static void main(String[] args) throws InterruptedException {
ReadWriteCounter counter = new ReadWriteCounter();
Runnable readTask = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
[Link]([Link]().getName() +
" read: " + [Link]());
}
}
};
Runnable writeTask = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
[Link]();
[Link]([Link]().getName() +
" incremented");
}
}
};
Thread writerThread = new Thread(writeTask);
Thread readerThread1 = new Thread(readTask);
Thread readerThread2 = new Thread(readTask);
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
[Link]();
[Link]("Final count: " + [Link]());
}
}
Fairness of Locks
Fairness in the context of locks refers to the order in which threads
acquire a lock. A fair lock ensures that threads acquire the lock in
the order they requested it, preventing thread starvation. With a fair
lock, if multiple threads are waiting, the longest-waiting thread is
granted the lock next. However, fairness can lead to lower
throughput due to the overhead of maintaining the order. Non-fair
Suresh Sencha
locks, in contrast, allow threads to “cut in line,” potentially offering
better performance but at the risk of some threads waiting
indefinitely if others frequently acquire the lock.
import [Link];
import [Link];
import [Link];
public class FairnessLockExample {
private final Lock lock = new ReentrantLock(true);
public void accessResource() {
[Link]();
try {
[Link]([Link]().getName() + "
acquired the lock.");
[Link](1000);
} catch (InterruptedException e) {
[Link]().interrupt();
} finally {
[Link]([Link]().getName() + "
released the lock.");
[Link]();
}
}
public static void main(String[] args) {
FairnessLockExample example = new FairnessLockExample();
Runnable task = new Runnable() {
@Override
public void run() {
[Link]();
}
};
Thread thread1 = new Thread(task, "Thread 1");
Thread thread2 = new Thread(task, "Thread 2");
Thread thread3 = new Thread(task, "Thread 3");
[Link]();
[Link]();
[Link]();
}
}
Deadlock
Suresh Sencha
A deadlock occurs in concurrent programming when two or more
threads are blocked forever, each waiting for the other to release a
resource. This typically happens when threads hold locks on
resources and request additional locks held by other threads. For
example, Thread A holds Lock 1 and waits for Lock 2, while Thread
B holds Lock 2 and waits for Lock 1. Since neither thread can
proceed, they remain stuck in a deadlock state. Deadlocks can
severely impact system performance and are challenging to debug
and resolve in multi-threaded applications.
class Pen {
public synchronized void writeWithPenAndPaper(Paper paper) {
[Link]([Link]().getName() + " is using
pen " + this + " and trying to use paper " + paper);
[Link]();
}
public synchronized void finishWriting() {
[Link]([Link]().getName() + " finished
using pen " + this);
}
}
class Paper {
public synchronized void writeWithPaperAndPen(Pen pen) {
[Link]([Link]().getName() + " is using
paper " + this + " and trying to use pen " + pen);
[Link]();
}
public synchronized void finishWriting() {
[Link]([Link]().getName() + " finished
using paper " + this);
}
}
class Task1 implements Runnable {
private Pen pen;
private Paper paper;
public Task1(Pen pen, Paper paper) {
[Link] = pen;
[Link] = paper;
}
@Override
Suresh Sencha
public void run() {
[Link](paper); // thread1 locks pen and tries to
lock paper
}
}
class Task2 implements Runnable {
private Pen pen;
private Paper paper;
public Task2(Pen pen, Paper paper) {
[Link] = pen;
[Link] = paper;
}
@Override
public void run() {
synchronized (pen){
[Link](pen); // thread2 locks paper and
tries to lock pen
}
}
}
public class DeadlockExample {
public static void main(String[] args) {
Pen pen = new Pen();
Paper paper = new Paper();
Thread thread1 = new Thread(new Task1(pen, paper), "Thread-1");
Thread thread2 = new Thread(new Task2(pen, paper), "Thread-2");
[Link]();
[Link]();
}
}
Thread communication
class SharedResource {
private int data;
private boolean hasData;
public synchronized void produce(int value) {
while (hasData) {
try {
wait();
} catch (InterruptedException e) {
[Link]().interrupt();
}
Suresh Sencha
}
data = value;
hasData = true;
[Link]("Produced: " + value);
notify();
}
public synchronized int consume() {
while (!hasData){
try{
wait();
}catch (InterruptedException e){
[Link]().interrupt();
}
}
hasData = false;
[Link]("Consumed: " + data);
notify();
return data;
}
}
class Producer implements Runnable {
private SharedResource resource;
public Producer(SharedResource resource) {
[Link] = resource;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
[Link](i);
}
}
}
class Consumer implements Runnable {
private SharedResource resource;
public Consumer(SharedResource resource) {
[Link] = resource;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
int value = [Link]();
}
}
}
public class ThreadCommunication {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
Thread producerThread = new Thread(new Producer(resource));
Suresh Sencha
Thread consumerThread = new Thread(new Consumer(resource));
[Link]();
[Link]();
}
}
Executors framework
The Executors framework was introduced in Java 5 as part of the
[Link] package to simplify the development of
concurrent applications by abstracting away many of the
complexities involved in creating and managing threads.
It will help in
1. Avoiding Manual Thread management
2. Resource management
3. Scalability
4. Thread reuse
5. Error handling
import [Link];
import [Link];
import [Link];
public class ExecutorFrameWork {
public static void main(String[] args) {
long startTime = [Link]();
ExecutorService executor = [Link](3);
for (int i = 1; i < 10; i++) {
int finalI = i;
Suresh Sencha
[Link](() -> {
long result = factorial(finalI);
[Link](result);
});
}
[Link]();
// [Link]();
try {
[Link](1, [Link]);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
[Link]("Total time " + ([Link]() -
startTime));
}
private static long factorial(int n) {
try {
[Link](1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
}
Future
import [Link];
import [Link];
import [Link];
import [Link];
public class Main {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
ExecutorService executorService =
[Link]();
Future<?> future = [Link](() ->
[Link]("Hello")); // runnable parameter
[Link]([Link]()); // blocking call ( null )
if([Link]()){
[Link]("Task is done !");
}
Suresh Sencha
[Link]();
}
}
==========
Hello
null
Task is done !
===============
import [Link];
import [Link];
import [Link];
import [Link];
public class Main {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
ExecutorService executorService =
[Link]();
Future<String> future = [Link](() -> "Hello"); //
callable parameter
[Link]([Link]()); // blocking call
if([Link]()){
[Link]("Task is done !");
}
[Link]();
}
}
=================
Hello
Task is done !
Atomic classes
public class VolatileCounter {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
[Link]();
}
public int getCounter() {
return [Link]();
}
public static void main(String[] args) throws InterruptedException {
VolatileCounter vc = new VolatileCounter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
Suresh Sencha
[Link]();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
[Link]();
}
});
[Link]();
[Link]();
[Link]();
[Link]();
[Link]([Link]());
}
Volatile keyword
class SharedObj {
private volatile boolean flag = false;
public void setFlagTrue() {
[Link]("Writer thread made the flag true !");
flag = true;
}
public void printIfFlagTrue() {
while (!flag) {
// do nothing
}
[Link]("Flag is true !");
}
}
public class VolatileExample {
public static void main(String[] args) {
SharedObj sharedObj = new SharedObj();
Thread writerThread = new Thread(() -> {
try {
[Link](1000);
} catch (InterruptedException e) {
[Link]().interrupt();
}
[Link]();
});
Thread readerThread = new Thread(() ->
[Link]());
Suresh Sencha
[Link]();
[Link]();
}
}
CountDownLatch
import [Link];
import [Link];
import [Link];
import [Link];
public class Test {
public static void main(String[] args) throws InterruptedException {
int n = 3;
ExecutorService executorService = [Link](n);
CountDownLatch latch = new CountDownLatch(n);
[Link](new DependentService(latch));
[Link](new DependentService(latch));
[Link](new DependentService(latch));
[Link]();
[Link]("Main");
[Link]();
}
}
class DependentService implements Callable<String> {
private final CountDownLatch latch;
public DependentService(CountDownLatch latch) {
[Link] = latch;
}
@Override
public String call() throws Exception {
try {
[Link]([Link]().getName() + "
service started.");
[Link](2000);
} finally {
[Link]();
}
return "ok";
}
}
==================================
pool-1-thread-3 service started.
pool-1-thread-2 service started.
pool-1-thread-1 service started.
Main
Suresh Sencha
Cyclic Barrier
import [Link];
import [Link];
public class Main {
public static void main(String[] args) {
int numberOfSubsystems = 4;
CyclicBarrier barrier = new CyclicBarrier(numberOfSubsystems, new
Runnable() {
@Override
public void run() {
[Link]("All subsystems are up and running.
System startup complete.");
}
});
Thread webServerThread = new Thread(new Subsystem("Web Server",
2000, barrier));
Thread databaseThread = new Thread(new Subsystem("Database", 4000,
barrier));
Thread cacheThread = new Thread(new Subsystem("Cache", 3000,
barrier));
Thread messagingServiceThread = new Thread(new
Subsystem("Messaging Service", 3500, barrier));
[Link]();
[Link]();
[Link]();
[Link]();
class Subsystem implements Runnable {
private String name;
private int initializationTime;
private CyclicBarrier barrier;
public Subsystem(String name, int initializationTime, CyclicBarrier
barrier) {
[Link] = name;
[Link] = initializationTime;
[Link] = barrier;
}
@Override
public void run() {
try {
[Link](name + " initialization started.");
[Link](initializationTime); // Simulate time taken to
Suresh Sencha
initialize
[Link](name + " initialization complete.");
[Link]();
} catch (InterruptedException | BrokenBarrierException e) {
[Link]();
}
}
}