您的位置: 网站首页 > 程序开发 > Java程序设计 > 第9章 多线程 > 【9.4 线程优先级】

9.4 线程优先级

 

9.4  线程优先级

多线程程序设计涉及到的一个问题是线程的优先级。线程优先级的大小决定了线程获取CPU时间的机会。Java语言提供了设计线程优先级的方法。

9.4.1  线程优先级和线程调度

线程调度有两种方式:抢占式和非抢占式。抢占式系统在任何给定的时间内将运行最高优先级的线程,系统中的所有线程都有自己的优先级。

线程的优先级(Priority)决定该线程的重要程度,即线程运行的顺序以及从处理器中获得的时间数量如何。

Java线程的优先级可由用户来设置,它是由一个整数表示的,这个整数越大,则线程的优先级越大。Java优先级的范围在Thread.MIN_PRIORITYThread.MAX_PRIORITY之间。默认情况下线程的优先级是Thread.NORM_PRIORITYThread类提供了setPriority( )getPriority( )方法来设置和读取优先级。

Java虚拟机是抢占式的,它能保证运行优先级最高的线程。在Java虚拟机中,如果把一个线程的优先级改为最高,那么它将取代当前正在运行的线程,除非这个线程结束运行或者进入休眠状态,否则将一直占用所有的处理器的时间。

【例9-9 本例是一个模仿赛跑的程序。Runner类的run( )方法中对自身的私有成员step做自增的操作,然后进入睡眠1毫秒。Race 类的main( )方法中创建了Runner类的两个对象runner[0]runner[1],分别把它们的优先级设置为Thread.MIN_PRIORITYThread.MAX_ PRIORITY,然后调用start( )方法启动它们。main( )方法每隔5秒钟就查询一下这两个对象的step值,然后分别打印这两个对象的step值。

可以看到,这个赛跑程序是不公平的,因为runner[1]的优先级比较高,所以它获取CPU的机会就大,可以预料,在一段时间后,runner[1]step值将比runner[0]step值大。完整的程序代码如下:

class Runner extends Thread {

    private int step = 0;

        public void run() {

        while(step < 50000){

            step ++;

            try{

                Thread.sleep(1);

            }catch(Exception e){};

        }

    }

    public int getStep(){

        return step;

    }

}

public class Race{

    public static void main(String args[]){

        Runner runner[] = {new Runner(),

                    new Runner()};

            runner[0].setPriority(Thread.MIN_PRIORITY);

        runner[1].setPriority(Thread.MAX_PRIORITY);

            runner[0].start();

        runner[1].start();

            while(runner[0].isAlive() && runner[1].isAlive()){

            try{

                Thread.sleep(5000);

            }catch(Exception e){};

                System.out.println("Runner 0: " + runner[0].getStep());

            System.out.println("Runner 1: " + runner[1].getStep());

        }

    }

}

运行这个程序,可以看到输出如下:

Runner 0: 3664

Runner 1: 4976

Runner 0: 7601

Runner 1: 9958

Runner 0: 11541

Runner 1: 14939

Runner 0: 15457

Runner 1: 19928

Runner 0: 19376

Runner 1: 24909

Runner 0: 23290

Runner 1: 29899

如果遇到两个优先级相同的线程,调度器将以轮转调度(Round-Robin)算法选择一个线程运行。这个被选择的线程将一直运行直至以下某个条件为真:

·    一个具有更高线程优先级的线程进入可运行状态。

·      这个线程结束运行。

·    在分时操作系统上,时间片到期。分时操作系统把CPU时间分成一个一个的时间片,操作系统轮流地把每个时间片分给各个并发线程,每个线程一次只能运行一个时间片。当时间片计数到时后,系统选择另一个线程并分给它时间片,让其投入运行,如此循环反复。

当这些条件中的某个为真时,另外一个线程将有机会得到CPU资源而运行。

【例9-10本例说明了这种情况,runner[0]runner[1]的优先级都设为2,代码如下:

class Runner extends Thread {

    private int step = 0;

        public void run() {

        while(step < 50000){

            step ++;

            try{

                Thread.sleep(1);

            }catch(Exception e){};

        }

    }

    public int getStep(){

        return step;

    }

}

public class Race{

public static void main(String args[]){

        Runner runner[] = {new Runner(),

                    new Runner()};

            runner[0].setPriority(2);

        runner[1].setPriority(2);

            runner[0].start();

        runner[1].start();

            while(runner[0].isAlive() && runner[1].isAlive()){

            try{

                Thread.sleep(5000);

            }catch(Exception e){};

                System.out.println("Runner 0: " + runner[0].getStep());

            System.out.println("Runner 1: " + runner[1].getStep());

        }

      }

}

程序的运行结果表明两个线程基本上是轮流执行的,这也表明程序所在的运行平台是分时操作系统。下面是这个程序的运行结果。

Runner 0: 4029

Runner 1: 4030

Runner 0: 7989

Runner 1: 7990

Runner 0: 11399

Runner 1: 11400

Runner 0: 13492

Runner 1: 13493

Runner 0: 15503

Runner 1: 15504

Runner 0: 17045

Runner 1: 17045

Runner 0: 20908

Runner 1: 20909

9.4.2  用户线程和守护线程

Java中比较特殊的线程是被称为守护(Daemon)线程的低级别线程。这个线程具有最低的优先级,用于为系统中的其他对象和线程提供服务。将一个用户线程设置为守护线程的方式是:在线程对象创建之前调用线程对象的setDaemon( )方法。典型的守护线程例子是JVM中的系统资源自动回收线程,它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

只有调用线程对象的setDaemon (true)方法时,这个线程才会自动成为守护线程(Daemon)。从调度的角度来说,守护线程与用户线程是类似的。Java虚拟机只在用户线程退出后才检查某个特定的线程是不是守护线程。如果Java 虚拟机发现内存中只有守护线程而没有用户线程时,它会自动退出。守护线程的存在只是为了服务于用户线程。垃圾回收线程是Java 虚拟机的基准实现中的标准守护线程。只有在某一线程对象处于创建和启动之间时,函数void setDaemon ( )才能被调用。

Java虚拟机启动时,通常只有一个非守护线程(在有些已作指定的类中,这一线程常典型地调用名为main的函数)。Java虚拟机继续执行线程,直到以下任何一种情况发生:

·    Runtime的退出函数被调用,并且安全管理程序允许其退出。

·    所有的非守护线程都因为各种原因而终止。这些原因包括:运行调用返回或是发生越界的意外等。