Chapter 19: Multiple Threads

19.1: Class Thread

At one moment the JVM can run several threads. Each thread is a single sequential flow of control. JVM has a scheduler, which divides the computing time and distribute it among all concurrent threads. Each thread has its own priority, and threads with higher priorities get more computing time than those with lower priorities.

Virtually any running piece of program belong to a certain thread. The main program belongs to a main thread.

Each thread is represented by an object which inherits from class java.lang.Thread. Its key method is run which its subclass must implement. You simply put the code that you want to be run as a thread into this method. Then when you call Thread's start method, it will do necessary initialization, then call the run method and start this thread.

You can pass a String to a thread's constructor as its name.

A thread dies when control reaches the end of the run method.

   
   public class Test {
       private class Thread1 extends Thread {  // Inner class. Good approach!
           private int id;
   
           public Thread1(int id0)
           {   id = id0; }
   
           public void run()
           {
               for(int i = 0; i<3; i++)
               {
                   System.out.println("Thread " + id + ": step " + i);
                   try { sleep(100); }
                   catch(InterruptedException e) {}
               }
           }
       }
   
       public static void main(String [] s)
       {
           Test t1 = new Test();
           Thread [] thread = new Thread[3];
           for(int i = 0; i<3; i++)
           {
               thread[i] = t1.new Thread1(i);
               thread[i].start();
           }
       }
   }

Output will be:

   
   Thread 0: step 0
   Thread 1: step 0
   Thread 2: step 0
   
   Thread 0: step 1
   Thread 1: step 1
   Thread 2: step 1
   
   Thread 0: step 2
   Thread 1: step 2
   Thread 2: step 2

* Method sleep

Scheduler divide the time into slots and give each thread one slot to run. When the time is up, scheduler will put the thread to sleep and wake up another one. When a thread still has time but you want to put it to sleep explicitly, you can call its sleep method. This method causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds. The thread does not lose ownership of any monitors. If a thread who owns a monitor is put to sleep, other threads will be waken by the scheduler, but they still can not access the locked synchronized methods.

A sleeping thread can be interrupted by calling its method interrput. Method sleep may throw a InterruptedException when the sleep is interrupted.

In the above example, if we mark out the call to sleep, we will get an output like

   
   Thread 0: step 0
   Thread 0: step 1
   Thread 0: step 2
   
   Thread 1: step 0
   Thread 1: step 1
   Thread 1: step 2
   
   Thread 2: step 0
   Thread 2: step 1
   Thread 2: step 2

The reason is that the run method is too short, it can be finished within one time slice. So we use sleep to force the current thread to give up the control.

Putting one thread into sleep properly can shorten other thread's running time and achieve an overal efficiency improvement. If one thread is quite long, before it's finished, it may be stopped and restarted for many times. Putting another thread to sleep for a while means that the scheduler does not need to stop the long thread prematurely to come back to the other thread, thus reducing the overhead and increasing the efficiency of the whole program.

* Method yield

Compared with sleep which refuses the control for a certain period of time, method yield simply give up the control to the scheduler before its time is up. If after a very short time the scheduler comes back, the thread will accept the control again.

This is a good way to give up control without slowing itself down.

A way to keep a thread alive but do not consume any computer time is putting the following code at the end of run method:

   
   while(true)
       yield();

* Some other features of class Thread

  1. It has a priority which you can check and set using method getPriority and setPriority;
  2. It can have a name, and it always belongs to a thread group
  3. You can check whether a thread is alive or asleep with method isAlive
  4. It can be a deamon thread, which works in the background
  5. You can get the current thread - even if you are not explicitly using thread, you have a thread called main - by calling static method currentThread

19.2: Interface Runnable

Because Java class can only extend one class, if a class have to extend another class, e.g. JFrame, then it can not extend Thread. To solve this problem, Java provides interface java.lang.Runnable, which has that run method. A class can just implement Runnable, implement the run method in the same way as with Thread. Then when you want to run this object, wrap a Thread object around this Runnable object and call Thread's start.

   
   import java.awt.*;
   
   public class Test extends Frame implements Runnable{
       private int id;
       private Thread self = null;
   
       public Test(int id0)
       {   id = id0; }
   
       public void run()
       {
           for(int i = 0; i<3; i++)
               System.out.println("Thread " + id + ": step " + i);
       }
   
       public static void main(String [] s)
       {
           Thread mainThread = Thread. currentThread();
   
           Test t1 = new Test(1234);
           Thread th1 = new Thread(t1); // Wrap a Thread around a Runnable
           th1.start();
   
           for(int i = 0; i<3; i++)
               System.out.println("In main: step " + i);
   
           t1.setSize(300,300);
           t1.show();
       }
   }

