你的位置:首页 > Java教程

[Java教程]Java语言导学笔记 Chapter 8 Thread


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接口。