SLC S22 Week5 || Threads in Java: Life Cycle and Threading Basics

in dynamicdevs-s22w5 •  23 days ago 

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:



Join Here: SLC S22 Week5 || Threads in Java: Life Cycle and Threading Basics




java.png

Designed with Canva

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.

image.png

This program is showing the use of thread priorities in Java.

image.png

  • 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.

image.png

  • 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 for Thread-1
    • Thread.NORM_PRIORITY = 5 for Thread-2
    • Thread.MAX_PRIORITY = 10 for Thread-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.


image.png

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 the Task class.

image.png

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()).

thread1-ezgif.com-optimize.gif


Here are some behaviour analysis about the thread priorities:

  1. Influence of Priority:

    • Higher-priority threads like Thread-3 are more likely to execute before lower-priority threads like Thread-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.
  2. 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.

image.png
image.png

Here is the step by step explanation of the code:

image.png

Two Resource objects resource1 and resource2 are created. These represent shared resources that threads will attempt to lock.

image.png

This is the thread 1 and it locks resource1 first. And after locking the resource1 it tries to lock resource2.

image.png

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 acquire resource2.
  • Thread 2 locks resource2 and waits to acquire resource1.

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:

  1. Thread 1 Behaviour:

    • Acquires a lock on resource1 using synchronized (resource1).
    • Tries to acquire a lock on resource2. However resource2 is already locked by Thread 2 so Thread 1 waits.
  2. Thread 2 Behaviour:

    • Locks a on resource2 with synchronized (resource2).
    • Tries to lock resource1. But resource1 is held by Thread 1 so Thread 2 awaits.
  3. 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.

image.png

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:

  1. Ordering Lock Acquisition:

    • Always acquire locks in a fixed order. For example both the threads should first lock resource1 followed by resource2. In this the resources will used turn by turn and it will help to prevent deadlock.
  2. Using tryLock with Timeout:

    • ReentrantLock of java has a tryLock() method with a timeout to avoid indefinite waiting for a lock.
      image.png
  3. Avoid Nested Locks:

    • Minimize the use of nested locks to reduce the likelihood of deadlocks.
  4. Use Higher-Level Concurrency Utilities:

    • Use frameworks like ExecutorService and ForkJoinPool that abstract thread management and reduce the risk of deadlocks.


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.

image.png
image.png

Here is the explanation of the code:

image.png

  • The Counter class maintains a shared variable current, 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 the current variable.

image.png

  • 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.

image.png

  • Three threads are created, all running the CounterTask class.
  • The run() method of CounterTask continuously invokes increment() while there are still numbers to be counted, because hasMore() returns true.
  1. 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.

  1. 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 calls unlock().
  2. Critical Section

    • The code block within the increment() method is protected by the lock. It is the critical section where the current counter is accessed and modified.
  3. 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.

image.png

Here is the explanation of the code how different methods are working in this program to implement thread pool:

image.png

  • 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.

image.png

  • The submit() method of the ExecutorService 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.

image.png

  • 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.

image.png

  • 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:

  1. 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.
  2. Resource Management:

    • A thread pool will not have an excessive number of concurrent threads.Iit avoids too much context switching and resource contention.
  3. 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.
  4. 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.

pool-ezgif.com-optimize.gif

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.

image.png
image.png

Here is the explanation of the code how multiple threads reads the different parts of the file simultaneously:

image.png

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.

image.png

  • A thread pool ExecutorService ensures that threads run concurrently to read their respective segments.

image.png

  • The last thread reads until the file end accounting for any remainder when dividing the file size.

image.png

  • 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.

image.png

For the testing purposes I have added sample lorem ipsum text in the text file which can be seen here in the above picture.

reader-ezgif.com-optimize.gif

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:

  1. Improved Performance:

    • Parallel processing speeds up file reading for large files.
  2. Conflict-Free:

    • Segments are carefully assigned to avoid overlapping reads.
  3. Scalability:

    • Can read larger files by increasing the number of threads.
  4. 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.

image.png
image.png

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

image.png

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

image.png

This class implements Runnable. It is used for depositing a certain amount to the bank account.

  • The run() method invokes account.deposit(amount) to execute the operation.
WithdrawalTask Class

image.png

  • This is a class that inherits Runnable. It indicates a function to withdraw a particular amount from the bank account.
  • The run() method invokes account.withdraw(amount) to execute the operation.
BankTransactionSystem Class

image.png

This is the main class of the program and it initializes the program.

  1. It creates a BankAccount with an initial amount of 1000.
  2. There are four threads:
    • Two for deposits (DepositTask) and two for withdrawals (WithdrawalTask).
  3. All threads start with start() method.

Thread Safety and Data Integrity

Here are some techniques for the thread safety:

Application of synchronized Methods

image.png

  • 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, and getBalance are all declared as synchronized.
  • 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 if amount <= 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:

  1. Synchronization:

    • No two threads access critical sections at the same time (methods updating balance).
    • Only one thread can execute deposit, withdraw, or getBalance at a time.
  2. Atomicity:

    • An operation (a deposit or a withdraw) should always appear atomic.
      It means there must be no chance of doing partial updates on balance.
  3. Check of the Invariant:

    • A withdrawal checks for available balance and would not permit withdrawal to produce negative balance.
  4. Design to Be Thread-safe:

    • As the tasks (DepositTask and WithdrawalTask) implement Runnable, each thread can execute its particular operation individually while still maintaining the rules of synchronization.

bank-ezgif.com-optimize.gif

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.

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  
Loading...