A disadvantage of implementing interface Runnable is that it can not call class Thread's methods such as sleep, yield, wait, etc. This greatly reduces the power of a thread.

To solve this problem, we add a Thread object as a member of that Runnable class, and let it wrap around the Runnable object itself (using handle this). Then in the run method we can call that thread member's methods.

   
   public class Test implements Runnable{
       private int id;
       private Thread self = null;
   
       public Test(int id0)
       {
            id = id0;
           self = new Thread(this);  // A Runnable object holds a Thread object
           self.start();             // wrapping around itself
       }
   
       public void run()
       {
           for(int i = 0; i<3; i++)
           {
               System.out.println("Thread " + id + ": step " + i);
               try { self. sleep(100); }
               catch(InterruptedException e) {}
           }
       }
   
       public static void main(String [] s)
       {
           Thread mainThread = Thread.currentThread();
           Test t1 = new Test(1234);
   
           for(int i = 0; i<3; i++)
           {
               System.out.println("In main: step " + i);
               try {  mainThread.sleep(100);  }
               catch(InterruptedException e){}
           }
       }
   }

Output will be:

   
   In main: step 0
   Thread 1234: step 0
   
   In main: step 1
   Thread 1234: step 1
   
   In main: step 2
   Thread 1234: step 2

You can see the whole logic of this program is a bit twisted. Whenever you want to combine other functionality into a thread class, you will often find that you have to inherit some other classes so you have to implement Runnable and have this twist.

The way to avoid using Runnable is to make a thread object purely a thread - only put the part of code that you want to run as a thread into that class. Make the thread class an inner class and put all other functionality in the outer class. This way you can always avoid using Runnable.

19.3: Monitor/Lock of an Object

Normally, before a method call is finished, if it is called again (obviously this can only happen in a multi-thread process), the result of the data which this method is working on may have error.

On the other hand, an object may have two or more methods to access the same data, e.g. one set method to update data and one get method to read it. If one thread calls the set method and is put to sleep half-way done, then another thread calls the get method, it may get a wrong result.

To prevent these things from happening, we must lock the object once the method is accessed until the method call is finished. To achieve this, each Object has one single monitor or lock. Key word synchronized can be put in front of a method or even a block. When a synchronized method or block is accessed, the object is locked, and the accessed method and all other synchronized methods or blocks of that object can not be accessed, until the initial access is finished.

The thread who locked the object is said to own the monitor of the object.

When an object is locked, unsynchronized methods can still be accessed.

A synchronized method takes four times slower than an unsynchronized one. So do not synchronized a method unless really have to.

   
   class Count {
       synchronized public void count()
       {
           for(int i = 0; i<3; i++)
           {
               System.out.println("Thread step " + i);
               try { Thread.currentThread().sleep(100); }
               catch(InterruptedException e) {}
           }
   
           System.out.println("** End **\n");
       }
   }
   
   class CallCount extends Thread {
       private Count count = null;
   
       public CallCount(Count c)
       {
           count = c;
           start();
       }
   
       public void run()
       {
           count.count();
       }
   }
   
   public class Test {
       public static void main(String [] s)
       {
           Count count = new Count();
   
           for(int i = 0; i<3; i++)
           {
               new CallCount(count);
           }
       }
   }

The output will be:

   Thread step 0
   Thread step 1
   Thread step 2
   Thread step 3
   Thread step 4
   ** End **
   
   Thread step 0
   Thread step 1
   Thread step 2
   Thread step 3
   Thread step 4
   ** End **
   
   Thread step 0
   Thread step 1
   Thread step 2
   Thread step 3
   Thread step 4
   ** End **

