Java Thread Programming: Concepts and Examples - Prof. Belay, Lecture notes of Information Technology

A comprehensive overview of java thread programming, covering key concepts such as thread creation, thread states, thread scheduling, daemon threads, and thread synchronization. It includes code examples to illustrate the implementation of these concepts and provides insights into the behavior of threads in java.

Typology: Lecture notes

2024/2025

Uploaded on 11/25/2024

tolerate-man
tolerate-man 🇪🇹

5 documents

1 / 45

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Chapter Six
Multithreading in Java
Multithreading in Java is a process of executing multiple threads
simultaneously.
A thread is a lightweight sub-process, the smallest unit of processing.
Multiprocessing and multithreading, both are used to achieve multitasking.
However, we use multithreading than multiprocessing because threads use a
shared memory area. They don't allocate separate memory area so saves
memory, and context-switching between the threads takes less time than
process.
Java Multithreading is mostly used in games, animation, etc.
Advantages of Java Multithreading
1) It doesn't block the user because threads are independent and you can
perform multiple operations at the same time.
2) You can perform many operations together, so it saves time.
3) Threads are independent, so it doesn't affect other threads if an exception
occurs in a single thread.
Multitasking
Multitasking is a process of executing multiple tasks simultaneously. We use
multitasking to utilize the CPU. Multitasking can be achieved in two ways:
Process-based Multitasking (Multiprocessing)
Thread-based Multitasking (Multithreading)
1) Process-based Multitasking (Multiprocessing)
Each process has an address in memory. In other words, each process
allocates a separate memory area.
A process is heavyweight.
Cost of communication between the process is high.
Switching from one process to another requires some time for saving and
loading registers, memory maps, updating lists, etc.
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28
pf29
pf2a
pf2b
pf2c
pf2d

Partial preview of the text

Download Java Thread Programming: Concepts and Examples - Prof. Belay and more Lecture notes Information Technology in PDF only on Docsity!

Chapter Six Multithreading in Java Multithreading in Java is a process of executing multiple threads simultaneously. A thread is a lightweight sub-process, the smallest unit of processing. Multiprocessing and multithreading, both are used to achieve multitasking. However, we use multithreading than multiprocessing because threads use a shared memory area. They don't allocate separate memory area so saves memory, and context-switching between the threads takes less time than process. Java Multithreading is mostly used in games, animation, etc. Advantages of Java Multithreading

  1. It doesn't block the user because threads are independent and you can perform multiple operations at the same time.
  2. You can perform many operations together, so it saves time.
  3. Threads are independent, so it doesn't affect other threads if an exception occurs in a single thread. Multitasking Multitasking is a process of executing multiple tasks simultaneously. We use multitasking to utilize the CPU. Multitasking can be achieved in two ways:  Process-based Multitasking (Multiprocessing)  Thread-based Multitasking (Multithreading)
  4. Process-based Multitasking (Multiprocessing)  Each process has an address in memory. In other words, each process allocates a separate memory area.  A process is heavyweight.  Cost of communication between the process is high.  Switching from one process to another requires some time for saving and loading registers, memory maps, updating lists, etc.
  1. Thread-based Multitasking (Multithreading)  Threads share the same address space.  A thread is lightweight.  Cost of communication between the thread is low. Note: At least one process is required for each thread. What is Thread in java A thread is a lightweight subprocess, the smallest unit of processing. It is a separate path of execution. Threads are independent. If there occurs exception in one thread, it doesn't affect other threads. It uses a shared memory area.

subgroups.

  1. boolean isDaemon() It tests if the thread is a daemon thread.
  2. void setDaemon() It marks the thread as daemon or user thread.
  3. void interrupt() It interrupts the thread.
  4. boolean isinterrupte d() It tests whether the thread has been interrupted.
  5. static boolean interrupted() It tests whether the current thread has been interrupted.
  6. static int activeCount( ) It returns the number of active threads in the current thread's thread group.
  7. void checkAccess () It determines if the currently running thread has permission to modify the thread.
  8. static boolean holdLock() It returns true if and only if the current thread holds the monitor lock on the specified object.
  9. static void dumpStack() It is used to print a stack trace of the current thread to the standard error stream.
  10. StackTrac eElement[] getStackTra ce() It returns an array of stack trace elements representing the stack dump of the thread.
  11. static int enumerate() It is used to copy every active thread's thread group and its subgroup into the specified array.
  12. Thread.St ate getState() It is used to return the state of the thread.
  13. ThreadGro up getThreadGr oup() It is used to return the thread group to which this thread belongs
  14. String toString() It is used to return a string representation of this thread, including the thread's name, priority, and thread group.
  15. void notify() It is used to give the notification for only one thread which is waiting for a particular object.
  16. void notifyAll() It is used to give the notification to all waiting

