Introduction
Thread States
Ping Pong Application
Using Join
The Ping Applet
Why Use a Thread? (End of class 1/29/98)
Attempting to Paint
Fixing PingBad.java
Using an External Thread
The PingPong Applet
An Object Oriented Version (End of class 2/5/98)
Summary of Applets
Next Topic: A Simple Animation
If an applet implements Runnable it must have a run
method. Executing:
ping = new Thread(this)
creates a new thread which can execute the run method of the applet.
The thread can be started with its start method:
ping.start();
At this point the thread is in its active state. It remains active
until it is stopped or it completes execution of its run method.
An active thread can be suspended with its suspend method. This delays further execution until its resume method is called. It is not an error to suspend a thread which has not yet started or has completed execution.
sleep throws the InterruptedException so it must catch it.
public class PingPong extends Thread { String word; // what word to print int delay; // how long to pause int count; // number of iterations PingPong(String What, int Time, int number) { word = What; delay = Time; count = number; setName(What); } public void run() { try { for(int i=0;i < count;i++) { System.out.println(i+": "+word+":"+activeCount()); sleep(delay); // wait until next time } } catch (InterruptedException e) { return; // end this thread } } }
class PingPongTest1{ public static void main (String[] args){ PingPong ping; PingPong pong; ping = new PingPong("ping", 2000, 10); pong = new PingPong("PONG", 5000, 3); ping.start(); pong.start(); } }
0: ping:3 0: PONG:3 1: ping:3 2: ping:3 1: PONG:3 3: ping:3 4: ping:3 2: PONG:3 5: ping:3 6: ping:3 7: ping:3 8: ping:2 9: ping:2
The following example waits for each thread to complete and then prints a message. It also has a method which shows the threads.
class PingPongTest2{ public static void show_threads(String msg) { Thread[] tlist = new Thread[50]; int count; count = Thread.enumerate(tlist); System.out.println(msg + " Number of threads: "+count); for (int i=0;i < count;i++) System.out.println(" "+i+": "+tlist[i]); } public static void main (String[] args){ PingPong ping; PingPong pong; show_threads("Start of main"); ping = new PingPong("ping", 2000, 10); show_threads("ping created"); pong = new PingPong("PONG", 3000, 5); show_threads("pong created"); ping.start(); pong.start(); try {pong.join();} catch(InterruptedException e) {} show_threads("pong joined"); try {ping.join();} catch(InterruptedException e) {} show_threads("ping joined"); } }
Start of main Number of threads: 1 0: Thread[main,5,main] ping created Number of threads: 2 0: Thread[main,5,main] 1: Thread[ping,5,main] pong created Number of threads: 3 0: Thread[main,5,main] 1: Thread[ping,5,main] 2: Thread[PONG,5,main] 0: ping:3 0: PONG:3 1: ping:3 1: PONG:3 2: ping:3 2: PONG:3 3: ping:3 4: ping:3 3: PONG:3 5: ping:3 4: PONG:3 6: ping:3 7: ping:3 pong joined Number of threads: 2 0: Thread[main,5,main] 1: Thread[ping,5,main] 8: ping:2 9: ping:2 ping joined Number of threads: 1 0: Thread[main,5,main]
Pushing the Start button starts the thread which executes the run method of the applet. The thread writes into a TextArea and outputs a sound. Since the thread executes the run method it has access to all of the variables of the applet.
The thread should be suspended when the the browser leaves the page containing the applet (the stop method) and resumed when the page is revisited (the start method). Note the test for a null pointer.
The Start button is disabled when the thread is running.
It is enabled when the thread is done.
When the thread is done a new ping thread is created so that it can be started again.
/* < Applet code = Ping width = 300 height = 300 > < /Applet > */ import java.applet.*; import java.awt.*; import java.awt.event.*; public class Ping extends Applet implements ActionListener, Runnable { TextArea output; Button StartButton; AudioClip ping_clip; Thread ping; public void init() { setBackground(Color.lightGray); Panel p = new Panel(); setLayout(new BorderLayout()); output = new TextArea(); output.setBackground(Color.gray); StartButton = new Button("Start"); StartButton.setBackground(Color.red); add("Center",output); add("South",StartButton); ping_clip = getAudioClip(getCodeBase(),"ping.au"); ping = new Thread(this); StartButton.addActionListener(this); } public void stop() { if (ping != null) ping.suspend(); } public void start() { if (ping != null) ping.resume(); } public void run() { StartButton.setEnabled(false); try { for(int i=0;i < 10;i++) { ping_clip.play(); output.append(i+": ping\n"); Thread.sleep(1000); // wait until next time } } catch (InterruptedException e) { return; // end this thread } output.append("done\n"); ping = new Thread(this); // So thread can be started again StartButton.setEnabled(true); } public void actionPerformed(ActionEvent e) { if (e.getSource() == StartButton) { output.append("starting thread\n"); ping.start(); } } }Click Here to run this applet.
/* < Applet code = PingSimple width = 300 height = 300 > < /Applet > */ import java.applet.*; import java.awt.*; import java.awt.event.*; public class PingSimple extends Applet implements ActionListener { TextArea output; Button StartButton; AudioClip ping_clip; public void init() { setBackground(Color.lightGray); Panel p = new Panel(); setLayout(new BorderLayout()); output = new TextArea(); output.setBackground(Color.gray); StartButton = new Button("Start"); StartButton.setBackground(Color.red); add("Center",output); add("South",StartButton); ping_clip = getAudioClip(getCodeBase(),"ping.au"); StartButton.addActionListener(this); } public void run() { StartButton.setEnabled(false); try { for(int i=0;i < 10;i++) { ping_clip.play(); output.append(i+": ping\n"); Thread.sleep(1000); // wait until next time } } catch (InterruptedException e) { return; // end this thread } output.append("done\n"); StartButton.setEnabled(true); } public void actionPerformed(ActionEvent e) { if (e.getSource() == StartButton) { output.append("starting thread\n"); run(); } } }It does behave a little differently in that the output continues if the browser leaves the page containing the applet.
Click Here to run this applet.
The applet PingBad.java> attempts to update a count of the number of pings after each ping.
/* < Applet code = PingBad width = 300 height = 300 > < /Applet > */ import java.applet.*; import java.awt.*; import java.awt.event.*; public class PingBad extends Applet implements ActionListener { TextArea output; Button StartButton; AudioClip ping_clip; int count; public void init() { setBackground(Color.lightGray); Panel p = new Panel(); setLayout(new BorderLayout()); output = new TextArea(); output.setBackground(Color.yellow); StartButton = new Button("Start"); StartButton.setBackground(Color.red); add("North",output); add("South",StartButton); ping_clip = getAudioClip(getCodeBase(),"ping.au"); StartButton.addActionListener(this); count = 0; } public void paint(Graphics g) { g.drawString("Ping Count is "+count,20,250); } public void run() { StartButton.setEnabled(false); try { for(int i=0;i < 10;i++) { ping_clip.play(); output.append(i+": ping\n"); count++; repaint(1); Thread.sleep(1000); // wait until next time } } catch (InterruptedException e) { return; // end this thread } repaint(1); output.append("done\n"); StartButton.setEnabled(true); } public void actionPerformed(ActionEvent e) { if (e.getSource() == StartButton) { output.append("starting thread\n"); run(); } } }The count is only redisplayed when the loop is complete even though repaint(1) is called inside the loop.
Click Here to run this applet.
/* < Applet code = PingFixed width = 300 height = 300 > < /Applet > */ import java.applet.*; import java.awt.*; import java.awt.event.*; public class PingFixed extends Applet implements ActionListener, Runnable { TextArea output; Button StartButton; AudioClip ping_clip; int count; Thread ping; public void init() { setBackground(Color.lightGray); Panel p = new Panel(); setLayout(new BorderLayout()); output = new TextArea(); output.setBackground(Color.yellow); StartButton = new Button("Start"); StartButton.setBackground(Color.red); add("North",output); add("South",StartButton); ping_clip = getAudioClip(getCodeBase(),"ping.au"); ping = new Thread(this); StartButton.addActionListener(this); count = 0; } public void stop() { if (ping != null) ping.suspend(); } public void start() { if (ping != null) ping.resume(); } public void paint(Graphics g) { g.drawString("Ping Count is "+count,20,250); } public void run() { StartButton.setEnabled(false); try { for(int i=0;i < 10;i++) { ping_clip.play(); output.append(i+": ping\n"); count++; repaint(1); Thread.sleep(1000); // wait until next time } } catch (InterruptedException e) { return; // end this thread } repaint(1); output.append("done\n"); ping = new Thread(this); // So thread can be started again StartButton.setEnabled(true); } public void actionPerformed(ActionEvent e) { if (e.getSource() == StartButton) { output.append("starting thread\n"); ping.start(); } } }Click Here to run this applet.
vip% diff PingBad.java PingFixed.java 1c1 < /* < Applet code = PingBad --- > /* < Applet code = PingFixed 10c10 < public class PingBad extends Applet implements ActionListener { --- > public class PingFixed extends Applet implements ActionListener, Runnable { 14a15 > Thread ping; 26a28 %gt ping = new Thread(this); 30a33,42 > public void stop() { > if (ping != null) > ping.suspend(); > } > > public void start() { > if (ping != null) > ping.resume(); > } > 49a62 > ping = new Thread(this); // So thread can be started again 56c69 < run(); --- > ping.start();
public class PingExternalThread extends Thread { PingExternalApplet ap; public PingExternalThread(PingExternalApplet ap) { this.ap = ap; } public void run() { ap.StartButton.setEnabled(false); try { for(int i=0;i < 10;i++) { ap.ping_clip.play(); ap.output.append(i+": ping\n"); ap.count++; ap.repaint(1); sleep(1000); // wait until next time } } catch (InterruptedException e) { return; // end this thread } ap.repaint(1); ap.output.append("done\n"); ap.new_thread(); // So thread can be started again ap.StartButton.setEnabled(true); } }The applet PingExternalApplet.java uses this thread.
/* < Applet code = PingExternalApplet width = 300 height = 300 > < /Applet > */ import java.applet.*; import java.awt.*; import java.awt.event.*; public class PingExternalApplet extends Applet implements ActionListener { TextArea output; Button StartButton; AudioClip ping_clip; int count; PingExternalThread ping; public void init() { setBackground(Color.lightGray); Panel p = new Panel(); setLayout(new BorderLayout()); output = new TextArea(); output.setBackground(Color.yellow); StartButton = new Button("Start"); StartButton.setBackground(Color.red); add("North",output); add("South",StartButton); ping_clip = getAudioClip(getCodeBase(),"ping.au"); new_thread(); StartButton.addActionListener(this); count = 0; } void new_thread() { ping = new PingExternalThread(this); } public void stop() { if (ping != null) ping.suspend(); } public void start() { if (ping != null) ping.resume(); } public void paint(Graphics g) { g.drawString("Ping Count is "+count,20,250); } public void actionPerformed(ActionEvent e) { if (e.getSource() == StartButton) { output.append("starting thread\n"); ping.start(); } } }Click Here to run this applet.
Here we use an external thread similar to PingExternalThread, but we pass the TextAreas and a clip rather than passing the applet.
The PingPongForApplet
The PingPongForApplet.java thread writes strings to two text areas and outputs a sound. The string contains a count of the active threads.
import java.awt.*; import java.applet.*; public class PingPongForApplet extends Thread { String word; // what word to print int delay; // how long to pause int count; // number of iterations TextArea area1; TextArea area2; AudioClip clip; PingPongForApplet(String What, int Time, int number, AudioClip clip, TextArea area1, TextArea area2) { word = What; delay = Time; count = number; this.area1 = area1; this.area2 = area2; this.clip = clip; setName(What); } public void run() { try { for(int i=0;i < count;i++) { clip.play(); area1.append(i+": "+word+":"+activeCount()+"\n"); area2.append(i+": "+word+":"+activeCount()+"\n"); sleep(delay); // wait until next time } } catch (InterruptedException e) { return; // end this thread } area1.append(word+" done\n"); area2.append(word+" done\n"); } }Here is the PPApplet.java applet which starts two of these threads.
Instead of disabling the Start button until the threads are done, it changes it to a Stop button which suspends the threads.
The threads are not automatically resumed if the browser reenters the page containing the applet, the Start button must be pushed first.
/* < Applet code = PPApplet width = 600 height = 400 > < /Applet > */ import java.applet.*; import java.awt.*; import java.awt.event.*; public class PPApplet extends Applet implements ActionListener { TextArea output_common; TextArea output_1; TextArea output_2; Button StartButton; PingPongForApplet ping; PingPongForApplet pong; AudioClip ping_clip; AudioClip pong_clip; public void init() { setBackground(Color.cyan); Panel p = new Panel(); setLayout(new BorderLayout()); p.setLayout(new GridLayout(1,3)); output_common = new TextArea(20,20); output_1 = new TextArea(20,20); output_2 = new TextArea(20,20); output_common.setBackground(Color.yellow); output_1.setBackground(Color.gray); output_2.setBackground(Color.gray); p.add(output_1); p.add(output_common); p.add(output_2); add("North",p); StartButton = new Button("Start"); StartButton.addActionListener(this); StartButton.setBackground(Color.pink); add("South",StartButton); ping_clip = getAudioClip(getCodeBase(),"ping.au"); pong_clip = getAudioClip(getCodeBase(),"pong.au"); } public void paint(Graphics g) { g.drawString("Nothing Useful here.",10,350); } public void stop() { StartButton.setLabel("Start"); if (ping != null) ping.suspend(); if (pong != null) pong.suspend(); } private void start_it() { if (ping == null) { ping = new PingPongForApplet("ping", 2000, 10, ping_clip, output_1,output_common); output_common.append("ping started\n"); output_1.append("ping started\n"); ping.start(); } else if (ping.isAlive()) { output_common.append("ping resumed\n"); output_1.append("ping resumed\n"); ping.resume(); } else { ping = new PingPongForApplet("ping", 2000, 10, ping_clip, output_1,output_common); output_common.append("ping restarted\n"); output_1.append("ping restarted\n"); ping.start(); } if (pong == null) { pong = new PingPongForApplet("PONG", 3000, 5, pong_clip, output_2,output_common); output_common.append("pong started\n"); output_1.append("pong started\n"); pong.start(); } else if (pong.isAlive()) { output_common.append("pong resumed\n"); output_2.append("pong resumed\n"); pong.resume(); } else { pong = new PingPongForApplet("PONG", 3000, 5, pong_clip, output_2,output_common); output_common.append("pong restarted\n"); output_2.append("pong restarted\n"); pong.start(); } } void suspend_it() { output_1.append("ping suspended\n"); output_2.append("PONG suspended\n"); ping.suspend(); pong.suspend(); } public void actionPerformed(ActionEvent e) { if (e.getSource() == StartButton) { output_common.append("Number of threads: "+Thread.activeCount()+"\n"); if (StartButton.getLabel().equals("Start")) { StartButton.setLabel("Stop"); output_common.append("Start Button Pushed\n"); start_it(); } else if (StartButton.getLabel().equals("Stop")) { StartButton.setLabel("Start"); output_common.append("Stop Button Pushed\n"); suspend_it(); } } } }The most complicated part of this is the start_it method which attempts to start the two threads.
Click Here to run this applet.
Conceptually, the thread waits and does output. The type of output should be general.
We can instead have the caller provide a method to do the output. You can do this in a straight forward way by passing the caller as an argument as we did in PingExternalThread, but this requires having the thread depend on the class of the caller.
Instead we create an interface which describes the output.
This is the interface DisplayInfo.java.
public interface DisplayInfo { public abstract void show_string(int id, String str); }It has one abstract method. Any class which implements this interface must define this methods. The PingPongThread.java is also very simple.
It is passed an id which can be used to identify the
instance of the thread and an object of class DisplayInfo.
DisplayInfo acts as a prototype for its methods and variables
so that these can be used by this class.
In this case there is only one method.
import java.awt.*; import java.applet.*; public class PingPongThread extends Thread { int delay; // how long to pause int count; // number of iterations int id; // an id for this thread String what; // String to display DisplayInfo info; // call show_string to display PingPongThread(int id, String what, int Time, int number, DisplayInfo info) { this.id = id; delay = Time; count = number; this.what = what; this.info = info; setName(what); } public void run() { try { for(int i=0;i < count-1;i++) { info.show_string(id,i+": "+what); sleep(delay); // wait until next time } } catch (InterruptedException e) { return; // end this thread } info.show_string(id,(count-1)+": "+what+"\n"+what+" done"); } }All of the structure of the output is contained in the applet PingPongApplet.java
The only interesting part is the show_string method. It tests the ID of the calling thread and outputs accordingly. It also keeps a count of the number of times it is called with each ID (ping_count and pong_count so that these values can be displayed by the paint method.
public void show_string(int id, String str) { if (id == PINGID) { ping_count++; ping_clip.play(); output_common.append(str+":"+Thread.activeCount()+"\n"); output_1.append(str+":"+Thread.activeCount()+"\n"); } else if (id == PONGID) { pong_count++; pong_clip.play(); output_common.append(str+":"+Thread.activeCount()+"\n"); output_2.append(str+":"+Thread.activeCount()+"\n"); } repaint(1); }Here is the complete applet.
/* < Applet code = PingPongApplet width = 600 height = 400 > < /Applet > */ import java.applet.*; import java.awt.*; import java.awt.event.*; public class PingPongApplet extends Applet implements ActionListener, DisplayInfo { final int PINGID = 0; final int PONGID = 1; TextArea output_common; TextArea output_1; TextArea output_2; Button StartButton; PingPongThread ping; PingPongThread pong; AudioClip ping_clip; AudioClip pong_clip; int ping_count = 0; int pong_count = 0; public void init() { setBackground(Color.cyan); Panel p = new Panel(); setLayout(new BorderLayout()); p.setLayout(new GridLayout(1,3)); output_common = new TextArea(20,20); output_1 = new TextArea(20,20); output_2 = new TextArea(20,20); output_common.setBackground(Color.yellow); output_1.setBackground(Color.lightGray); output_2.setBackground(Color.lightGray); p.add(output_1); p.add(output_common); p.add(output_2); add("North",p); StartButton = new Button("Start"); StartButton.addActionListener(this); StartButton.setBackground(Color.pink); add("South",StartButton); ping_clip = getAudioClip(getCodeBase(),"ping.au"); pong_clip = getAudioClip(getCodeBase(),"pong.au"); } public void paint(Graphics g) { g.drawString("ping count = "+ping_count,10,330); g.drawString("pong count = "+pong_count,10,345); g.drawString("thread count = "+Thread.activeCount(),10,360); } public void stop() { StartButton.setLabel("Start"); if (ping != null) ping.suspend(); if (pong != null) pong.suspend(); } private void start_it() { if (ping == null) { ping = new PingPongThread(PINGID,"ping", 2000, 10, this); output_common.append("ping started\n"); output_1.append("ping started\n"); ping.start(); } else if (ping.isAlive()) { output_common.append("ping resumed\n"); output_1.append("ping resumed\n"); ping.resume(); } else { ping = new PingPongThread(PINGID,"ping", 2000, 10, this); output_common.append("ping restarted\n"); output_1.append("ping restarted\n"); ping.start(); } if (pong == null) { pong = new PingPongThread(PONGID,"PONG", 3000, 5, this); output_common.append("pong started\n"); output_1.append("pong started\n"); pong.start(); } else if (pong.isAlive()) { output_common.append("pong resumed\n"); output_2.append("pong resumed\n"); pong.resume(); } else { pong = new PingPongThread(PONGID,"PONG", 3000, 5, this); output_common.append("pong restarted\n"); output_2.append("pong restarted\n"); pong.start(); } } void suspend_it() { output_1.append("ping suspended\n"); output_2.append("PONG suspended\n"); ping.suspend(); pong.suspend(); } public void show_string(int id, String str) { if (id == PINGID) { ping_count++; ping_clip.play(); output_common.append(str+":"+Thread.activeCount()+"\n"); output_1.append(str+":"+Thread.activeCount()+"\n"); } else if (id == PONGID) { pong_count++; pong_clip.play(); output_common.append(str+":"+Thread.activeCount()+"\n"); output_2.append(str+":"+Thread.activeCount()+"\n"); } repaint(1); } public void actionPerformed(ActionEvent e) { if (e.getSource() == StartButton) { output_common.append("Number of threads: "+Thread.activeCount()+"\n"); if (StartButton.getLabel().equals("Start")) { StartButton.setLabel("Stop"); output_common.append("Start Button Pushed\n"); start_it(); } else if (StartButton.getLabel().equals("Stop")) { StartButton.setLabel("Start"); output_common.append("Stop Button Pushed\n"); suspend_it(); } } } }Click Here to run this applet.