If we comment out the keyword "synchronized", the output will be

   
   Thread step 0
   Thread step 0
   Thread step 0
   Thread step 1
   Thread step 1
   Thread step 1
   Thread step 2
   Thread step 2
   Thread step 2
   Thread step 3
   Thread step 3
   Thread step 3
   Thread step 4
   Thread step 4
   Thread step 4
   ** End **
   
   ** End **
   
   ** End **
   

* Method wait( ) and wait(long)

Sometimes a thread accesses a synchronized method and get stuck halfway waiting for something not under its control, such as IO. It may get stuck for a long time. We do not want to waste this time. Then we put some code in that synchronized method, saying that "if I am stuck and won't be OK for a while, I will unlock the monitor so that other threads can use it". We will only put this code at a proper point so that when other threads accesses this method, there will be no trouble.

When a thread owns a monitor of an object, it can call Object's wait method to release its ownership of the monitor. This waiting thread is said to be "waiting on this object's monitor". Then another thread will be able to access the synchronized methods. The second thread who locked the object may also call wait and become waiting for the object's monitor, and so on. So generally there can be many threads waiting for an object's monitor.

Only a thread who owns the monitor can call wait. If a thread who does not own the monitor calls wait, an IllegalMonitorStateException will be thrown.

Because a thread only owns a monitor (i.e. locks the object) when it accesses a synchronized method of that object, more precisely speaking it is actually the synchronized method but a thread who calls wait. It is of course also correct to say that the thread calls wait, because at that moment that method belongs to that thread.

* Method notify and notifyAll

You can either call wait( ) to wait endlessly until another thread calls the notify method of the object, or call wait(long) to wait for a certain milliseconds and then wake up automatically. Before a thread wakes up automatically it can also be waken by notify.

When one thread releases the monitor, another thread will use it. After it finishes, it should call the object's notify method to wake up one arbitrary thread who is waiting on the monitor, or call notifyAll to wake up all waiting threads and let them compete for the monitor on their priorities.

* An analogy about wait and notify

Suppose there is only one computer in your office. When you are writing a report on it half way you wouldn't like others to touch your keyboard. So right after you start to use the computer you lock it somehow from others by e.g. putting a note on the screen when you are away for a second. Now you are one thread and the computer is a locked object.

Now halfway during your writing, you find that you need some information, and you are waiting for your friend's email for that information. So you stop working and the computer becomes idle. You begin to feel guilty, because other colleagues i.e. threads are waiting to use this resource. So you log off the computer, releasing the lock of this resource to allow other threads to access it. It is equal to calling wait.

You can either decide to come back after one hour, and join those waiting persons to get hold of the computers again - that is equal to calling wait(long), or you may decide that you will stay in your office until the guy who is using the computer after you finishes his job and notify you - that is equal to calling wait( ). One problem with the later option is: what if you decide to wait until notified, but the guy finishes but does not notify you. Then you will be waiting endlessly. So it seems to be safer to call wait(long).

19.4: An Example About the Use of Monitor

In the following example, class TwoCounters holds two counters and an increase method to increase the two counters by one, and a check method to check whether they are equal. Data member increaser knows who is increasing the counters.

Inner class Increaser is a thread to call TwoCounter's increase method, and Checker to call check. In main one object of each inner thread class is created.

* Testing step (1) unsynchronized methods

   
   import java.awt.*;
   
   class TwoCounters {
       private TextArea tf1 = new TextArea("xxx", 2, 30);
       private int counter1 = 0;
       private int counter2 = 0;
       private String increaser = null;
   
       private void delay(int i) // create one milisecond delay
       {
           for(long x = 0; x < 1000; x++)
               for(long y = 0; y < 32 * i; y++);
       }
   
       public TwoCounters(Container c1)
       {   c1.add(tf1);  }
   
       public void increase(Test.Increaser th1)
       {
           increaser = th1.id;
           counter1++;
           delay(2000);  //delay for two seconds
           counter2++;
           delay(2000);
       }
   
       public void check()
       {
           String temp = increaser + "   " + counter1 +
                         ((counter1 == counter2)? " ==" : " !=") + counter2;
           if(temp != tf1.getText())
               tf1.setText(temp);
       }
   }
   
   
   public class Test extends Frame {
   
       public class Increaser extends Thread {
           private TwoCounters tc1;
           public String id = null;
   
           public Increaser(TwoCounters tc0, String id0)
           {
               tc1 = tc0;
               id = id0;
               start();
           }
   
           public void run()
           {
               while(true)
               {
                   tc1.increase(this);
                   yield();
               }
           }
       }
   
       public class Checker extends Thread {
           private TwoCounters tc1;
   
           public Checker(TwoCounters tc0)
           {
               tc1 = tc0;
               start();
           }
   
           public void run()
           {
               while(true)
               {
                   tc1.check();
                   yield();
               }
           }
       }
   
   
       public static void main(String [] s)
       {
   
           Test t1 = new Test();
           t1.setLayout(new FlowLayout());
           TwoCounters tc1 = new TwoCounters(t1);
           t1.setSize(400,150);
           t1.show();
           t1.new Increaser(tc1, "Increaser 1");
           t1.new Checker(tc1);
       }
   }

