Thread Lifecycle and Basic Operations

Welcome to the next step in our journey into Java concurrency. In this lesson, we will delve deeply into the lifecycle of a thread and explore some fundamental thread operations. By the end of this lesson, you will have a firm grasp of thread states, lifecycle management, and how to use essential thread methods to control their behavior.

What You'll Learn

In this lesson, you'll explore:

  • The different states in the lifecycle of a thread.
  • Key thread methods such as start(), sleep(), join(), and setPriority().
  • Practical insights into thread priorities and how they affect thread scheduling.

By the end of this lesson, you will understand how to manage thread lifecycles effectively and use different thread operations to build efficient multithreaded applications.

Thread Lifecycle

In Java, a thread can exist in one of several states throughout its lifecycle. Understanding these states is crucial to designing and debugging multithreaded applications effectively:

  1. NEW: The thread is created but not yet started.
  2. RUNNABLE: The thread is ready to run and is waiting for CPU time.
  3. BLOCKED: The thread is waiting for a monitor lock to enter or re-enter a synchronized block/method. For instance, if multiple threads are trying to access the same synchronized method, they may be blocked until they acquire the lock.
  4. WAITING: The thread is waiting indefinitely for another thread to perform a particular action.
  5. TIMED_WAITING: The thread is waiting for another thread to perform a particular action for a specified waiting time.
  6. TERMINATED: The thread has completed its execution, either because it has run to completion or because an exception has occurred that has terminated the run method.

Consider the following simple implementation of the Runnable interface:

This RunnableDemo class implements the Runnable interface and overrides the run method to print the thread's name. Now, let's use this class to understand the basic thread operations.

Creating and Starting Threads

When a thread is created, it starts in the NEW state. It transitions to the RUNNABLE state when the start method is called, which internally invokes the run method in a new thread of execution.

In the Main class, we create two threads, t1 and t2, and start them using the start method. It's important to note that calling the run method directly does not create a new thread—it merely executes the run method in the current (main) thread. Always use the start method to create a new thread of execution.

The Sleep Method

The sleep method pauses the execution of the current thread for a specified duration, putting the thread into the TIMED_WAITING state.

It is enclosed in a try-catch block because Thread.sleep can throw an InterruptedException if another thread interrupts the sleeping one. This exception needs to be handled, and the catch block provides a way to manage this interruption and continue program execution.

In the run method, we make the current thread sleep for 1 second, which is useful for simulating a delay in the thread's activity or for scheduling tasks.

The Join Method

The join method pauses the execution of the current thread until the specified thread completes its execution. This allows one thread to wait for another to finish before proceeding. When the join method is called without a timeout, the current thread will enter the WAITING state until the specified thread completes.

In this example, t1.join() makes the main thread wait until t1 finishes execution before starting t2. This ensures that t2 only starts running after t1 is done.

Since join can throw an InterruptedException if the current thread is interrupted while waiting, we need to either declare throws InterruptedException in the method signature (as shown above) or handle it using a try-catch block. This ensures that the program safely deals with any interruptions that might occur during the thread's waiting period.

Entering the BLOCKED State

The BLOCKED state occurs when a thread is waiting to gain access to a resource that is being used by another thread in a synchronized block or method. We'll learn more about the synchronized keyword in the next unit, but for now, you can think of it as a mechanism that ensures only one thread can execute a particular method or block of code at a time, preventing conflicts in multi-threaded environments.

In this situation, if multiple threads try to execute the same synchronized code, only one thread can proceed, while others are temporarily blocked until the first thread finishes and releases control of the code.

In this example, printMessage is a synchronized method. When t1 is executing this method, t2 will enter the BLOCKED state, waiting for t1 to finish before it can execute the same method.

Terminating Threads

A thread moves to the TERMINATED state after its run method completes execution or when an unhandled exception occurs that stops the thread.

In the above RunnableDemo class, once the run method finishes execution, the thread will move into the TERMINATED state. This state indicates that the thread has either completed its task or has stopped due to an exception.

Setting Priority

In Java, thread priorities can be set using the setPriority method, which helps guide the thread scheduler in deciding which thread to execute first. Priorities range from Thread.MIN_PRIORITY (1) to Thread.MAX_PRIORITY (10), with Thread.NORM_PRIORITY (5) as the default. By default, a thread inherits the priority of its creator.

The JVM uses a fixed-priority pre-emptive scheduling algorithm, which usually gives preference to the highest-priority thread. However, sometimes the scheduler may run lower-priority threads to avoid starvation—a situation where high-priority threads keep lower-priority threads from executing.

Java's Thread class provides:

  • getPriority(): Returns the current priority of the thread.
  • setPriority(int priority): Sets the thread's priority to a value between 1 and 10. Any value outside this range will throw an error.

Here's an example:

In this example, t1 is assigned maximum priority, t2 normal priority, and t3 minimum priority. The actual execution order can vary, as the behavior of thread priorities is platform-dependent. Thus, priorities are a suggestion to the JVM and not a strict guarantee of execution sequence.

The Importance of Thread Lifecycle and Operations

Understanding the lifecycle and operations of threads is crucial for managing concurrent tasks effectively. Here’s why:

  • Efficient Resource Management: Knowing when and how to pause or terminate threads helps in conserving system resources.
  • Improving Performance: Properly prioritizing threads can lead to more efficient execution of important tasks.
  • Avoiding Deadlocks: Techniques such as careful use of join and managing synchronized blocks help in avoiding deadlocks—situations where threads are waiting indefinitely for each other. (We will explore deadlocks in more depth in a future unit.)
  • Real-World Applications: From web servers to database systems, many real-world applications rely on well-managed threads to handle multiple tasks concurrently, making these concepts vital for developing robust applications.

By mastering these thread lifecycle states and operations, you'll be well-equipped to handle complex concurrent programming scenarios, thereby enhancing your software development skills.

Ready to put these concepts into practice? Let’s move to the exercises and get started!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal