您的位置: 网站首页 > 程序开发 > Java程序设计 > 第9章 多线程 > 【9.2 建立Java线程】

9.2 建立Java线程

 

9.2  建立Java线程

Java提供了类java.lang.Thread 来方便多线程编程,这个类提供了大量的方法来方便我们控制自己的各个线程。除此之外,通过Java的计时器Timer能够实现简单的任务线程,而且Runnable接口在无法继承Thread类的情况下提供另外一种实现多线程的方法。下面将分别介绍它们。

9.2.1  计时器

java.util.Timer java.util.TimerTask可以使开发人员很容易实现简单的任务。在JDK1.3引入这两个类之前,开发人员必须编写自己的调度程序,这需要花费很大精力来处理线程和复杂的Object.wait( )方法。不过,Java计时器没有足够的能力来满足许多应用程序的计划要求。

下面的例子是利用Timer在一段时间后启动一个线程。

首先定义一个类RemindTask,这个类继承自TimerTask类,并且覆盖了run( )方法,在run( )方法中编写我们要做的事情,在这个例子中即打印“Time’s up!”,然后调用cancel( )方法取消计时器。在Reminder类的构造函数中创建一个Timer的对象,然后调用schedule( )方法,如下所示。

timer.schedule(new RemindTask(), seconds*1000);

它的作用是在seconds*1000毫秒后启动任务:new RemindTask( )

【例9-1本例将在运行5秒后打印出“Time’s up!”。

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();

        }

    }

 

    public static void main(String args[]) {

        new Reminder(5);

        System.out.println("Task scheduled.");

    }

}

9-2中的程序将每1秒钟重复做一件事情,即打印警告信息“Alert!”,重复3次后打印“Time’s up!”,并且退出程序,注意schedule( )方法的调用参数,如下所示:

timer.schedule(new RemindTask(), 0, 1*1000);

【例9-2本例每隔1*1000毫秒调用RemindTask线程的run( )方法。

import java.util.*;

public class Alert {

    Timer timer;

    public Alert() {

        timer = new Timer();

        timer.schedule(new RemindTask(),

               0,

               1*1000);

    }

    class RemindTask extends TimerTask {

    int numWarning = 3;

        public void run() {

         if (numWarning > 0) {

             System.out.println("Alert!");

             numWarning--;

         } else {

                  System.out.println("Time's up!");

                 System.exit(0);

         }

        }

    }

    public static void main(String args[]){

    new Alert();

    }

  

}

程序的输出如下:

Alert!

Alert!

Alert!

Time's up!

9.2.2  Thread

如果要做的不是一个简单的重复作业,则Timer就显得力不从心了。这时java.lang. Thread类将派上用场。下面介绍Thread类的使用。

1.创建线程

任何顺序执行的程序都有开头、结尾以及一系列执行语句。线程也与这种程序类似,所不同的是:线程本身不能运行,它只能用于程序中。线程就是程序中单独顺序的流控制。

Java提供Thread类来创建线程。下面是创建启动一个线程的语句。

Thread thread1=new Thread();

thread1.start();

因此,我们可以通过Thread( )构造方法创建一个线程,并启动该线程。事实上,启动线程将启动线程的run( )方法,而Thread类中的run( )方法没有任何操作语句,所以这个线程没有任何操作。要使线程实现预定功能,必须定义自己的run( )方法。Java中通常有以下两种方式定义run( )方法。

1)通过定义一个Thread类的子类,在该子类中覆盖run( )方法。Thread子类的实例对象就是一个线程,显然,该线程有我们自己设计的线程体run( )方法,启动线程就启动了子类中重写的run( )方法。

2)通过Runnable接口,在该接口中定义run( )方法的接口。接口跟类非常类似,主要用来实现特殊功能,如复杂关系的多重继承功能。在此,我们定义一个实现Runnable( )接口的类,在该类中定义自己的run( )方法,然后以该类的实例对象为参数调用Thread类的构造方法来创建一个线程。

线程的所有活动都是通过线程run( )方法来实现的。在一个线程被建立并初始化以后,Java的运行系统就自动调用run( )方法,正是通过run( )方法才使得建立线程的目的得以实现

