Implementing Runnable Versus Extending Thread
Rather than inherit from Thread, a class can implement the interface java.lang.Runnable to allow a thread to be run within it. Runnable specifies that only one method be implemented:
public void run()
This is the same method signature that run() has in Thread. In fact, Thread also implements Runnable! Note that run() does not take any parameters, does not return anything, and does not declare that it throws any exceptions.
1、一个类实现Runnable接口就允许创建新线程在其内部运行run()方法中的statment
2、Thread也是实现Runnable接口
3、实现Runnable接口唯一要做的事情就是实现run()方法,注意此方法,无参数,无返回值
The Thread class has four constructors that take a Runnable object as a parameter:
public Thread(Runnable target)
public Thread(Runnable target, String name)
public Thread(ThreadGroup group, Runnable target)
public Thread(ThreadGroup group, Runnable target, String name)
Any instance of a class that implements the Runnable interface may be passed as the target to one of these constructors. When the Thread instance’s start() method is invoked, start() will start the new thread in the run() method of target rather than in Thread’s run() method. The Runnable to be used may be specified only at the time of a Thread’s construction; the Thread holds a reference to it in a private member variable.
实现了Runnable接口的类,有以上几种方法变为线程,然后调用Thread的start()方法执行类中的run()方法
SecondCounter.java
/*
* Created on 2005-7-7
*
* TODO To change the template for this generated file go to
* Window - Preferences - Java - Code Style - Code Templates
*/
package org.tju.msnrl.jonathan.thread.chapter1;
import javax.swing.*;
import java.awt.*;
import java.text.*;
/**
* @author Jonathan Q. Bo
*
* TODO To change the template for this generated type comment go to
* Window - Preferences - Java - Code Style - Code Templates
*/
public class SecondCounter extends JComponent implements Runnable {
private volatile boolean keepRunning;
private volatile int arcLen;
private volatile String timeMsg;
private Font paintFont;
public SecondCounter(){
this.keepRunning = true;
this.paintFont = new Font("SansSerif",Font.BOLD,14);
this.timeMsg = "never start!";
}
public void runClock(){
DecimalFormat df = new DecimalFormat("0.000");
long normalSleepTime = 100;
long nextSleepTime = normalSleepTime;
int counter = 0;
long startTime = System.currentTimeMillis();
this.keepRunning = true;
while(this.keepRunning){
try{
Thread.sleep(nextSleepTime);
}catch(InterruptedException e){
System.out.println("Interrupted Exception " + e.getMessage());
}
/*improve the accuracy*/
counter++;
double counterSec = counter / 10.0;
double elapseSec = (System.currentTimeMillis() - startTime)/1000.0;
double diffSec = counterSec - elapseSec;
nextSleepTime = normalSleepTime + (long)(diffSec*1000);
if(nextSleepTime < 0)
nextSleepTime = 0;
this.timeMsg = "passed time should be " + counterSec + ", in fact be " + elapseSec + ",the diff is " + diffSec;
this.arcLen = (((int)counterSec)%60) * 360 / 60;//角度
repaint();
}
}
public void stopClock(){
this.keepRunning = false;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
this.runClock();
}
public void paint(Graphics g){
g.setColor(Color.BLACK);
g.setFont(this.paintFont);
g.drawString(this.timeMsg,0,15);
g.fillOval(0,20,100,100);
g.setColor(Color.WHITE);
g.fillOval(3,23,94,94);
g.setColor(Color.BLUE);
g.fillArc(2,22,96,96,90,this.arcLen);
}
}
SecondCounterMain.java
/*
* Created on 2005-7-7
*
* TODO To change the template for this generated file go to
* Window - Preferences - Java - Code Style - Code Templates
*/
package org.tju.msnrl.jonathan.thread.chapter1;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
/**
* @author Administrator
*
* TODO To change the template for this generated type comment go to
* Window - Preferences - Java - Code Style - Code Templates
*/
public class SecondCounterMain extends JPanel {
private JButton startB;
private JButton stopB;
private SecondCounter sc;
public SecondCounterMain(){
sc = new SecondCounter();
startB = new JButton("start");
stopB = new JButton("stop");
startB.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e){
startB.setEnabled(false);
Thread countThread = new Thread(sc,"SecondCounter");
countThread.start();
stopB.setEnabled(true);
stopB.requestFocus();
}
}
);
stopB.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e){
stopB.setEnabled(false);
sc.stopClock();
startB.setEnabled(true);
startB.requestFocus();
}
}
);
JPanel innerButtonP = new JPanel();
innerButtonP.setLayout(new GridLayout(0,1,0,3));
innerButtonP.add(startB);
innerButtonP.add(stopB);
JPanel buttonP = new JPanel();
buttonP.setLayout(new BorderLayout());
buttonP.add(innerButtonP,BorderLayout.NORTH);
this.setLayout(new BorderLayout(10,10));
this.setBorder(new EmptyBorder(20,20,20,20));
this.add(buttonP,BorderLayout.WEST);
this.add(sc,BorderLayout.CENTER);
}
public static void main(String[] args) {
SecondCounterMain scm = new SecondCounterMain();
JFrame f = new JFrame("second counter");
f.setContentPane(scm);
f.setSize(620,200);
f.setVisible(true);
f.addWindowListener(
new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}
}
);
}
}
On lines 6, 8, and 9 of Listing 4.3, the modifier volatile is included for some of the member variables. By indicating that a member variable is volatile, you inform the JavaVM that its value might be changed by one thread while being used by another. In this case, one thread is checking keepRunning, and another thread will change its value to false some time after the timer is started. Under certain circumstances, if the variable was not marked as volatile, the while loop would not see the new value and would run the timer forever. This is an important detail often overlooked (even by experienced developers) and is discussed in detail in Chapter 7, “Concurrent Access to Objects and Variables.”
修饰符volatile含义:一个变量的值可以被一个线程修改,同时被其他线程使用,就像上面的例子,java虚拟机创建的awt消息响应处理线程可能修改keeprunning变量,而同时工作线程一直在检测此变量的值是否为真。如果不使用此修饰符,可能修改后的值永远不会被工作线程也就是本程序中的计时器检测到。这是一个很重要的细节。
Although not many statements exist in the while loop, it has been shown that over time, they cause the loop to run significantly more slowly than desired. To improve the accuracy, the sleep time should be varied based on the current system clock time. The final version of SecondCounter is simply called SecondCounter; its code appears in Listing 4.7.
A new local variable named nextSleepTime is used to vary the number of milliseconds to sleep each time through the loop (lines 24 and 32). The nextSleepTime value is recalculated based on the difference between the counter seconds and the system clock seconds (lines 42–45). If this value happens to be less than zero, zero is used instead because it’s impossible to sleep for a negative amount of time (lines 47–49).
Thread.sleep()并不是很准确的让线程停止一定时间,如何增强计时器的实时性?上面的例子给了一个有效的解决方案。检测时间差,动态改变线程休息时间。
Here are a few other lessons learned:
Do not use the event handling thread to perform long-running tasks; it should be allowed to return to the business of handling events relatively quickly. For the long tasks, use a worker thread. This is critical if a Stop or Cancel Request button exists and could be pressed before the original task completes.
Proper use of the volatile keyword is not trivial and is necessary in many cases to guarantee desired code execution behavior when two or more threads access the same member variable. Chapter 7 explains volatile in detail.
The amount of time it takes to execute even a few statements is not predictable. This time can become significant if the statements are executed over and over in a loop. When accuracy is important, you should check the system clock to see how much real time actually elapsed and then make adjustments accordingly.
通过学习本章我们应该知道:
1、不要在事件处理线程中作长时间的作业,而应该使用工作线程,最好有stop/cancel之类的按钮可以在任务完成前可以被执行
2、要正确使用volatile修饰符,这是保证预期代码行为的必要手段,尤其是在多线程访问同一变量的时候
3、还有就是执行任务的时间是不确定的,如果程序要求很高的精确度,就应该检测理论和现实的时间差,作相应的调整