Monday 13 March 2017

Using isAlive( ) and join( ) & Thread Priorities - Java Tutorials

Using isAlive( ) and join( )

As mentioned, often you will want the main thread to finish last. In the preceding examples, this is accomplished by calling sleep( ) within main( ), with a long enough delay to ensure that all child threads terminate prior to the main thread. However, this is hardly a satisfactory solution, and it also raises a larger question: How can one thread know when another thread has ended? Fortunately, Thread provides a means by which you can answer this question.

Two ways exist to determine whether a thread has finished. First, you can call isAlive( ) on the thread. This method is defined by Thread, and its general form is shown here:

  final boolean isAlive( )

The isAlive( ) method returns true if the thread upon which it is called is still running. It returns false otherwise.

While isAlive( ) is occasionally useful, the method that you will more commonly use to wait for a thread to finish is called join( ), shown here:

  final void join( ) throws InterruptedException

This method waits until the thread on which it is called terminates. Its name comes from the concept of the calling thread waiting until the specified thread joins it. Additional forms of join( ) allow you to specify a maximum amount of time that you want to wait for the specified thread to terminate.

Here is an improved version of the preceding example that uses join( ) to ensure that the main thread is the last to stop. It also demonstrates the isAlive( ) method.

  // Using join() to wait for threads to finish.
  class NewThread implements Runnable {
    String name; // name of thread
    Thread t;

    NewThread(String threadname) {
      name = threadname;
      t = new Thread(this, name);
      System.out.println("New thread: " + t);
      t.start(); // Start the thread
    }

    // This is the entry point for thread.
    public void run() {
      try {
        for(int i = 5; i > 0; i--) {
          System.out.println(name + ": " + i);
          Thread.sleep(1000);
        }
      } catch (InterruptedException e) {
        System.out.println(name + " interrupted.");
      }
      System.out.println(name + " exiting.");
    }
  }

  class DemoJoin {
    public static void main(String args[]) {
      NewThread ob1 = new NewThread("One");
      NewThread ob2 = new NewThread("Two");
      NewThread ob3 = new NewThread("Three");

      System.out.println("Thread One is alive: "
                          + ob1.t.isAlive());
      System.out.println("Thread Two is alive: "
                          + ob2.t.isAlive());
      System.out.println("Thread Three is alive: "
                          + ob3.t.isAlive());
      // wait for threads to finish
      try {
        System.out.println("Waiting for threads to finish.");
        ob1.t.join();
        ob2.t.join();
        ob3.t.join();
      } catch (InterruptedException e) {
        System.out.println("Main thread Interrupted");
      }

      System.out.println("Thread One is alive: "
                          + ob1.t.isAlive());
      System.out.println("Thread Two is alive: "
                          + ob2.t.isAlive());
      System.out.println("Thread Three is alive: "
                          + ob3.t.isAlive());
      
      System.out.println("Main thread exiting.");
    }
  }

Sample output from this program is shown here:

  New thread: Thread[One,5,main]
  New thread: Thread[Two,5,main]
  New thread: Thread[Three,5,main]
  Thread One is alive: true
  Thread Two is alive: true
  Thread Three is alive: true
  Waiting for threads to finish.
  One: 5
  Two: 5
  Three: 5
  One: 4
  Two: 4
  Three: 4
  One: 3
  Two: 3
  Three: 3
  One: 2
  Two: 2
  Three: 2
  One: 1
  Two: 1
  Three: 1
  Two exiting.
  Three exiting.
  One exiting.
  Thread One is alive: false
  Thread Two is alive: false
  Thread Three is alive: false
  Main thread exiting.

As you can see, after the calls to join( ) return, the threads have stopped executing.





Thread Priorities

Thread priorities are used by the thread scheduler to decide when each thread should be allowed to run. In theory, higher-priority threads get more CPU time than lower-priority threads. In practice, the amount of CPU time that a thread gets often depends on several factors besides its priority. (For example, how an operating system implements multitasking can affect the relative availability of CPU time.) A higher-priority thread can also preempt a lower-priority one. For instance, when a lower-priority thread is running and a higher-priority thread resumes (from sleeping or waiting on I/O, for example), it will preempt the lower-priority thread.

