Advanced Java Programing Individual Assignment
Advanced Java Programing Individual Assignment
UNIVERSITY
COLLEGE OF COMPUTING
DEPARTMENT OF COMPUTER
SCIENCE
COURSETITLE:ADVANCED JAVA PROGRAMMING
COURSECODE:COSC3053
INDIVIDUAL ASSIGNMENT
NAME: MELKAMU AWTACHEW
ID:3869/13
What is Thread?
Threads: is a lightweight unit of execution within a program. It is a fundamental part of Java's
multithreading support, allowing concurrent execution of multiple tasks or processes within a
single program. It’s refers to a sequence of instructions that can be executed independently
within a program. It also refers to a separate flow of execution within a program that can run
independently of other threads. Threads allow different parts of a program to execute
concurrently, leading to improved performance, responsiveness, and utilization of system
resources.
Thread can be thought of as an independent path of execution that operates within a larger
program. It enables the program to perform multiple tasks simultaneously, leading to increased
efficiency and responsiveness. They provide a way to achieve multitasking and increase the
efficiency of program execution by dividing tasks into smaller units that can be executed
concurrently. Multiple threads can exist within a single process, allowing for concurrent
execution of tasks.
Java threads are managed by the Java Virtual Machine (JVM) and provide features such as
synchronization, which allows threads to communicate and coordinate with each other.
Threads can be created by extending the Thread class or implementing the Runnable interface
in Java. Each Java program has at least one thread, known as the main thread, which is
automatically created when the program starts. Additional threads can be created by the
programmer to perform specific tasks concurrently. These threads can run concurrently,
meaning they can execute simultaneously, or they can be scheduled to run at different times
based on the program's requirements.
In summary, threads in Java are used to achieve concurrent execution of tasks, allowing
programs to utilize system resources efficiently and provide better responsiveness. By dividing a
program's workload into multiple threads, developers can take advantage of parallelism and
create more efficient and responsive applications.
Multithreading, Multitasking, Multiprocessing
A. Multithreading: is a process of executing two or more threads simultaneously for
maximum utilization of the CPU. It’s also the capability of a program to execute multiple
threads concurrently. It is a technique that allows different parts of a program to run
simultaneously, enabling concurrent execution of tasks and taking advantage of
available system resources. It’s saves time as you can perform multiple operations
together and the threads are independent, so it does not block the user to perform
multiple operations at the same time and also, if an exception occurs in a single thread,
it does not affect other threads. Multithreading enables concurrent execution of tasks
by dividing them into separate threads. It provides the means to create, manage, and
coordinate threads, allowing for parallel execution, improved performance, and
responsiveness in Java applications.
Some examples of Multithreading are Web browser, Word processor Video processor.
Process vs Thread
Process: is an active program that is under execution. It is more than the program code, as it
includes the program counter, process stack, registers, program code etc. Compared to this, the
program code is only the text section. It represents the execution of a Java application, which
can consist of multiple threads. Each Java process has its own memory space, system resources,
and a separate instance of the Java Virtual Machine (JVM) running the application. When you
run a Java program, it starts a new process that operates independently of other processes.
Each process runs independently and is isolated from other processes, meaning that it has its
own memory space and cannot directly access or modify the memory of other processes.
Processes are managed by the operating system, which schedules their execution, assigns
system resources, and facilitates inter-process communication. Processes communicate with
each other through inter-process communication (IPC) mechanisms, such as pipes, shared
memory, or message passing.
Thread: is a unit of execution within a Java process. It is a lightweight sequence of instructions
that can run concurrently with other threads within the same process. Java threads are
instances of the java.lang. Thread class or can be created by implementing the java.lang.
Runnable interface. Multiple threads within a Java process share the same memory space,
allowing for efficient communication and data sharing between them. Java's multithreading
capabilities allow you to create and manage multiple threads within a single Java process. This
enables concurrent execution of tasks, improves performance, and enhances responsiveness in
applications. By leveraging threads, you can achieve parallelism and efficiently utilize available
system resources.
A thread shares information like data segment, code segment, files etc. with its peer threads
while it contains its own registers, stack, counter etc. It’s basically a subpart of a large process.
In a process, all the threads within it are interrelated to each other. A typical thread contains
some information like data segment, code segment, etc. This information is being shared to
their peer threads during execution. The most important feature of threads is that they share
memory, data, resources, etc. with their peer threads within a process to which they belong.
Both processes and threads are related to each other and very much similar, hence
create confusion to understand the differences between both of them. The process and
thread are an independent sequence of execution, but both are differentiated in a way
that processes execute in different memory spaces, whereas threads of the same
process execute in shared memory space.
o The main difference between process and thread are that a process represents the
execution of a Java program and is isolated from other processes, while a thread is a
unit of execution within a Java process and allows for concurrent execution of tasks
within the same memory space. Processes have their own memory and resources,
whereas threads share resources within a process. Threads are lightweight and used for
achieving concurrency within a Java program, while processes are independent
instances of Java programs.
Java's multithreading capabilities enable you to create, manage, and coordinate multiple
threads within a single program. Threads within a Java program share the same memory space,
enabling efficient communication and data sharing. However, it's crucial to handle thread
synchronization and potential race conditions when multiple threads access shared data to
ensure data integrity and avoid conflicts.
By utilizing multithreading in Java, you can effectively utilize system resources, improve
responsiveness, and achieve parallelism within your program, leading to enhanced
performance and better user experience. It is commonly used in scenarios such as graphical
user interfaces (GUIs), server applications, and computationally intensive tasks. For example, in
a GUI application, you can use separate threads to handle user input, update the interface, and
perform background computations simultaneously, ensuring smooth user interaction. It
enables you to enhance the performance and responsiveness of your program by leveraging
the capabilities of modern processors with multiple cores or support for simultaneous
multithreading (SMT). By dividing the workload into multiple threads, you can achieve parallel
execution of tasks, making efficient use of available system resources.
Multithreading allows many parts of a program to run simultaneously. These parts are referred
to as threads, and they are lightweight processes that are available within the process. As a
result, multithreading increases CPU utilization through multitasking. By utilizing multi-
threading, Java programs can achieve parallelism and make efficient use of available system
resources, such as CPU cores. Java provides built-in support for multi-threading through the
java.lang. Thread class and the java.lang.Runnable interface.
Multithreading enables us to run multiple threads concurrently. For instance, in a web browser,
we can have one thread which handles the user interface, and in parallel we can have another
thread which fetches the data to be displayed. Therefore, multithreading improves the
responsiveness of a system.
B. Thread Synchronization: is the process of coordinating the execution of multiple
threads to ensure orderly and predictable behavior. It’s a way of programming several
threads to carry out independent tasks easily. It is capable of controlling access to
multiple threads to a particular shared resource. The main reason for using thread
synchronization is to prevent interference between threads. In multi-threaded
programs, threads may need to access shared resources or critical sections of code
concurrently. Synchronization mechanisms, such as locks, semaphores, or monitors, are
used to control access to these shared resources and prevent data corruption or race
conditions. Java provides keywords like synchronized and classes like Lock and
Semaphore for thread synchronization.
Java provides synchronization mechanisms that allow threads to safely access shared resources.
The most commonly used synchronization mechanism in Java is the synchronized keyword,
which can be applied to methods or code blocks. When a thread encounters a synchronized
block or method, it acquires a lock on the associated monitor, ensuring that only one thread
can execute the synchronized code at a time. Other threads attempting to access the same
synchronized block or method will be blocked until the lock is released. By properly
synchronizing access to shared resources, thread synchronization prevents data corruption and
ensures data integrity. It establishes a happens-before relationship, ensuring that changes
made by one thread are visible to other threads.
It is essential when multiple threads access shared resources concurrently. It helps maintain
data consistency, prevents race conditions, and ensures the correct execution of multi-
threaded programs. However, it's important to use synchronization judiciously to avoid
potential performance bottlenecks or deadlocks. Understanding and applying proper
synchronization techniques are critical for developing robust and thread-safe Java applications.
Additionally, thread synchronization can be used for signaling and communication between
threads, allowing them to coordinate their activities and avoid unnecessary waiting or busy-
waiting.
C. Priorities: represent the relative importance of threads for CPU scheduling. Each thread
in Java is assigned a priority value that ranges from 1 to 10. The larger the integer, the
higher the priority. But it is not guaranteed because it depends on JVM specification that
which scheduling it chooses. Note that not only JVM a Java programmer can also assign
the priorities of a thread explicitly in a Java program. The thread scheduler uses this
integer from each thread to determine which one should be allowed to execute. The
default priority of a thread is usually inherited from its parent thread.
Thread priorities are used to provide guidance to the Java thread scheduler, which sets the
order in which threads receive CPU time. They are typically used to indicate the relative
importance or urgency of different threads within an application. Threads with higher priorities
are more likely to be scheduled and executed by the scheduler than threads with lower
priorities. It is crucial to remember, however, that thread priorities are not strictly guaranteed
and may vary among platforms and JVM implementations.
The setPriority() function of the Thread class in Java can be used to set the priority of a thread.
The getPriority() method can also be used to retrieve a thread's priority.It is important to use
thread priorities wisely and understand their limitations. While higher priority threads are more
likely to be scheduled, strict ordering and fairness are not guaranteed. It is generally not
advisable to rely only on thread priority for crucial synchronization or timing requirements.
Types of synchronization
Synchronization: is the ability to regulate multiple processes’ access to a shared resource. It’s
a mechanism used to coordinate and control access to shared resources or critical sections of
code in a multi-threaded environment. Multiple threads attempt to access shared resources at
the same time under the Multithreading concept, resulting in inconsistent outcomes.
Synchronization is required for thread-to-thread communication to be reliable. Synchronization
aids in thread interference prevention. It helps on the prevention of concurrency issues.
Synchronization is crucial for maintaining thread safety and preventing race conditions and data
corruption in multi-threaded applications. By properly synchronizing access to shared
resources, developers can ensure correct and predictable behavior of their concurrent
programs. It ensures that only one thread can access a shared resource or execute a critical
section at a time, preventing conflicts and maintaining data consistency.
It operates in a separate process that is not connected to another. The operating system
allocates resources to the process, such as memory and CPU time. The phrase “process
synchronization” refers to the sharing of capabilities between two or more processes while
guaranteeing data consistency. The Critical Section is a piece of code that is shared by several
processes in a programming language. There are various methods to prevent critical section
problems, such as Peterson’s Solution, but the most famous is Semaphores.
1. Mutual Exclusive: also known as a Mutual Exclusive, allows only one thread to access
shared resources. In other word it is the property where only one thread can access a
shared resource or a critical section of code at a time. This prevents conflicts and data
inconsistencies. In Java, mutual exclusion can be achieved using the synchronized
keyword or Lock objects. It will not enable simultaneous access to shared resources.
Here is one example for mutual exclusive thread synchronization:
In this example, we have a “PrintJob” class with a “print” method that is declared as
synchronized. This ensures that only one thread can execute the “print” method at a time.
We create two “PrinterThread” objects, passing the same “PrintJob” instance and different
document names to each thread. When the threads start, they invoke the “print” method on
the shared “PrintJob” object. Due to the synchronized keyword, only one thread can access the
“print” method at a time, ensuring mutual exclusion. As a result, the documents are printed
sequentially, preventing conflicts and maintaining data consistency. The threads are separate
instances, but they synchronize their access to the shared “PrintJob” object, achieving mutual
exclusion.
Implementation:
class PrintJob {
this.printJob = printJob;
this.document = document;
@Override
printJob.print(document);
thread1.start();
thread2.start();
}
Types of Mutual exclusion threads:
A. Synchronized Method: It provides the synchronized keyword, which can be applied
to methods to achieve mutual exclusion. When a thread enters a synchronized method,
it acquires the lock associated with the object instance or class. Other threads
attempting to execute synchronized methods on the same object or class must wait
until the lock is released. When a method is declared as synchronized, only one thread
can execute that method at a time, ensuring mutual exclusion.
Example:
public class SynchronizedMethodExample {
count++;
B. Synchronized block: Java allows the use of synchronized blocks to create smaller
critical sections within methods. With synchronized blocks, a thread acquires the lock
associated with the specified object or class before entering the block. A synchronized
block allows you to create a smaller critical section within a method. Only one thread
can enter the synchronized block at a time, ensuring mutual exclusion within that
block.
Example:
public class SynchronizedBlock() {
private int count = 0;
private Object lock = new Object();
synchronized (lock) {
// Critical section
count++;
}
// Non-critical section
}
}
Example:
public class StaticSynchronizationExample {
count++;
In this example, the “setMessage” method sets the message content and notifies other threads
waiting on the object. The “getMessage” method retrieves the message and notifies waiting
threads. The “wait()” method suspends the calling thread until it is notified by another thread
using “notify()” or “notifyAll()”. It demonstrate how mutual exclusion and inter-thread
communication can be implemented in Java. By ensuring mutual exclusion, we prevent
concurrent access to shared resources, while inter-thread communication allows threads to
synchronize their actions and share information effectively.
Implementation:
class Message {
while (isMessageReady) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
content = message;
isMessageReady = true;
notifyAll();
while (!isMessageReady) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
isMessageReady = false;
notifyAll();
return content;
}
The Object class in java contains three final methods that allows threads to
communicate about the lock status of a resource. These methods are wait(), notify()
and notifyAll().
package com.journaldev.concurrency
this.msg=m;
@Override
synchronized (msg) {
try{
msg.wait();
}catch(InterruptedException e){
e.printStackTrace();
notify(): It is called inside a synchronized context to notify a waiting thread that it can
proceed. notify method wakes up only one thread waiting on the object and that thread
starts execution. So if there are multiple threads waiting for an object, this method will
wake up only one of them. The choice of the thread to wake depends on the OS
implementation of thread management. After notify() is called, the awakened thread
does not immediately regain the lock. It must wait for the notifying thread to release the
lock before it can proceed.
Example:
package com.journaldev.concurrency;
public class Notifier implements Runnable {
private Message msg;
public Notifier(Message msg) {
this.msg = msg;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name+" started");
try {
Thread.sleep(1000);
synchronized (msg) {
msg.setMsg(name+" Notifier work done");
msg.notify();
// msg.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
notifyAll(): It is called inside a synchronized context to notify all waiting threads that
they can proceed. notifyAll method wakes up all the threads waiting on the object,
although which one will process first depends on the OS implementation. These
methods can be used to implement producer consumer problem where consumer
threads are waiting for the objects in Queue and producer threads put object in queue
and notify the waiting threads. Let’s see an example where multiple threads work on
the same object and we use wait, notify and notifyAll methods. After notifyAll() is called,
the awakened threads do not immediately regain the lock. They must wait for the
notifying thread to release the lock before they can proceed.
Example:
package com.journaldev.concurrency;
public class WaitNotifyTest {
public static void main(String[] args) {
Message msg = new Message("process it");
Waiter waiter = new Waiter(msg);
new Thread(waiter,"waiter").start();
Waiter waiter1 = new Waiter(msg);
new Thread(waiter1, "waiter1").start();
Notifier notifier = new Notifier(msg);
new Thread(notifier, "notifier").start();
System.out.println("All the threads are started");
}
}
In this example, the Message class represents a shared message object between the Producer
and Consumer threads. The produce() method is used by the producer to set the content of the
message, and the consume() method is used by the consumer to retrieve the content of the
message. The methods are synchronized and use the wait() and notify() calls to control the
execution flow. The producer waits when the message is not empty, and the consumer waits
when the message is empty. The notify() calls notify the waiting threads to resume execution.