下面的程序说明了如何通过继承Thread类实现线程。

【例9-3在本例中,myThread类继承了Thread类并覆盖了Thread类的run( )方法,在main( )方法中生成myThread的实例,并调用start( )方法启动这个线程。完整的代码如下。

class myThread extends Thread {

    public void run() {

        for (int i = 0; i < 10; i++) {

            System.out.println("Hello" + i);

        }

    }

}

public class threadSample{

    public static void main(String args[]){

        myThread thread = new myThread();

        thread.start();

    }

}

这个程序的输出如下:

Hello0

Hello1

Hello2

Hello3

Hello4

Hello5

Hello6

Hello7

Hello8

Hello9

下面再实现一个稍微复杂一点的例子。

【例9-4在本例中,myThread类的run( )方法打印一些信息并且进入睡眠,睡眠时间是随机的。在main( )方法中将创建两个myThread类的对象并分别启动它们。代码如下:

class myThread extends Thread {

myThread(String str){

        super(str);

       }

public void run() {

        for (int i = 0; i < 10; i++) {

            System.out.println(getName() + " : Hello" + i);

            try {

                sleep((int)(Math.random() * 1000));

            } catch (InterruptedException e) {}

        }

}

}

public class multiThreadSample{

public static void main(String args[]){

        myThread thread1 = new myThread("Thread 1");

        myThread thread2 = new myThread("Thread 2");

        thread1.start();

        thread2.start();

}

}

2.线程命名

每个线程体都有一个名字,Thread默认的名称是一个短线连字符和一个零开始的数字符号。如果不接受Java的默认线程名称,则可以选择使用自定义的名称。为了能够自定义名称,Thread类提供带有name参数的构造函数。在例9-4multiThreadSample中,对线程体的构造如下。

myThread thread1 = new myThread("Thread 1");

myThread thread2 = new myThread("Thread 2");

我们将myThread的对象thread1thread2分别命名为“Thread 1”和“Thread 2”。除此之外,Thread类还提供了方法setName( )用于设置一个线程的名字,以及getName( )方法返回当前名称。例如:

System.out.println(getName() + " : Hello" + i);

线程命名最大的作用在于程序调试。

3.线程状态

在例9-4multiThreadSample.java中,首先创建一个Thread的对象,如下所示:

myThread thread1 = new myThread("Thread 1");

在启动这个线程之前,这个线程处于创建状态,它仅仅是一个空的线程对象,系统不为它分配资源。处于这种状态时只能启动或终止该线程,调用除这两种以外的其他方法都会失败并且会引起非法状态处理。

之后我们进行如下调用。

thread1.start();

其中,start( )方法启动了运行这个线程所需的系统资源安排其运行,并调用线程体的run( )方法,这样就使得该线程处于可运行( Runnable )状态。需要注意的是:这一状态并不是运行中状态(Running ),因为线程也许并未真正运行。由于很多计算机都是单处理器的,所以要在同一时刻运行所有的处于可运行状态的线程是不可能的,Java的运行系统必须实现调度来保证这些线程共享处理器。处于可运行状态的线程不一定正在运行,但是在大多数情况下,处于可运行状态的线程是在运行中的。当一个线程正在运行时,它肯定处于运行状态,并且也是当前正在运行的线程。

当下面4种情况的其中一种发生时,线程就进入不可运行状态:调用了sleep( )方法;调用了suspend( )方法;为等候一个条件变量,线程调用wait( )方法;输入输出流中发生线程阻塞。

例如,在例9-4multiThreadSample.java中,我们在run( )方法中进行了sleep( )调用:

try {

sleep((int)(Math.random() * 1000));

} catch (InterruptedException e) {}

从这个例子中可以看到,通过调用sleep( )方法使得myThread线程休止了(int)(Math. random( )* 1000)毫秒,这时即使处理器空闲,也不能执行该线程。sleep( )调用结束以后,myThread又成为可运行的了。

对于上4种情况,Java都有特定的返回可运行状态的方法与之对应,如下所述,执suspend( )后,如果线程处于活动状态则被动挂起,且不再有进一步的活动,除非重新开始

