// CS 1520 Spring 2010 // Multithreading demonstration using balls on the screen. In this handout, // the NewBall class extends Thread, since each NewBall needs to "move" on // its own. However, we don't want all of the balls to be requesting // repaints "in parallel", since it would overwork the computer and cause the // screen to flicker. Instead, we will have the main class be responsible // for repainting the screen at specific intervals. In order to do this, we // have the Bounce class implement the Runnable interface and attach a // Thread to it in the constructor. Note that this "refresher" thread will // run whether or not there are any NewBalls to actually paint. With a bit // more effort we can have the user start and stop this refresher thread (we // will do this in a future handout) import javax.swing.*; import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import java.util.*; public class Bounce extends JFrame implements Runnable { private static Color [] theColors = {Color.red, Color.blue, Color.magenta, Color.green, Color.gray}; private ShapePanel drawPanel; private JButton addBall, reset; private ArrayList theBalls; // Store balls in an ArrayList so that // we can draw them public Bounce() { super("Multithreading demonstration"); drawPanel = new ShapePanel(400, 300); // Set up the Panel and // the buttons for the addBall = new JButton("Add a Ball"); // user reset = new JButton("Reset Screen"); ButtonHandler bhandler = new ButtonHandler(); addBall.addActionListener(bhandler); reset.addActionListener(bhandler); theBalls = new ArrayList(); // Create ArrayList Container c = getContentPane(); c.add(drawPanel, BorderLayout.CENTER); c.add(addBall, BorderLayout.SOUTH); c.add(reset, BorderLayout.NORTH); Thread refresher = new Thread(this); // start thread that will refresher.start(); // redraw the screen. Note // that we are passing "this" to the thread, // meaning that the current object's run() method // will be executed within the thread. Note also // that it would be more efficient to only start // this thread if we have at least one ball on // the screen, since otherwise it is just spinning // without doing anything. This improvement is // made in BounceKey.java. setSize(400, 390); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } public void run() { while (true) // infinite loop -- runs until program ends { drawPanel.repaint(); // all we do here is repaint the try // panel, then sleep for a bit. { // Since the sleep() method could Thread.sleep(20); // throw InterruptedException (which } // is a checked exception) catch (InterruptedException e) {} // we must catch it } } public static void main(String [] args) { Bounce win = new Bounce(); } class ButtonHandler implements ActionListener { public void actionPerformed(ActionEvent e) { if (e.getSource() == addBall) // add a new NewBall to { // the ArrayList and start NewBall nextBall = new NewBall(); // its thread running. theBalls.add(nextBall); nextBall.start(); } else if (e.getSource() == reset) { for (int i = 0; i < theBalls.size(); i++) theBalls.get(i).deflate(); theBalls.clear(); // In the above code we "stop" all of the threads and then // remove the items from the ArrayList. Note that removing them // from the ArrayList is enough to keep them from being drawn, // but they would still be running, wasting CPU cycles. Also // note that we do not actually call the deprecated stop() // method. Instead we set a boolean value to cause the run() // method to terminate -- this is a much safer way of doing it } } } private class NewBall extends Thread { private RectangularShape thisBall; private Color thisColor; private int size, speed; private boolean inflated; private int deltaX, deltaY; public NewBall() // Create a new NewBall with { // a random size, speed,location, inflated = true; // direction and color. Note the size = 10 + (int) (Math.random() * 60); // "speed" is actually the delay speed = 10 + (int) (Math.random() * 100); // between moves in the run() int startx = (int) (Math.random() * 300); // method int starty = (int) (Math.random() * 200); deltaX = 2 + (int) (Math.random() * 10); if (Math.random() > 0.5) deltaX = -deltaX; deltaY = 2 + (int) (Math.random() * 10); if (Math.random() > 0.5) deltaY = -deltaY; int colind = (int) (Math.random() * 5); thisColor = theColors[colind]; thisBall = new Ellipse2D.Double(startx, starty, size, size); } public void deflate() { inflated = false; // this allows the thread to terminate gracefully, as } // can be seen in the run() method below. public void run() // This can only be executed once for a Thread. Once { // it terminates, the thread is "dead" and cannot be while (inflated) // restarted. If you want an object to last between { // starts, stops and restarts of a thread, you should try { // make it Runnable rather than Thread.sleep(speed); // have it extend Thread } catch (InterruptedException e) {} move(); //repaint(); // Why don't we want a repaint() here? Remove // the comment and run the program to see (note: // it may not be obvious in the observed // execution -- check the Task Manager) } } public void draw(Graphics2D g2d) { if (thisBall != null) { g2d.setColor(thisColor); g2d.fill(thisBall); } } private void move() { int oldx = (int) thisBall.getX(); int oldy = (int) thisBall.getY(); int newx = oldx + deltaX; if (newx + size > 400 || newx < 0) // A "bounce" simply involves deltaX = - deltaX; // reversing the direction of the int newy = oldy + deltaY; // ball when it intersects a if (newy + size > 300 || newy < 0) // border. deltaY = - deltaY; thisBall.setFrame(newx, newy, size, size); } } private class ShapePanel extends JPanel { private int prefwid, prefht; public ShapePanel (int pwid, int pht) { prefwid = pwid; prefht = pht; } public Dimension getPreferredSize() { return new Dimension(prefwid, prefht); } public void paintComponent (Graphics g) // draw all of the balls { // in the ArrayList super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; for (int i = 0; i < theBalls.size(); i++) theBalls.get(i).draw(g2d); } } }