Hi! Today we'll continue to talk about multithreading. Let's examine the Thread class and what a few of its methods do. When we studied class methods previously, we usually just wrote this: <method name> -> <what the method does>. Multithreading: What the methods of the Thread class do - 1This won't work with Thread's methods :) They have more complex logic that you won't be able to figure out without a few examples.

The Thread.start() method

Let's start by repeating ourselves. As you probably recall, you can create a thread by making your class inherit the Thread class and overriding the run() method. But it won't start itself, of course. To do this, we call our object's start() method. Multithreading: What the methods of the Thread class do - 2Let's recall the example from the previous lesson:
public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("Thread executed: " + getName());
   }
}


public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {
           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
Note: To start a thread, you must call the special start() method rather than the run() method! This is an easy error to make, especially when you first start studying multithreading. In our example, if you call the run() method 10 times instead of start(), you would get this:
public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {
           MyFirstThread thread = new MyFirstThread();
           thread.run();
       }
   }
}
Look at the results of our program: Thread executed: Thread-1 Thread executed: Thread-2 Thread executed Thread-3 Thread executed: Thread-4 Thread executed: Thread-5 Thread executed: Thread-6 Thread executed: Thread-7 Thread executed: Thread-8 Thread executed: Thread-9 Look at the order of the output: Everything is happening in perfect order. Weird, huh? We're not used to this, because we already know that the order in which threads are started and executed is determined by a superior intellect inside our operating system: the thread scheduler. Maybe we just got lucky? Of course, this isn't about luck. You can verify this by running the program a couple more times. The issue is that call the run() method directly has nothing to do with multithreading. In this case, the program will be executed on the main thread, the same thread that executes the main() method. It simply successively print 10 lines on the console and that's it. 10 threads haven't been started. So, remember this in the future and constantly check yourself. If you want the run() method to be called, call start(). Let's go further.

The Thread.sleep() method

To suspend execution of the current thread for a while, we use the sleep() method. Multithreading: What the methods of the Thread class do - 3The sleep() method takes a number of milliseconds as an argument, which indicates the amount of time to put the thread to sleep.
public class Main {

   public static void main(String[] args) throws InterruptedException {

       long start = System.currentTimeMillis();

       Thread.sleep(3000);

       System.out.println(" - How long did I sleep? \n - " + ((System.currentTimeMillis()-start)) / 1000 + " seconds");

   }
}
Console output: - How long did I sleep? - 3 seconds Note: the sleep() method is static: it sleeps the current thread. That is, the one currently being executed. Here's another important point: a sleeping thread can be interrupted. In this case, the program throws an InterruptedException. We'll consider an example below. By the way, what happens after the thread wakes up? Will it continue to be executed right from where it left off? No. After a thread wakes up, i.e. the time passed as an argument to Thread.sleep() has passed, it transitions into runnable state. But, this doesn't mean that the thread scheduler will run it. It may quite possibly give preference to some other non-sleeping thread and allow our freshly-awakened thread to continue its work a little later. Be sure to remember this: waking up doesn't mean continuing work immediately!

The Thread.join() method

Multithreading: What the methods of the Thread class do - 4The join() method suspends execution of the current thread until another thread finishes. If we have 2 threads, t1 and t2, and we write
t1.join()
then t2 won't start until t1 has finished its work. The join() method can be used to guarantee the execution order of threads. Let's consider how the join() method works in the following example:
public class ThreadExample extends Thread {

   @Override
   public void run() {

       System.out.println("Thread started: "getName());

       try {
           Thread.sleep(5000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("Thread " + getName() + " is finished.");
   }
}


public class Main {

   public static void main(String[] args) throws InterruptedException {

       ThreadExample t1 = new ThreadExample();
       ThreadExample t2 = new ThreadExample();

       t1.start();


 /* The second thread (t2) will start running only after the first thread (t1)
       is finished (or an exception is thrown) */
       try {
           t1.join();
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

       t2.start();

       // The main thread will continue running only after t1 and t2 have finished
       try {
           t1.join();
           t2.join();
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

       System.out.println("All threads have finished. The program is finished.");

   }
}
We created a simple ThreadExample class. Its task is to display a message that the thread has started, fall asleep for 5 seconds, and then finally report that the work is complete. Piece of cake. The main logic is in the Main class. Look at the comments: we use the join() method to successfully manage the threads' execution order. If you remember how we started this topic, execution order is handled by the thread scheduler. It runs threads at its own discretion: each time in a different way. Here we are using the method to guarantee that the t1 thread will first be started and executed first, then the t2 thread, and only after that will the program's main thread continue. Moving on. In real programs, you'll often find situations when you will need to interrupt the execution of a thread. For example, our thread is running, but it's waiting for a certain event or condition. If it occurs, then the thread stops. It would probably make sense if there was some sort of stop() method. But it's not so simple. Once upon a time, Java actually did have a Thread.stop() method and allowed a thread to be interrupted. But it was later removed from the Java library. You can find it in the Oracle documentation and see that it is marked as deprecated. Why? Because it just stopped the thread without doing anything else. For example, the thread might be working with data and changing something. Then in the middle of its work it was abruptly and unceremoniously cut off by the stop() method. Without a proper shutdown, nor the release of resources, not even error handling — there was none of this. To exaggerate slightly, the stop() method simply destroyed everything in its way. It was like pulling the power cord from the outlet to turn off the computer. Yeah, you can get the desired result. But everybody knows that after a couple of weeks the computer won't be thanking you for treating it that way. That's why the logic for interrupting threads changed in Java and now uses a special interrupt() method.

The Thread.interrupt() method

What happens if the interrupt() method is called on a thread? There are 2 possibilities:
  1. If the object was in the waiting state, for example, due to the join or sleep methods, then the wait will be interrupted and the program will throw an InterruptedException.
  2. If the thread was in a functioning state, then the boolean interrupted flag will be set on the object.
But we have to check the value of this flag on the object and correctly complete the work on our own! That's why the Thread class has the boolean isInterrupted() method. Let's return to the clock example that was in a lesson in the basic course. For convenience, we've simplified it slightly:
public class Clock extends Thread {

   public static void main(String[] args) throws InterruptedException {
       Clock clock = new Clock();
       clock.start();

       Thread.sleep(10000);
       clock.interrupt();
   }

   public void run() {
       Thread current = Thread.currentThread();

       while (!current.isInterrupted())
       {
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               System.out.println("The thread was interrupted");
               break;
           }
           System.out.println("Tick");
       }
   }
}
In this case, the clock is started and starts ticking every second. In the 10th second, we interrupt the clock's thread. As you already know, if the thread that we're trying to interrupt is in one of the waiting states, the result is an InterruptedException. This is a checked exception, so we can be easily catch it and execute our logic to finish the program. And that's just what we did. Here's our result: Tick Tick Tick Tcik Tick Tick Tick Tick Tick The thread was interrupted This concludes our introduction to the Thread class's most important methods. Good luck!