如果线程处于睡眠状态中,sleep( )方法中的参数为休息时间,当这个时间过去后,线程即为可运行的。

如果线程在等待条件变量,那么要停止等待的话,需要该条件变量所在的对象调用notify( )notifyAll( )方法;

如果在I/O流中发生线程阻塞,则特定的I/O指令将结束这种不可运行状态。

线程的终止一般可通过两种方法实现:自然撤销或是被停止。

自然撤销是指从线程的run( )方法正常退出,而调用stop( )方法也可以停止当前线程。

Thread类的程序接口中提供了isAlive( )方法,如果线程已被启动并且未被终止,那么isAlive( )返回true;如果返回false,则该线程是新创建或是已被终止。而且,若返回true,则该线程可以是可运行的,也可以是不可运行的。也就是说,我们不能通过isAlive( )方法的返回值判断这个线程是处于可运行状态还是处于不可运行状态。

4Join( )方法

有些情况下,当新创建一个线程并启动它之后,希望等待这个线程结束之后再作其他事情。

【例9-5本例中,线程的run( )函数的功能是打印5行字符,而main( )函数希望在线程打印完5字符之后打印“Thread finished”的信息。程序代码如下。

class myThread extends Thread {

public void run() {

        for (int i = 0; i < 5; i++) {

            System.out.println("Hello" + i);

         }

      }

}

public class threadSample2{

      public static void main(String args[]){

        myThread thread = new myThread();

        thread.start();

        System.out.println("Thread finished.");

      }

}

但是运行这个程序的结果却和设想不一样,一个可能的输出是:

Thread finished.

Hello0

Hello1

Hello2

Hello3

Hello4

出现这种情况的原因是:程序中的main( )函数执行thread.start( )后马上就执行System. out.println("Thread finished."),而这时thread线程可能尚未执行完毕。

【例9-6为了正确打印信息,我们可以通过不停地调用isAlive( )方法来判断线程是否结束,只有结束后才继续main( )函数的工作。因此,改写上面的程序代码如下:

class myThread extends Thread {

public void run() {

        for (int i = 0; i < 5; i++) {

            System.out.println("Hello" + i);

      }

    }

}

public class threadSample2{

public static void main(String args[]){

        myThread thread = new myThread();

       

        thread.start();

        while(thread.isAlive())

            try{

                Thread.sleep(10);

            }catch (InterruptedException e){};

           

        System.out.println("Thread finished.");

}

}

main( )函数循环调用isAlive( )方法判断thread的状态,如果线程thread函数还没结束,则主线程调用Thread.sleep(10)休眠10毫秒。可以看到,这次打印的信息是正确的。

事实上,while循环、isAlive( )方法和sleep( )方法结合起来是很有用的。另外,Java语言提供了join( )方法用于等待一个线程的停止。join( )方法有3个形式:join()join(long millis)join(long millis, int nanos)。后两个join( )方法在等待线程停止的过程中加入了超时的概念,如果超过参数所指定的时间,线程还没退出,则join( )方法返回到调用join( )的线程继续。因此,再改写上面的程序代码如下:

class myThread extends Thread {

public void run() {

        for (int i = 0; i < 5; i++) {

            System.out.println("Hello" + i);

        }

}

}

public class threadSample3{

public static void main(String args[]){

        myThread thread = new myThread();

        thread.start();

        try{

            thread.join();

        }catch (InterruptedException e){};

            System.out.println("Thread finished.");

}

}

5.访问线程

Thread类提供了一个静态方法currentThread( ),可以用于返回正在运行的线程体引用。这个方法有时候是很重要的。在例9-7中,myThread线程体的run( )方法调用foo类的do Something( )方法打印一些信息,这些信息包括调用线程的名字,但是foo类中并没有保存对线程体的引用,因此只能通过Thread.currentThread( ).getName( )得到调用线程的名字。

【例9-7本例只是简单地说明了currentThread( )方法的必要性,因此只是调用get Name( )得到当前运行线程的名字。事实上,在实际应用中,调用currentThread( )方法得到当前线程后可用于其他很多目的。

class foo{

      static void doSomething(){

        for (int i = 0; i < 10; i++)

            System.out.println(Thread.currentThread().getName() + " : Hello" + i);

}

}

