8.1 什么是线程
def: 线程是程序内的一个单一的顺序控制流程
作为一个顺序的控制流程,线程必须在运行它的程序中占用一些资源。例如,线程必须有它自己的执行堆栈和程序计数器。在线程内运行的代码只在此上下文中工作。其他一些文章将线程称为执行上下文(execution context)。
如果你的程序必须反复执行一个任务,那么应该考虑使用java.util.Timer类。对于在一段程序后执行另一个任务,Timer类也很有用。
如果你正在编写一个具有图形的用户界面(GUI)的程序,那么应该使用javax.swing.Timer类。
对线程的基本支持由java.lang.Thread类提供。它提供了一个线程API并提供了线程的所有共有行为。这些行为包括启动、睡眠、运行、放弃和给予优先级。要使用Thread类实现线程,需要为它提供一个run方法,run方法实际执行线程的任务。
8.2 使用Timer和TimerTask类
Reminder.java这个例子使用计时器在一段延迟时间之后执行一个任务。
import java.util.Timer;
import java.util.TimerTask;
public class Reminder{
Timer timer;
public Reminder(int seconds){
timer = new Timer();
timer.schedule(new RemindTask(), seconds*1000);
}
class RemindTask extends TimerTask{
public void run(){
System.out.println("Time's up!");
timer.cancel(); //Terminate the timer thread
}
}
public static void main(String args[]){
new Reminder(5);
System.out.println("Task scheduled.");
}
}
实现和调度有计时器线程执行的任务的基本部分。
1、 实现一个定制的TimerTask子类。run方法包含执行任务的代码。
2、 通过实例化Timer类创建一个线程。
3、 实例化一个计时器任务对象(new RenmindTask())。
4、 调度这个计时器任务的执行。本例使用schedule方法。
让一个任务在特定时间执行:
import java.util.Timer;
import java.util.TimerTask;
import java.util.Calendar;
import java.util.Date;
public class Reminder{
Timer timer;
public Reminder(int seconds){
timer = new Timer();
timer.schedule(new ReminderTask(), seconds*1000);
}
public Reminder(){
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 11);
calendar.set(Calendar.MINUTE, 9);
calendar.set(Calendar.SECOND, 0);
Date time = calendar.getTime();
timer = new Timer();
timer.schedule(new RemindTask2(), time);
}
class ReminderTask extends TimerTask{
public void run(){
System.out.println("Time's up!");
timer.cancel(); //Terminate the timer thread
}
}
class RemindTask2 extends TimerTask{
public void run(){
System.out.println("Time's up! Ring on.");
timer.cancel(); //Terminate the timer thread
}
}
public static void main(String args[]){
new Reminder(5);
new Reminder();
System.out.println("Task scheduled.");
}
}
在默认情况下,只要程序的计时器线程在运行,程序就一直运行。有四种方式可以终止计时器线程。
1、 在计时器线程上调用cancel方法。
2、 使计时器成为“守护线程”(deamon),办法是这样创建计时器: new Timer(true) 。如果程序中仅剩下守护线程,那么程序退出。
3、 在计时器的被调度的所有任务都完成后,删除所有对Timer对象的引用。最后计时器的线程将退出。
4、 调用System.exit方法,使整个程序退出。
重复执行任务:
import java.util.Timer;
import java.util.TimerTask;
import java.awt.Toolkit;
/**
* Schedule a task that executes once every second.
*/
public class AnnoyingBeep {
Toolkit toolkit;
Timer timer;
public AnnoyingBeep() {
toolkit = Toolkit.getDefaultToolkit();
timer = new Timer();
timer.schedule(new RemindTask(),
0, //initial delay
1*1000); //subsequent rate
}
class RemindTask extends TimerTask {
int numWarningBeeps = 3;
public void run(){
if (numWarningBeeps > 0) {
toolkit.beep();
System.out.println("Beep!");
numWarningBeeps--;
} else {
toolkit.beep();
System.out.println("Time's up!");
//timer.cancel(); //Not necessary because we call System.exit
System.exit(0); //Stops the AWT thread (and everything else)
}
}
}
public static void main(String args[]) {
System.out.println("About to schedule task.");
new AnnoyingBeep();
System.out.println("Task scheduled.");
}
}
AnnoyingBeep程序使用schedule方法的三参数版本,指定它的任务应该马上开始执行,并且每1秒执行一次。下面是所有可用来调度任务反复执行的方法:
schedule(TimerTask task, long delay, long period)
schedule(TimerTask task, Date time, long period)
scheduleAtFixedRate(TimerTask task, long delay, long period)
scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
在调度任务反复执行时,如果执行的平滑性很重要,那么应该使用schedule方法之一,如果一次蜂鸣声因为某种原因推迟了,那么后续的所有的蜂鸣声将相应延迟;如果时间的同步更重要,那么应该使用scheduleAtFixedRate方法之一,如果想让程序在第一次蜂鸣后正好3秒退出,,如果一次蜂鸣声因为某种原因延迟了,那么两次蜂鸣的时间可能较近,小于1秒。
8.3 定制线程的run方法
有两种技术可以为线程提供run方法:
1、 对Thread类进行子类化并覆盖run方法
2、 实现Runnable接口
对Thread类进行子类化并覆盖run方法
Thread类本身是一个Runnable对象。
public class SimpleThread extends Thread{
public SimpleThread(String str){
super(str);
}
public void run(){
for(int i = 0; i < 10; i++){
System.out.println(i + " " + getName());
try{
sleep((int)(Math.random()*1000));
}catch(InterruptedException e){}
}
System.out.println("Done! " + getName());
}
}
第一个方法是构造器,调用超类构造器设置线程名称。
public class TwoThreadDemo{
public static void main(String[] args){
new SimpleThread("Jamaica").start();
new SimpleThread("Fiji").start();
}
}
实现Runnable接口
import java.awt.Graphics;
import java.util.*;
import java.text.DateFormat;
import java.applet.Applet;
public class Clock extends Applet implements Runnable {
private Thread clockThread = null;
public void start(){
if(clockThread == null){
clockThread = new Thread(this, "Clock");
clockThread.start();
}
}
public void run(){
Thread myThread = Thread.currentThread();
while(clockThread == myThread){
repaint();
try{
Thread.sleep(1000);
}catch(InterruptedException e){
//the VM doesn't want us to sleep anymore;
//so get back to work
}
}
}
public void paint(Graphics g){
//get the time and convert it to a date
Calendar cal = Calendar.getInstance();
Date date = cal.getTime();
//format it and display it
DateFormat dateFormatter = DateFormat.getTimeInstance();
g.drawString(dateFormatter.format(date), 5, 10);
}
//overrides Applet's stop method, not Thread's
public void stop(){
clockThread = null;
}
}
<html>
<head>
<title>Clock</title>
</head>
<body>
I'm now listening to OuRuoLa
<APPLET CODE= "Clock.class" WIDTH=150 HEIGHT=35></APPLET>
</body>
</html>
这个Clock applet 显示当前时间并且每1秒更新一次。问题是会间接的闪烁,或许和CPU的工作状态有关。用appletviewer Clock.html查看。
Clock applet的run方法进行循环,知道浏览器要求他停止。在循环的每次迭代期间,时钟重新绘制它的显示。
如果你的泪必须子类化另一个类,那么应该使用Runnable接口。
8.4 线程的生存周期
1、创建线程
clockThread = new Thread(this, "Clock"); 执行后Clock applet处于”新线程”状态。此时线程仅仅是一个空的Thread对象,这时只能启动线程,调用除start方法外的任何方法都是无意义的,而且会导致IllegalThreadStateException。实际上,只要在线程上调用一个方法,而此线程的状态不允许此方法调用,那么运行时系统都会抛出IllegalThreadStateException。
2、启动线程
clockThread.start(); start方法分配线程所需的系统资源,调度线程运行,并调用线程的run方法。
start方法返回后,线程处于“正在运行”状态。实际情况要更复杂。如果计算机只有一个处理器,这样就不可能同时运行所有”正在运行“的线程。Java运行时环境必须实现一个调度方案,以便在所有”正在运行“的线程分享处理器。所以在任何给定时刻,”正在运行”的线程可能正在等待轮到它使用CPU。
public void run(){
Thread myThread = Thread.currentThread();
while(clockThread == myThread){
repaint();
try{
Thread.sleep(1000);
}catch(InterruptedException e){
//the VM doesn't want us to sleep anymore;
//so get back to work
}
}
}
Clock的run方法在条件clockThread == myThread成立时一直循环。它使线程和applet平缓地退出。在循环中,applet重新绘制本身,然后让线程睡眠1000毫秒。
1、 使线程不可运行
当以下事件之一发生时,线程变成“不可运行”状态:
它的sleep方法被调用;
线程调用wait方法等待某个条件得到满足;
线程因I/O而阻塞。
对于每个进入“不可运行“状态的入口,有一个特定的不同出口将线程返回到”可运行“状态。
如果一个线程已经进入睡眠状态,那么必须经过指定的睡眠时间(毫秒数);
如果一个线程正在等待某个条件,那么另一个对象必须通过调用notify或notifyAll告知正在等待的线程条件发生了改变;
如果一个线程因I/O而阻塞,那么I/O必须完成。
2、 停止线程
自然消亡;
while(clockThread == myThread)这个条件表示,当前执行的线程不等于clockThread时循环退出。当你离开页面时,运行此applet的应用程序调用applet的stop方法。然后,这个方法设置clockThread为null,由此让主循环终止run方法。如果重新访问这个页面,那么再次调用start方法,时钟在一个新线程中再次启动。即使停止和启动applet的时间快于循环一次的迭代,clockThread线程也将不同于myThread,循环将终止。
3、 isAlive方法
返回false,线程处于”新线程“状态,或者已经消亡。返回true,线程处于”可运行“或者”不可运行“。