We will create and run two threads in the next program. One of them will operate a Pi generator with a really cool algorithm that can generate Pi to an unlimited number of digits one at a time. The other thread will be the thread that runs inside a ClockPanel1. PiPanel1 (example 16.8) creates a window with a ClockPanel1 at the top, a JTextArea in the center that displays Pi continuously appending the latest-generated digits, and a JButton at the bottom that can pause or play the Pi-generation. There is some significant code in PiPanel dealing with displaying Pi and scrolling the JTextArea as necessary so the latest digits are always in view. This code has nothing to do with threads and, for this chapter’s purposes, you may ignore it. On the other hand it might give you some insight into the handling of JTextAreas, JScrollPanes and JScrollBars, so it’s your choice. The PiSpigot class that generates Pi (example 16.9) is also not relevant to threads but it’s based on such an incredible algorithm that we will discuss it at the end of this chapter for readers that are interested.
Example 16.8: chap16.pi.PiPanel1.java
1 package chap16.pi; 2 3 import java.awt.BorderLayout; 4 import java.awt.Font; 5 import java.awt.event.ActionEvent; 6 import java.awt.event.ActionListener; 7 8 import javax.swing.JButton; 9 import javax.swing.JFrame; 10 import javax.swing.JPanel; 11 import javax.swing.JScrollBar; 12 import javax.swing.JScrollPane; 13 import javax.swing.JTextArea; 14 import javax.swing.SwingConstants; 15 import javax.swing.SwingUtilities; 16 17 public class PiPanel1 extends JPanel implements ActionListener { 18 private JTextArea textArea; 19 private JButton spigotButton; 20 private PiSpigot spigot; 21 private int numDigits = 0; 22 private JScrollBar vScrollBar; 23 private JScrollPane scrollPane; 24 25 private boolean paused = true; 26 27 /* producerThread is protected so that a subclass can reference it. */ 28 protected Thread producerThread; 29 30 /* buttonPanel is protected so that a subclass can reference it. */ 31 protected final JPanel buttonPanel; 32 33 public PiPanel1() { 34 setLayout(new BorderLayout()); 35 36 textArea = new JTextArea(20, 123); 37 textArea.setFont(new Font("Monospaced", Font.PLAIN, 12)); 38 textArea.setEditable(false); 39 scrollPane = new JScrollPane(textArea); 40 scrollPane.setHorizontalScrollBarPolicy( 41 JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 42 scrollPane.setVerticalScrollBarPolicy( 43 JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); 44 add(scrollPane, BorderLayout.CENTER); 45 46 vScrollBar = scrollPane.getVerticalScrollBar(); 47 48 spigotButton = new JButton("Play"); 49 spigotButton.addActionListener(this); 50 buttonPanel = new JPanel(new BorderLayout()); 51 buttonPanel.add(spigotButton, BorderLayout.CENTER); 52 add(buttonPanel, BorderLayout.SOUTH); 53 54 spigot = new PiSpigot(); 55 startProducer(); 56 } 57 58 /* Thread-related methods */ 59 private void startProducer() { 60 producerThread = new Thread() { 61 public void run() { 62 while (true) { 63 Thread.yield(); 64 while (paused) { 65 pauseImpl(); 66 } 67 int digit = spigot.nextDigit(); 68 handleDigit(digit); 69 } 70 } 71 }; 72 producerThread.start(); 73 } 74 75 /* pauseImpl will be implemented differently in a future subclass */ 76 protected void pauseImpl() { 77 try { 78 Thread.sleep(Long.MAX_VALUE); 79 } catch (InterruptedException e) {} 80 } 81 private void play() { 82 paused = false; 83 playImpl(); 84 } 85 /* playImpl will be implemented differently in a future subclass */ 86 protected void playImpl() { 87 producerThread.interrupt(); 88 } 89 private void pause() { 90 paused = true; 91 } 92 93 /* Play/Pause Button */ 94 public void actionPerformed(ActionEvent e) { 95 String action = spigotButton.getText(); 96 if ("Play".equals(action)) { 97 play(); 98 spigotButton.setText("Pause"); 99 } else { 100 pause(); 101 spigotButton.setText("Play"); 102 } 103 } 104 105 /* GUI-related methods */ 106 protected void showLastLine() { 107 /* 108 * These swing conponent methods are not thread-safe 109 * so we use SwingUtilities.invokeLater 110 */ 111 SwingUtilities.invokeLater(new Runnable() { 112 public void run() { 113 JScrollBar vScrollBar = scrollPane.getVerticalScrollBar(); 114 int numLines = textArea.getLineCount(); 115 int lineHeight = 116 textArea.getScrollableUnitIncrement( 117 scrollPane.getViewport().getViewRect(), 118 SwingConstants.VERTICAL, 119 -1); 120 int visibleHeight = vScrollBar.getVisibleAmount(); 121 int numVisibleLines = visibleHeight / lineHeight; 122 int scroll = (numLines - numVisibleLines) * lineHeight; 123 vScrollBar.setValue(scroll); 124 } 125 }); 126 } 127 protected void displayDigit(int digit) { 128 if (numDigits == 1) { 129 textArea.append("."); 130 } 131 textArea.append(String.valueOf(digit)); 132 numDigits++; 133 if (numDigits > 1) { 134 if ((numDigits - 1) % 100 == 0) { 135 textArea.append("\n "); //JTextArea.append is thread-safe 136 showLastLine(); 137 } else if ((numDigits - 1) % 5 == 0) { 138 textArea.append(" "); //JTextArea.append is thread-safe 139 } 140 } 141 } 142 /* handleDigit will be implemented differently in a future subclass */ 143 protected void handleDigit(int digit) { 144 displayDigit(digit); 145 } 146 public static void main(String[] arg) { 147 JFrame f = new JFrame(); 148 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 149 f.getContentPane().add(new PiPanel1(), BorderLayout.CENTER); 150 f.getContentPane().add(new chap16.clock.ClockPanel1(), BorderLayout.NORTH); 151 f.pack(); 152 f.show(); 153 } 154 }
Example 16.9: chap16.pi.PiSpigot.java
1 package chap16.pi; 2 3 import java.math.BigInteger; 4 5 public class PiSpigot { 6 private static final BigInteger zero = BigInteger.ZERO; 7 private static final BigInteger one = BigInteger.ONE; 8 private static final BigInteger two = BigInteger.valueOf(2); 9 private static final BigInteger three = BigInteger.valueOf(3); 10 private static final BigInteger four = BigInteger.valueOf(4); 11 private static final BigInteger ten = BigInteger.valueOf(10); 12 13 private BigInteger q = one; 14 private BigInteger r = zero; 15 private BigInteger t = one; 16 private BigInteger k = one; 17 private BigInteger n = zero; 18 19 public int nextDigit() { 20 while (true) { 21 n = three.multiply(q).add(r).divide(t); 22 if (four.multiply(q).add(r).divide(t).equals(n)) { 23 q = ten.multiply(q); 24 r = ten.multiply(r.subtract(n.multiply(t))); 25 return n.intValue(); 26 } else { 27 BigInteger temp_q = q.multiply(k); 28 BigInteger temp_2k_plus_1 = two.multiply(k).add(one); 30 BigInteger temp_t = t.multiply(temp_2k_plus_1); 29 BigInteger temp_r = two.multiply(q).add(r).multiply(temp_2k_plus_1); 31 BigInteger temp_k = k.add(one); 32 q = temp_q; 33 r = temp_r; 34 t = temp_t; 35 k = temp_k; 36 } 37 } 38 } 39 }
Use the following commands to compile and execute the example. From the directory containing the src folder:
javac –d classes -sourcepath src src/chap16/pi/PiPanel1.java java –cp classes chap16.pi.PiPanel1
After PiPanel1’s constructor assembles the GUI, it calls startProducer() (line 55) which creates a Thread whose run() method is an infinite digit-producing loop. Every time it goes through the body of the loop, it politely calls Thread.yield() (line 63). Calling Thread.yield() allows other threads that may have been waiting a chance to execute, possibly causing the current thread to pause its own execution. If no threads were waiting, the current thread just continues. Since digit producing is CPU intensive and because this loop never ends, calling Thread.yield() is more than just polite. It’s the right thing to do. Table 16-6 lists Thread’s yield() method.
Method Name and Purpose |
---|
public static void yield() Allows other threads that may have been waiting a chance to execute, possibly causing the current thread to pause its own execution. |
Let’s discuss the logic of the producer thread’s run() method. (See lines 61 - 70 of example 16-8). After calling thread.yield(), the loop’s next action depends on the value of the boolean variable, paused. If paused is false, the loop produces another digit, calls handleDigit() and repeats. If paused is true, the thread enters the while(paused) loop which calls pauseImpl() causing the thread to sleep. Unless it is interrupted, the producer thread will exit sleep(Long.MAX_VALUE) only after approximately 300 million years. If it does sleep this long, the while(paused) loop will notice that paused is still true and will send the thread back to sleep again. I think it’s more likely that the producer thread will exit sleep by being interrupted. How about you?
If the “Play” button is pressed while the producer is sleeping, the play() method will set paused to false and call playImpl() which wakes the producerThread up by calling producerThread.interrupt(). This will cause the while(paused) loop to exit since paused is no longer true, and digit-producing to resume. If, on the other hand, the “Pause” button is pressed while the producer is running, the producer thread will enter the while(paused) loop automatically on the very next digit-producing iteration and begin sleeping. This use of sleep() and interrupt() is somewhat unconventional. Later in the chapter, we will discuss a more appropriate mechanism for achieving the same results.
The sleep() and interrupt() methods can be used as a mechanism for regulating a thread’s use of the CPU. If a thread’s run() method is time consuming, it should call yield() periodically to allow other threads a chance to run.