class myThread extends Thread {

      public void run() {

        foo.doSomething();

       }

}

public class ThreadAccess{

       public static void main(String args[]){

        myThread thread1 = new myThread();

        myThread thread2 = new myThread();

        thread1.start();

        thread2.start();

      }

}

【例9-8Thread类还提供了静态方法activeCount( )enumerate( ),分别用于得到当前同一线程组中的活动线程数目和这些线程的副本。本例程序代码如下:

class myThread extends Thread {

      public void run() {

        try{

            sleep(5000);

        }catch(Exception e){};

      }

}

public class ThreadAccess2{

      blic static void main(String args[]){

        myThread thread1 = new myThread();

        myThread thread2 = new myThread();

        thread1.start();

        thread2.start();

            int n = Thread.activeCount();

        System.out.println("Active threads count: " + n);

        Thread tarray[] = new Thread[Thread.activeCount()];

        Thread.enumerate(tarray);

        System.out.println("Active threads: ");    

        for(int i=0; i< tarray.length; i++)

            System.out.println(tarray[i].getName());

     

}

这个程序的输出如下:

Active threads count: 3

Active threads:

main

Thread-0

Thread-1

需要注意的是:在一般情况下,当重申一个数组时不要依赖activeCount( )的返回值。如果这样做,程序将存在抛出一个NullPointerException对象的风险。由于在调用active Count( )enumerate(Thread[ ] thdarray)之间,一个或更多线程可能结束,此时,enumerate (Thread[ ] thdarray)能够复制少数线程引用进它的数组。因此,仅考虑将activeCount( )的返回值作为数组可能大小的最大值;同样,考虑将enumerate(Thread [ ] thdarray)的返回值作为在一个程序对此方法调用时活跃线程的数目。

9.2.3  Runnable接口

前面介绍了通过继承Thread类来引入多线程,然而,有些情况下却不能够这么做。Java语言不允许一个类继承多个类,如果某个类已经声明成继承其他类,那么它将不可以继承Thread类。幸运的是,Java的设计者已经意识到不可能创建Thread子类的情形总是会发生的,由此产生了java.lang.Runnable接口和带Runnable参数的Thread构造器,如Thread(Runn-

able target)

Runnable接口声明了唯一的方法:void run( )。这个方法和Threadrun( )方法一样作为线程的执行入口服务。因为Runnable是一个接口,任何类都能通过将一个implements子句包含进类头和提供一个适当的run( )方法实现接口。在执行期间,程序代码能从这个类创建一个对象或runnable,并将runnable的引用传递给一个适当的Thread构造器。构造器和Thread对象一起存贮这个引用,并确保一个新线程在调用Thread对象的start( )方法后调用runnablerun( )方法。

abstract class foo {

      abstract void printInfo();

}

class MyRunnable extends foo implements Runnable {

      void printInfo(){

          for (int i=0; i<5; i++) {

          System.out.println(Thread.currentThread().getName() + " : Hello" + i);

            try {

                Thread.sleep(500);

            } catch (InterruptedException e) { e.printStackTrace(); }

          }

      }

      public void run () {

        printInfo();

      }

}

public class RunnableDemo{

        public static void main(String args[]){

        MyRunnable thread1 = new MyRunnable();

        MyRunnable thread2 = new MyRunnable();

        new Thread(thread1).start();

        new Thread(thread2).start();

      }

}

RunnableDemo由类RunnableDemofooMyRunnable组成。类RunnableDemo通过创建一个MyRunnable对象驱动应用程序。fooMyRunnable组成了一个基于foo层次的类。foo是抽象的,因为它提供一个抽象的printInfo( )方法。MyRunnable类扩展foo类。由于MyRunnable继承了foo类,因此它不能同时继承Thread类,因此只能通过使之实现Runnable接口来实现多线程。在RunnableDemo类的main( )方法中创建了两个MyRunnable的对象thread1thread2,并且把它们绑定到Thread类的对象上,并调用Threadstart( )方法启动线程,如下所示:

MyRunnable thread1 = new MyRunnable();

MyRunnable thread2 = new MyRunnable();

new Thread(thread1).start();

new Thread(thread2).start();