Display like "Increase 1 3 !=4" and "Increaser 1 4 = =4"will show for two seconds alternately, because the two counters will stay unequal for two seconds, then equal for two seconds.

* Testing step (2) synchronized methods

Now add keyword synchronized in front of method increase and check, result will be: "Increaser 1 = =" will stay, and "!=" will never show up. This is because once Increaser thread accesses increase, the TwoCounters object is locked, and Checker thread can not access check when the two counters are unequal.

* Testing step (3) an approprite place to call wait

Now in method increase replace the second delay with a call to wait:

   
       synchronized public void increase(Test.Increaser th1)
       {
           increaser = th1.id;
           counter1++;
           delay(1000);
           counter2++;
   
           try { wait(8000); }
           catch(InterruptedException e){}
       }

and in main create four more Increase thread:

   
       public static void main(String [] s)
       {
   
           Test t1 = new Test();
           t1.setLayout(new FlowLayout());
           TwoCounters tc1 = new TwoCounters(t1);
           t1.setSize(400,150);
           t1.show();
           t1.new Increaser(tc1, "Increaser 1");
           t1.new Increaser(tc1, "Increaser 2");
           t1.new Increaser(tc1, "Increaser 3");
           t1.new Increaser(tc1, "Increaser 4");
           t1.new Increaser(tc1, "Increaser 5");
           t1.new Checker(tc1);
       }

Display like "Increaser 1 1= =1", "Increaser 2 2 ==2", "Increaser3 3= =3", "Increaser 4 4= =4", "Increaser 5 5= =5" will show one second in sequence, then the last one will stay about three seconds. This is because thread "Increase 1" owns the monitor for one second, then goes to wait, then "Increaser 2" takes the monitor for one second, then goes to wait, then "Increase 3", and so on. In the last three seconds all threads are waiting.

This test is to show you that one synchronized method of one object can still be called by many threads meantime without trouble, so long as the call to wait is at the right place so that necessary finish up is done. The following example will show you an incorrect place to call wait.

* Test step (4) a wrong place to call wait

Now if we add a call to wait between the two counters:

   
       synchronized public void increase(Test.Increaser th1)
       {
           increaser = th1.id;
           counter1++;
           delay(500);
   
           try { wait(2000); }
           catch(InterruptedException e){}
   
           counter2++;
   
           try { wait(2000); }
           catch(InterruptedException e){}
       }
   

Then we can see that first the left operand of the comparison sign will increase one by one for five, then the right operand will increase for five, then the left one again, and so on. For example, "1==1", "2!=1", "3!=1", "4!=1", "5!=1", "5!=2", "5!=3", "5!=4", "5==5", ... This proves that if you decide to give up your monitor at a wrong point in the flow, then even if the methods has been synchronized, you may still have trouble.

19.5: Another Example about Wait