In theory, threads of equal priority should get equal access to the CPU. But you need to be careful. Remember, Java is designed to work in a wide range of environments. Some of those environments implement multitasking fundamentally differently than others. For safety, threads that share the same priority should yield control once in a while. This ensures that all threads have a chance to run under a nonpreemptive operating system. In practice, even in nonpreemptive environments, most threads still get a chance to run, because most threads inevitably encounter some blocking situation, such as waiting for I/O. When this happens, the blocked thread is suspended and other threads can run. But, if you want smooth multithreaded execution, you are better off not relying on this. Also, some types of tasks are CPU-intensive. Such threads dominate the CPU. For these types of threads, you want to yield control occasionally, so that other threads can run.

To set a thread’s priority, use the setPriority( ) method, which is a member of Thread. This is its general form:

  final void setPriority(int level)

Here, level specifies the new priority setting for the calling thread. The value of level must be within the range MIN_PRIORITY and MAX_PRIORITY. Currently, these values are 1 and 10, respectively. To return a thread to default priority, specify NORM_PRIORITY, which is currently 5. These priorities are defined as final variables within Thread.

You can obtain the current priority setting by calling the getPriority( ) method of Thread, shown here:

  final int getPriority( )

Implementations of Java may have radically different behavior when it comes to scheduling. The Windows XP/98/NT/2000 version works, more or less, as you would expect. However, other versions may work quite differently. Most of the inconsistencies arise when you have threads that are relying on preemptive behavior, instead of cooperatively giving up CPU time. The safest way to obtain predictable, cross-platform behavior with Java is to use threads that voluntarily give up control of the CPU.

The following example demonstrates two threads at different priorities, which do not run on a preemptive platform in the same way as they run on a nonpreemptive platform. One thread is set two levels above the normal priority, as defined by Thread.NORM_PRIORITY, and the other is set to two levels below it. The threads are started and allowed to run for ten seconds. Each thread executes a loop, counting the number of iterations. After ten seconds, the main thread stops both threads. The umber of times that each thread made it through the loop is then displayed.

  // Demonstrate thread priorities.
  class clicker implements Runnable {
    int click = 0;
    Thread t;
    private volatile boolean running = true;

    public clicker(int p) {
      t = new Thread(this);
      t.setPriority(p);
    }

    public void run() {
      while (running) {
        click++;
      }
    }

    public void stop() {
      running = false;
    }

    public void start() {
      t.start();
    }
  }

  class HiLoPri {
    public static void main(String args[]) {
      Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
      clicker hi = new clicker(Thread.NORM_PRIORITY + 2);
      clicker lo = new clicker(Thread.NORM_PRIORITY - 2);

      lo.start();
      hi.start();
      try {
        Thread.sleep(10000);
      } catch (InterruptedException e) {
          System.out.println("Main thread interrupted.");
      } 
      
      lo.stop();
      hi.stop();

      // Wait for child threads to terminate.
      try {
        hi.t.join();
        lo.t.join();
      } catch (InterruptedException e) {
        System.out.println("InterruptedException caught");
      }
      System.out.println("Low-priority thread: " + lo.click);
      System.out.println("High-priority thread: " + hi.click);
    }
  }

The output of this program, shown as follows when run under Windows 98, indicates that the threads did context switch, even though neither voluntarily yielded the CPU nor blocked for I/O. The higher-priority thread got approximately 90 percent of the CPU time.

  Low-priority thread: 4408112
  High-priority thread: 589626904

Of course, the exact output produced by this program depends on the speed of your CPU and the number of other tasks running in the system. When this same program is run under a nonpreemptive system, different results will be obtained.

One other note about the preceding program. Notice that running is preceded by the keyword volatile. Although volatile is examined more carefully in the next chapter, it is used here to ensure that the value of running is examined each time the following loop iterates:

  while (running) {
    click++;
  }

Without the use of volatile, Java is free to optimize the loop in such a way that a local copy of running is created. The use of volatile prevents this optimization, telling Jav that running may change in ways not directly apparent in the immediate code.

No comments:

Post a Comment