Hello everyone! I hope you will be good. Today I am here to participate in the contest of @kouba01 about Threads in Java: Life Cycle and Threading Basics. It is really an interesting and knowledgeable contest. There is a lot to explore. If you want to join then:
![java.png](https://steemitimages.com/640x0/https://cdn.steemitimages.com/DQmYguTM3eXrpGHiikZfMMaTAzwF9C19PkDazYRCt65qUmz/java.png)
Task 1: Implement Thread Priorities
Write a program that demonstrates how thread priorities affect execution order. Create three threads and assign them the priorities MIN_PRIORITY, NORM_PRIORITY, and MAX_PRIORITY. Run the threads and observe the execution order. Provide an explanation of whether the thread priority influences their execution order and why.
As the professor has taught that a thread is a single path of execution in the program and by default each program starts executing from the main thread. If we have different priorities of thread in a program then the thread with higher priorities will be executed first and similarly the thread will less priority.
Here is the program that demonstrates how thread priorities affect execution order.
This program is showing the use of thread priorities in Java.
- At the top I have initialized three threads which are given below:
Thread-1
Thread-2
Thread-3
- Each thread is given a name and run the
Task
class implementation.
Then I have written the code which is setting the priority to the threads. It sets a priority to the threads as follows by sing each of the following explicit values to the threads:
Thread.MIN_PRIORITY
= 1 forThread-1
Thread.NORM_PRIORITY
= 5 forThread-2
Thread.MAX_PRIORITY
= 10 forThread-3
.
Thread Priority: It is the relative importance of a thread. The higher the priority, the sooner the thread is likely to be scheduled for execution. However this is not guaranteed as it depends on the JVM and OS thread scheduler.
After initializing and setting the priorities to the threads now we have to write the start function to execute the threads.
- The
start()
method starts the execution of the threads. - Each thread calls its
run()
method which was defined in theTask
class.
This is the Task
class and it provides data to the threads which they execute on the basis of their priorities. This class adds visual representation of the threads execution and we can analyze which type of data is executed first because according to us the threads with high priority should be executed first. Here are the features of this class:
- Implements the
Runnable
interface. - The
run()
method prints a message three times including:- The thread name (
Thread.currentThread().getName()
). - The thread priority (
Thread.currentThread().getPriority()
).
- The thread name (
Here are some behaviour analysis about the thread priorities:
Influence of Priority:
- Higher-priority threads like
Thread-3
are more likely to execute before lower-priority threads likeThread-1
. - However, the exact order of execution can vary depending on:
- JVM Implementation: Some JVMs strictly prioritize threads based on priority while others rely more on fairness.
- Operating System Scheduler: The OS scheduler may distribute CPU time among threads irrespective of their Java priorities.
- Higher-priority threads like
Thread Scheduling:
- In multi-threaded environments the scheduler tries to balance CPU usage among threads.
- Priority is a hint for the scheduler and not a guarantee for the execution order.
The program above demonstrates how thread priorities can influence execution order but are not to be used for deterministic control. In order to strictly maintain order the developer has to use mechanisms like join()
, locks, or thread pools.
Task 2: Simulate a Deadlock
Develop a program that intentionally causes a deadlock using synchronized methods. Design a scenario where two or more threads attempt to acquire locks on shared resources in reverse order, leading to a deadlock. Provide a detailed explanation of how the deadlock occurs and suggest strategies to prevent it.
Deadlock is the major problem which often happens in the systems normally when some tasks lock the resources and they do not free them for other tasks in the queue. It is about the deadlocks from my study but here we have to understand and explain the deadlocks in the java programming and I will show you how to prevent from these deadlocks.
Here is a Java program that intentionally causes a deadlock using synchronized methods. It creates two threads that acquire locks on shared resources (lock1
and lock2
) in reverse order resulting in a deadlock.
Here is the step by step explanation of the code:
Two Resource objects resource1
and resource2
are created. These represent shared resources that threads will attempt to lock.
This is the thread 1 and it locks resource1
first. And after locking the resource1
it tries to lock resource2
.
This is the thread 2 and it locks resource2
first. And after locking the resource2
it tries to lock resource1
.
If we notice that both threads are started using start()
. This creates a race condition between both the threads:
- Thread 1 locks
resource1
and waits to acquireresource2
. - Thread 2 locks
resource2
and waits to acquireresource1
.
Neither thread can proceed because they are waiting for the other to release the lock on the resource they need. This condition leads to a deadlock.
How Deadlock Occurs
Here is the detailed explanation how the deadlocks occur:
Thread 1 Behaviour:
- Acquires a lock on
resource1
usingsynchronized (resource1)
. - Tries to acquire a lock on
resource2
. Howeverresource2
is already locked by Thread 2 so Thread 1 waits.
- Acquires a lock on
Thread 2 Behaviour:
- Locks a on
resource2
withsynchronized (resource2)
. - Tries to lock
resource1
. Butresource1
is held by Thread 1 so Thread 2 awaits.
- Locks a on
Deadlock
- Both threads are blocked indefinitely because each is waiting for the other to release its lock. A deadlock has occurred because neither thread can continue.
Here Thread-0 is the the thread 1 and the Thread-1 serves as the thread 2. Neither of the threads can move forward as they are waiting for each other to release the necessary locks.
Avoiding Deadlock Strategies
Indeed after the research for each problem some strategies have been built which help to avoid those problems. Similarly in order to cope with the deadlock problems here are some strategies which prevent deadlock:
Ordering Lock Acquisition:
- Always acquire locks in a fixed order. For example both the threads should first lock
resource1
followed byresource2
. In this the resources will used turn by turn and it will help to prevent deadlock.
- Always acquire locks in a fixed order. For example both the threads should first lock
Using
tryLock
with Timeout:ReentrantLock
of java has atryLock()
method with a timeout to avoid indefinite waiting for a lock.
Avoid Nested Locks:
- Minimize the use of nested locks to reduce the likelihood of deadlocks.
Use Higher-Level Concurrency Utilities:
- Use frameworks like
ExecutorService
andForkJoinPool
that abstract thread management and reduce the risk of deadlocks.
- Use frameworks like
Task 3: Multithreaded Counter
Create a program where multiple threads count from 1 to 100 concurrently. Ensure that the numbers are printed in the correct order without conflicts, even though the counting occurs in parallel. Describe the mechanisms (e.g., synchronization) used to maintain the correct sequence.
Sometimes in our daily life and in the machines we have to perform some tasks concurrently and their working and execution should be smooth and intact from others. They should not conflict with each other so that the things can be done effectively.
Similarly in the computer machines we control this effectiveness of concurrent working through defining logic by using programming code. Here is a program where multiple threads count from 1 to 100
concurrently. This ensures that the numbers are printed in the correct order without conflicts, even though the counting occurs in parallel.
I will use synchronization mechanisms to ensure that the threads print numbers in the desired order without conflicts.
Here is the explanation of the code:
- The
Counter
class maintains a shared variablecurrent
, representing the current number to be printed. - The
MAX
value ensures counting stops at 100. - A
ReentrantLock
is used to ensure thread-safe access to thecurrent
variable.
- The
increment()
method is synchronized using a lock (lock.lock()
and `lock.unlock()'). - This way, no thread can modify and print the value of
current
at any time. - The
if
statement avoids the possibility that any of them tries to print numbers beyond 100.
- Three threads are created, all running the
CounterTask
class. - The
run()
method ofCounterTask
continuously invokesincrement()
while there are still numbers to be counted, becausehasMore()
returns true.
- Output Example
The program prints the numbers in a sequential order despite running multiple threads in parallel.
Mechanisms Used
In this program I have used the following mechanism where different threads execute but the order of the numbers from 1 to 100
remains same.
ReentrantLock
- A
ReentrantLock
is used to ensure only one thread accesses the critical section which is printing and incrementing the counter at a time. - The
lock()
method blocks other threads until the current thread finishes and callsunlock()
.
- A
Critical Section
- The code block within the
increment()
method is protected by the lock. It is the critical section where thecurrent
counter is accessed and modified.
- The code block within the
Thread Coordination
- The threads are concurrent but the use of the lock prevents them from interfering with each other when printing numbers so that the numbers are printed in sequence.
This approach has different benefits which are given below:
- Thread Safety: Ensures sequential output by allowing only one thread to access the critical section at a time.
- Scalability: Can easily scale to more threads without compromising correctness.
- Fairness: The lock ensures fairness by preventing thread starvation.
Task 4: Thread Pool Implementation
Write a program that uses a thread pool to process a list of tasks efficiently. Implement a set of simple tasks (e.g., displaying messages or performing basic calculations) and assign them to the threads in the pool. Explain how thread pools improve performance compared to creating threads for each task individually.
Thread pool implementation in programming is the process of using the previously created threads for the execution of the current tasks. It offers a solution to the problem of the thread problem.
Here is a Java program that uses a thread pool to process a list of tasks efficiently. It implements a set of simple tasks (e.g., displaying messages or performing basic calculations) and assign them to the threads in the pool.
Here is the explanation of the code how different methods are working in this program to implement thread pool:
- The program employs the
Executors.newFixedThreadPool(poolSize)
method to create a thread pool with a fixed number of threads,poolSize = 4
. - A thread pool is an implementation that allows a limited number of threads to execute tasks concurrently while other tasks wait in a queue.
- The
submit()
method of theExecutorService
submits tasks to the thread pool for execution. - Each task is an instance of the
Runnable
interface which one of the threads in the pool executes.
- Each task prints out the name of the thread and the number of the task it is executing.
- The
Thread.sleep(500)
introduces some processing time for each task.
- Once all tasks are submitted the
shutdown()
method is invoked to let the thread pool know that it should not accept any new tasks and shut down once all currently running tasks are completed.
How Thread Pools Enhance Performance
Here is the explanation of how the thread pools enhance the performance compared to the creation of the new threads for each new task:
Reduced Overhead:
- Starting a new thread for each task is very costly in terms of memory and CPU. A thread pool reuses threads and in this way it reduces this cost of using memory and CPU.
Resource Management:
- A thread pool will not have an excessive number of concurrent threads.Iit avoids too much context switching and resource contention.
Task Queueing:
- The tasks are queued up and executed by the threads in a pool as soon as it is released which ensures each thread is utilized well.
Scalability:
- A thread pool allows the workload to scale up the number of threads it uses which makes a thread pool an excellent solution for large quantities of tasks.
The is the output. The thread names and the task numbers vary with concurrent execution. Each thread in the pool handles tasks sequentially. It shows efficient use of the resources. Each task is taking some time to be executed and after the complete execution then the next one is being executed when it gets its turn sequentially.
Task 5: Parallel File Reader
Write a program where multiple threads read different parts of a file simultaneously. Divide the file into distinct segments, assign each segment to a thread, and print the content read by each thread. Explain the logic used for dividing and assigning file segments to avoid conflicts and ensure efficiency.
In real life sometimes we need to read the different parts of the file simultaneously with the help of the multiple threads and similarly we use this concept in java programming where different threads read the different parts of the file.
Here is a program where multiple threads read different parts of a file simultaneously. It divides the file into distinct segments, assign each segment to a thread and print the content read by each thread.
Here is the explanation of the code how multiple threads reads the different parts of the file simultaneously:
Here in this part of the code I have divided the file into segments.Here is how it is done:
- The file total size is obtained using
file.length()
. - The file is divided into equal parts by using
segmentSize = fileLength / threadCount
. - Each thread is assigned a segment defined by a start and end byte range.
- Each thread reads a non-overlapping segment of the file.
- The
RandomAccessFile.seek(start)
method ensures the thread reads from its assigned starting position. - The
RandomAccessFile
class is used because it allows reading from arbitrary file positions without locking the whole file.
- A thread pool
ExecutorService
ensures that threads run concurrently to read their respective segments.
- The last thread reads until the file end accounting for any remainder when dividing the file size.
- This
FileSegmentReader1
class is used to read the different segments of the file. - This class uses a constructor which accepts the path of the file and the strating and ending point of the text.
- There is a
try-catch
method usage which handles the successful file reading. If there is any error then it also handles that exception.
For the testing purposes I have added sample lorem ipsum text in the text file which can be seen here in the above picture.
Here in the output you can see that the different threads from a pool are reading the different segments of the file. This implementation ensures efficient, conflict-free parallel file reading while maximizing thread utilization.
Here are some advantages for the parallel processing:
Improved Performance:
- Parallel processing speeds up file reading for large files.
Conflict-Free:
- Segments are carefully assigned to avoid overlapping reads.
Scalability:
- Can read larger files by increasing the number of threads.
Reusability:
- The logic can be reused for different file reading tasks that require parallelism.
Task 6: Thread-safe Bank Transaction System
Develop a program simulating a bank system where multiple threads perform deposits and withdrawals on shared bank accounts. Use synchronization to ensure thread safety and prevent issues such as race conditions. Provide an explanation of the techniques used to maintain data integrity in the presence of concurrent threads.
Here is a Java program simulating a bank system where multiple threads perform deposits and withdrawals on shared bank accounts. It uses synchronization to ensure thread safety and prevent issues such as race conditions.
Here is the explanation of the code:
This is a simulation of a bank system where many threads make concurrent deposits and withdrawals. It has used synchronized methods for thread safety and data integrity.
Class BankAccount
It represents a shared bank account that can perform a deposit, withdraw money or return the current balance. It is defined as follows:
Key Attributes:
deposit(double amount)
: Thread safe addition of the given amount to the account balance.withdraw(double amount)
: Removes the given amount from the account if the balance is sufficient. All is done in a thread safe way.getBalance()
: Synchronized method for getting the current balance to maintain the consistency.
DepositTask
Class
This class implements Runnable
. It is used for depositing a certain amount to the bank account.
- The
run()
method invokesaccount.deposit(amount)
to execute the operation.
WithdrawalTask
Class
- This is a class that inherits
Runnable
. It indicates a function to withdraw a particular amount from the bank account. - The
run()
method invokesaccount.withdraw(amount)
to execute the operation.
BankTransactionSystem
Class
This is the main class of the program and it initializes the program.
- It creates a
BankAccount
with an initial amount of1000
. - There are four threads:
- Two for deposits (
DepositTask
) and two for withdrawals (WithdrawalTask
).
- Two for deposits (
- All threads start with
start()
method.
Thread Safety and Data Integrity
Here are some techniques for the thread safety:
Application of synchronized
Methods
- It prevents race conditions since only one thread in a multi-threading environment is permitted to execute a synchronized method on the same object at one time.
Methods:deposit
,withdraw
, andgetBalance
are all declared assynchronized
. - Ensures that the
balance
variable is accessed and modified in a controlled manner. - Prevents threads from overwriting each other's updates to the balance.
Avoiding Overdrawn Accounts
- The
withdraw
method contains a condition to check ifamount <= balance
before deducting. - If the condition fails, it prevents the withdrawal and prints a message about insufficient funds.
Shared Resource
- The
BankAccount
object is a shared resource to which all threads access. - Synchronization allows concurrent threads to access the shared resource without compromising its state.
Data Integrity Techniques
Here are the data integrity techniques in the presence of concurrent threads:
Synchronization:
- No two threads access critical sections at the same time (methods updating
balance
). - Only one thread can execute
deposit
,withdraw
, orgetBalance
at a time.
- No two threads access critical sections at the same time (methods updating
Atomicity:
- An operation (a
deposit
or awithdraw
) should always appear atomic.
It means there must be no chance of doing partial updates onbalance
.
- An operation (a
Check of the Invariant:
- A withdrawal checks for available balance and would not permit withdrawal to produce negative balance.
Design to Be Thread-safe:
- As the tasks (
DepositTask
andWithdrawalTask
) implementRunnable
, each thread can execute its particular operation individually while still maintaining the rules of synchronization.
- As the tasks (
Here in the output of the program we can see that the multiple threads are doing deposits and withdrawals on the shared bank accounts while ensuring the security and safety as well as integrity of the system.
The synchronized methods ensure that operations are performed in a predictable and safe manner, even in the presence of multiple threads. This design ensures the program maintains data integrity and thread safety while supporting concurrent operations on the bank account.