The above example is modified. Now the Sender sends one character, then goes to sleep for 3 seconds. The Receiver's run method has an infinite loop to receive the characters sent by the Sender. It has another method blink, which will blink a TextField once. In the main method, the main thread has an infinite loop to call blink.

   
   import java.awt.*;
   import java.io.*;
   
   class Sender extends Thread {
       private Writer out = null;
   
       public Sender(Writer w0)
       {
           out = w0;
           start();
       }
   
       public void run()
       {
           char temp;
           while(true)
           {
               for(char c = 'A'; c < 'Z'; c++)
               {
                   try{ out.write(c); }
                   catch(IOException e) {}
   
                   try{ sleep(3000); }
                   catch(InterruptedException e) {}
               }
           }
       }
   }
   
   class Receiver extends Thread {
       private Reader in = null;
       private TextArea t1 = new TextArea(5,40);
       private TextField t2 = new TextField(5);
   
       public Receiver(Container c0, Reader r0)
       {
           t1.setEditable(false);
           in = r0;
           c0.add(t1);
           c0.add(t2);
           start();
       }
   
       synchronized public void blink()
       {
           try {
               t2.setText("---");
               sleep(80);
               t2.setText("   ");
               sleep(80);
           }
           catch(InterruptedException e) {}
       }
   
       synchronized public void run()
       {
           while(true)
           {
               /*try { wait(1500); }
               catch(InterruptedException e) {}*/
   
               try { t1.append(" " + (char)(in.read())); }
               catch(IOException e){}
   
               try { sleep(100); }
               catch(InterruptedException e) {}
           }
       }
   }
   
   
   public class Test1 extends Frame{
       public static void main(String [] argu)
       {
           PipedReader reader = null;
           Test1 t1 = new Test1();
           t1.setLayout(new FlowLayout());
   
           PipedWriter writer = new PipedWriter();
           try{ reader = new PipedReader(writer); }
           catch(IOException e) {}
   
           new Sender(writer);
           Receiver r1 = new Receiver(t1, reader);
   
           t1.setSize(600,200);
           t1.show();
   
           while(true)
           {
               r1.blink();
               try{ Thread.currentThread().sleep(10); }
               catch(InterruptedException e) {}
           }
       }
   }

Now if Receiver's run and blink are not synchronized, you will see the TextField keep blinking, because when Receiver's run is waiting for IO and Sender is sleeping, the main thread will have plenty of time to call Receiver's blink.

After we make Receiver's run and blink synchronized, you will see that the TextField will never blink, because Receiver's run is an infinite loop and it will lock the thread object r1 forever, so that main thread can never call blink.

Now when we call wait in Receiver's run for 1.5 second, we will see that the TextField will keep still for 1.5 seconds and then blinking for 1.5 seconds. It means that when Receiver's run is waiting for IO (it takes 3 seconds), it gives up the monitor for 1.5 seconds so that other threads can call its synchronized methods.

19.6: Blocking, Unblocking, Suspending, and Terminating a Thread

When a thread is not alive, for example, when it is sleeping, waiting, or trying to call a synchronized method which is locked by another thread, we say that this thread is blocked. Especially, when a thread is waiting for IO, it will become blocked - computing time will be give to other threads.

* Terminating a thread with a flag

The bad way to terminate a thread is to call its deprecated method stop. It is deprecated because it doesn't do any necessary clean up and leave the state of an object damaged.

The correct way is to set a flag in the thread to indicate whether it's time to stop. For example, the event handler of the "Quit" button may set that flag.

* Blocking a thread from outside with a flag

As we discussed before, a thread can become blocked itself when it calls sleep, wait, or is waiting for IO. But how can the outside world block a thread?

A thread can not be put to sleep by another thread, because sleep is a static class method. It does not belong to any specific thread object. A call to this static method, no matter through an object handle or class name, can only put a thread itself to sleep, not another thread. Calling Thread.sleep in the main method will put the main thread to sleep.

Outsiders also can not call a thread's wait method, because the outsider does not know whether the thread owns the monitor at the moment when it calls.

A bad way to block a thread is to call its suspend method and then restart it with resume. These two methods are deprecated because they are deadlock-prone - a suspended thread doesn't release the monitor. If one thread is suspended and expects another thread to resume it, while that other thread is waiting for the monitor and only after that can it call resume, then a deadlock happens.

The correct way to block a thread is similar to the correct way to terminate a thread: put a flag in the thread, and at the appropriate point in the flow check this flag to decide whether to call sleep or wait.

* Interrupting a blocked thread

When a thread in blocked - by calling sleep, wait, or whatever, you can call its interrupt method to wake it up. Then the sleep or wait method will throw an InterruptedException. That's why you have to catch it.