threads of a particular object.

  1. void setContextC lassLoader() It sets the context ClassLoader for the Thread.
  2. ClassLoad er getContextC lassLoader() It returns the context ClassLoader for the thread.
  3. static Thread.Un caughtExc eptionHan dler getDefaultU ncaughtExc eptionHandl er() It returns the default handler invoked when a thread abruptly terminates due to an uncaught exception.
  4. static void setDefaultU ncaughtExc eptionHandl er() It sets the default handler invoked when a thread abruptly terminates due to an uncaught exception. Life cycle of a Thread (Thread States) In Java, a thread always exists in any one of the following states. These states are:
  1. New
  2. Active
  3. Blocked / Waiting
  4. Timed Waiting
  5. Terminated Explanation of Different Thread States New: Whenever a new thread is created, it is always in the new state. For a thread in the new state, the code has not been run yet and thus has not begun its execution. Active: When a thread invokes the start() method, it moves from the new state to the active state. The active state contains two states within it: one is runnable, and the other is running.

If there are a lot of threads in the waiting or blocked state, then it is the duty of the thread scheduler to determine which thread to choose and which one to reject, and the chosen thread is then given the opportunity to run. Timed Waiting: Sometimes, waiting for leads to starvation. For example, a thread (its name is A) has entered the critical section of a code and is not willing to leave that critical section. In such a scenario, another thread (its name is B) has to wait forever, which leads to starvation. To avoid such scenario, a timed waiting state is given to thread B. Thus, thread lies in the waiting state for a specific span of time, and not forever. A real example of timed waiting is when we invoke the sleep() method on a specific thread. The sleep() method puts the thread in the timed wait state. After the time runs out, the thread wakes up and start its execution from when it has left earlier. Terminated: A thread reaches the termination state because of the following reasons:  When a thread has finished its job, then it exists or terminates normally.  Abnormal termination: It occurs when some unusual events such as an unhandled exception or segmentation fault. A terminated thread means the thread is no more in the system. In other words, the thread is dead, and there is no way one can respawn (active after kill) the dead thread. The following diagram shows the different states involved in the life cycle of a thread.