The following example demonstrates all the above concepts:

   
   import java.awt.*;
   import java.io.*;
   import java.awt.event.*;
   
   class Blinker extends Thread {
       private TextField t1 = new TextField(30);
       private    boolean stopFlag = false;
       private boolean sleepFlag = false;
   
       public Blinker(Container c0)
       {
           t1.setEditable(false);
           c0.add(t1);
       }
   
       synchronized void doNothing(){}
   
       public void setStopFlag(boolean f1)
       {   stopFlag = f1;  }
   
       public void setSleepFlag(boolean f1)
       {   sleepFlag = f1;  }
   
       synchronized public void run()
       {
           while(!stopFlag)
           {
               for(int i = 0; i < 5; i++)
               {
                   t1.setText("---");
                   try {  wait(2000); }
                   catch(InterruptedException e2){}
                   t1.setText("///");
                   try {  sleep(2000); }
                   catch(InterruptedException e2){}
                   for(long ii = 0; ii < 999999; ii++);
                   yield();
               }
               t1.setText("Auto Sleeping");
               try {  sleep(3000); }
               catch(InterruptedException e1) {}
   
               if(sleepFlag)
               {
                   t1.setText("Put to Sleep");
                   try {  sleep(5000); }
                   catch(InterruptedException e1) {}
                   sleepFlag = false;
               }
           }
           t1.setText("Stopped!");
       }
   }
   
   
   public class Test1 extends Frame{
       Button setStopFlag = new Button("Stop Flag");
       Button setSleepFlag = new Button("Sleep Flag");
       Button interrupt = new Button("Interrupt");
   
       Blinker blinker = null;
   
       class StopL implements ActionListener
       {
           public void actionPerformed(ActionEvent e)
           {   blinker.setStopFlag(true);  }
       }
   
       class SleepL implements ActionListener
       {
           public void actionPerformed(ActionEvent e)
           {   blinker.setSleepFlag(true);  }
       }
   
       class InterruptL implements ActionListener
       {
           public void actionPerformed(ActionEvent e)
           {   blinker.interrupt();  }
       }
   
       public static void main(String [] argu)
       {
           Test1 t1 = new Test1();
           t1.setLayout(new FlowLayout());
           t1.blinker = new Blinker(t1);
   
           t1.setStopFlag.addActionListener(t1.new StopL());
           t1.setSleepFlag.addActionListener(t1.new SleepL());
           t1.interrupt.addActionListener(t1.new InterruptL());
   
           t1.add(t1.setStopFlag);
           t1.add(t1.setSleepFlag);
           t1.add(t1.interrupt);
           t1.setSize(600,200);
           t1.show();
           t1.blinker.start();
       }
   }

The Blinker thread will blink "---" and "///" for two seconds alternatively for 5 times, then it will show "Auto Sleeping" for 3 seconds. All the time intervals are achieved by calling either wait or sleep. The thread will repeat the above process infinitely.

If you press the "Stop Flag" button, the stop flag of the thread is set true, and the thread will terminate at the beginning of one cycle when stopFlag is checked. This demonstrates the correct way to terminate a thread.

If you press the "Sleep Flag" button, the sleep flag of the thread is set true, and the thread will be blocked for an extra 5 seconds showing "Put to sleep", at the end of the cycle where the sleepFlag is checked. This demonstrates the correctly way to block a thread from outside. Putting it on waiting is the same.

If you press "Interrupt" button at any time interval - during a call to wait or sleep, that interval will be interrupted and next step will start immediately. This demonstrates the effect of interrupt.

19.7: Synchronized Block

Not only a method can be synchronized with others, a block of code inside a method can also be synchronized. The handle of the object to be locked (usually represented by this) should be put in the "( )".

   
   synchronized(this)
   {
       code to be synchronized
   }

19.8: Multiple Thread of the OS

Normally when we talk about multiple threads, we mean multiple threads created by one main program. But the OS (such as Windows and Unix) also support multiple threads. You can run two main programs at the mean time.

Look at the following program:

   
   class Test {
       public static void main(String [] argu)
       {
            Test t1 = new Test();
            int i = 0;
            while(true)
            {
                System.out.println("***** " + i++);
                //delay();
                try{ Thread.sleep(2000); }
                catch(InterruptedException e) {}
           }
       }
   
       private void delay()
       {
           for(long i = 0; i < 9999; i++)
               for(long j = 0; j < 9999; j++);
       }
   }

If you run this program, and before you close the DOS window you run it again, then you will have two DOS windows running the same program. These two programs are maintained by the OS as multiple threads.

Now if you do not call sleep but call delay, you will find that the counter of the current window always increases much more quickly than the one on the background. It means that the OS scheduler always gives the current window more priority. But if you do not call delay but call sleep, you will find that the current window and the background window increases at exactly the same speed.

Therefore, if you want to write two programs which will be run on the same machine, calling sleep may be a way to balance the computing time between the two applications.

19.9: The Event Dispatcher Thread

If in your program you create a GUI component and add a listener to it, a separate thread called the event dispatcher thread will be started apart from the main thread, which monitors all the GUI components which have registered listeners.

When one event happens over one GUI component, the event dispatcher will enter and execute the corresponding event handling method of the registered listener object. If other events happen before the current event handling method is finished, they are queued until the current method is finished.

So bear in mind that the event handling code will be run as a separate thread.

When the main program creates many windows, you have a feeling that there are multiple threads running, because when you activate any component in any window you can immediately see the corresponding response. But actually because a GUI system is an event-driven system, the main thread only creates and shows those GUIs, and after that it terminates. Then there is only one event dispatcher thread running, taking care of all the GUI components in all windows. A window does not run its own thread. No matter how many windows you have created, they do not consume any computer time if you do not activate a component such as pressing a button or changing a TextArea.

Therefore, if one event handling method blocks the thread, for example, it waits for input or has an infinite loop, the whole system will be frozen and no more events will be created and handled.

The following example demonstrates the above theory. The event handling method actionPerformed handles both the "Start" button and the "Quit" button. The code for "Start" has an infinite loop. If you press "Quit" first the system can exit, but if you press "Enter" first the system will no longer have any response to further events.

   
   import java.awt.*;
   import java.awt.event.*;
   
   public class BadGUI extends Frame implements ActionListener
   {
       private Button start = new Button("Start");
       private Button exit = new Button("Exit");
   
       public BadGUI()
       {
           setLayout(new FlowLayout());
           add(start);
           add(exit);
           start. addActionListener(this);
           exit. addActionListener(this);
           setSize(200,80);
           setVisible(true);
       }
   
       public void actionPerformed(ActionEvent ae)
       {
           if (ae. getSource() == start)
               while(true)
               {
                  try {  Thread.sleep(5000);  } 
                  catch (InterruptedException e) { }
               }
           else
               System.exit(0);
       }
   
       public static void main(String args[])
       {
           new BadGUI();
       }
   }

19.10: Priority of a Thread

A thread has a priority. You can get it with method getPriority, or set it with setPriority. The maximum priority is 10, which is represented by Thread.MAX_PRIORITY, and the minimum priority is 1, which is represented by Thread.MIN_PRIORITY. The priority of a new thread is always 5.

When there are more than one threads running, the scheduler will run the thread with highest priority first. A thread with lower priority gets run less often, but it will be run anyway.

19.11: ThreadGroup

Any thread belongs to a java.lang.ThreadGroup. When you create a thread, you can pass the handle of a thread group to the its constructor. If you don't, this new thread by default belongs to the system thread group.

A thread group also belongs to another thread group. You can pass the name of the parent handle to the child's constructor. If you don't, it by default belongs to the system thread group.

   
   ThreadGroup tg1 = new ThreadGroup("Thread Group A);
   Thread1 th1 = new Thread1(tg1);

Threads within the same thread group can modify each other, while threads in different thread group can not. This reminds me of classes in the same package.

You can ask a thread group how many threads it contains with its method activeCount, or get an array of all its threads with method enumerate(Thread [ ]).

A thread group has a maximum priority. You can get it with getMaxPriority and set it with setMaxPriority. A child thread group can not have higher maximum priority than its parent. Once a thread group is created, its maximum priority can only be decreased.

If a thread group has a max priority of higher than 5, than a new thread in this group always has a default priority of 5. If the max priority of the group is less than 5, then the new thread's priority will be the same as the group. A thread can not have higher priority than the max priority of its group.