Implementation of Thread States In Java, one can get the current state of a thread using the Thread.getState() method. The java.lang.Thread.State class of Java provides the constants ENUM to represent the state of a thread. These constants are:

  1. public static final Thread.State NEW It represents the first state of a thread that is the NEW state.
  2. public static final Thread.State RUNNABLE It represents the runnable state.It means a thread is waiting in the queue to run.
  3. public static final Thread.State BLOCKED It represents the blocked state. In this state, the thread is waiting to acquire a lock.
  4. public static final Thread.State WAITING It represents the waiting state. A thread will go to this state when it invokes the Object.wait() method, or Thread.join() method with no timeout. A thread in the waiting state is waiting for another thread to complete its task.
  5. public static final Thread.State TIMED_WAITING It represents the timed waiting state. The main difference between waiting and timed waiting is the time constraint. Waiting has no time constraint, whereas
  1. System.out.println("The state of thread t1 while it invoked the metho d join() on thread t2 -"+ ThreadState.t1.getState());
  2. // try-catch block
  3. try
  4. {
  5. Thread.sleep( 200 );
  6. }
  7. catch (InterruptedException ie)
  8. {
  9. ie.printStackTrace();
  10. }
  11. }
  12. }
  13. // ThreadState class implements the interface Runnable
  14. public class ThreadState implements Runnable
  15. {
  16. public static Thread t1;
  17. public static ThreadState obj;
  18. // main method
  19. public static void main(String argvs[])
  20. {
  21. // creating an object of the class ThreadState
  22. obj = new ThreadState();
  23. t1 = new Thread(obj);
  24. // thread t1 is spawned
  25. // The thread t1 is currently in the NEW state.
  26. System.out.println("The state of thread t1 after spawning it - " + t1.g etState());
  1. // invoking the start() method on
  2. // the thread t
  3. t1.start();
  4. // thread t1 is moved to the Runnable state
  5. System.out.println("The state of thread t1 after invoking the method start() on it - " + t1.getState());
  6. }
  7. public void run()
  8. {
  9. ABC myObj = new ABC();
  10. Thread t2 = new Thread(myObj);
  11. // thread t2 is created and is currently in the NEW state.
  12. System.out.println("The state of thread t2 after spawning it - "+ t2.ge tState());
  13. t2.start();
  14. // thread t2 is moved to the runnable state
  15. System.out.println("the state of thread t2 after calling the method st art() on it - " + t2.getState());
  16. // try-catch block for the smooth flow of the program
  17. try
  18. {
  19. // moving the thread t1 to the state timed waiting
  20. Thread.sleep( 200 );
  21. }
  22. catch (InterruptedException ie)

The state of thread t2 when it has completed it's execution - TERMINATED Explanation: Whenever we spawn a new thread, that thread attains the new state. When the method start() is invoked on a thread, the thread scheduler moves that thread to the runnable state. Whenever the join() method is invoked on any thread instance, the current thread executing that statement has to wait for this thread to finish its execution, i.e., move that thread to the terminated state. Therefore, before the final print statement is printed on the console, the program invokes the method join() on thread t2, making the thread t1 wait while the thread t2 finishes its execution and thus, the thread t2 get to the terminated or dead state. Thread t1 goes to the waiting state because it is waiting for thread t2 to finish it's execution as it has invoked the method join() on thread t2. Java Threads | How to create a thread in Java There are two ways to create a thread:

  1. By extending Thread class
  2. By implementing Runnable interface. Thread class: Thread class provide constructors and methods to create and perform operations on a thread.Thread class extends Object class and implements Runnable interface. Commonly used Constructors of Thread class:  Thread()  Thread(String name)  Thread(Runnable r)  Thread(Runnable r,String name) Commonly used methods of Thread class:
  3. public void run(): is used to perform action for a thread.
  4. public void start(): starts the execution of the thread.JVM calls the run() method on the thread.
  1. public void sleep(long miliseconds): Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds.
  2. public void join(): waits for a thread to die.
  3. public void join(long miliseconds): waits for a thread to die for the specified miliseconds.
  4. public int getPriority(): returns the priority of the thread.
  5. public int setPriority(int priority): changes the priority of the thread.
  6. public String getName(): returns the name of the thread.
  7. public void setName(String name): changes the name of the thread.
  8. public Thread currentThread(): returns the reference of currently executing thread.
  9. public int getId(): returns the id of the thread.
  10. public Thread.State getState(): returns the state of the thread.
  11. public boolean isAlive(): tests if the thread is alive.
  12. public void yield(): causes the currently executing thread object to temporarily pause and allow other threads to execute.
  13. public void suspend(): is used to suspend the thread(depricated).
  14. public void resume(): is used to resume the suspended thread(depricated).
  15. public void stop(): is used to stop the thread(depricated).
  16. public boolean isDaemon(): tests if the thread is a daemon thread.
  17. public void setDaemon(boolean b): marks the thread as daemon or user thread.
  18. public void interrupt(): interrupts the thread.
  19. public boolean isInterrupted(): tests if the thread has been interrupted.
  20. public static boolean interrupted(): tests if the current thread has been interrupted.
  1. public static void main(String args[]){
  2. Multi3 m1=new Multi3();
  3. Thread t1 =new Thread(m1); // Using the constructor Thread(Runnable r )
  4. t1.start();
  5. }
  6. } Output: thread is running... If you are not extending the Thread class, your class object would not be treated as a thread object. So you need to explicitly create the Thread class object. We are passing the object of your class that implements Runnable so that your class run() method may execute.
  1. Using the Thread Class: Thread(String Name) We can directly use the Thread class to spawn new threads using the constructors defined above. FileName: MyThread1.java
  1. public class MyThread
  2. {
  3. // Main method
  4. public static void main(String argvs[])
  5. {
  6. // creating an object of the Thread class using the constructor Thread(Stri ng name)
  7. Thread t= new Thread("My first thread");
  8. // the start() method moves the thread to the active state
  9. t.start();
  10. // getting the thread name by invoking the getName() method
  1. String str = t.getName();
  2. System.out.println(str);
  3. }
  4. } Output: My first thread
  1. Using the Thread Class: Thread(Runnable r, String name) Observe the following program. FileName: MyThread2.java
  1. public class MyThread2 implements Runnable
  2. {
  3. public void run()
  4. {
  5. System.out.println("Now the thread is running ...");
  6. }
  7. // main method
  8. public static void main(String argvs[])
  9. {
  10. // creating an object of the class MyThread
  11. Runnable r1 = new MyThread2();
  12. // creating an object of the class Thread using Thread(Runnable r, S tring name)
  13. Thread th1 = new Thread(r1, "My new thread");
  14. // the start() method moves the thread to the active state
  15. th1.start();
  16. // getting the thread name by invoking the getName() method
  17. String str = th1.getName();
  18. System.out.println(str);
  19. }
  20. }

In the above table, we can see that Thread t1 has arrived first, then Thread t2, then t3, and at last t4, and the order in which the threads will be processed is according to the time of arrival of threads. Hence, Thread t1 will be processed first, and Thread t4 will be processed last. Time-slicing scheduling: Usually, the First Come First Serve algorithm is non-preemptive, which is bad as it may lead to infinite blocking (also known as starvation). To avoid that, some time-slices are provided to the threads so that after some time, the running thread has to give up the CPU. Thus, the other waiting threads also get time to run their job.

In the above diagram, each thread is given a time slice of 2 seconds. Thus, after 2 seconds, the first thread leaves the CPU, and the CPU is then captured by Thread2. The same process repeats for the other threads too. Preemptive-Priority Scheduling: The name of the scheduling algorithm denotes that the algorithm is related to the priority of the threads. Suppose there are multiple threads available in the runnable state. The thread scheduler picks that thread that has the highest priority. Since the algorithm is also preemptive, therefore, time slices are also provided to the threads to avoid starvation. Thus, after some time, even if the highest priority thread has not completed its job, it has to release the CPU because of preemption.