多线程程序设计涉及到的一个问题是线程的优先级。线程优先级的大小决定了线程获取CPU时间的机会。Java语言提供了设计线程优先级的方法。
线程调度有两种方式:抢占式和非抢占式。抢占式系统在任何给定的时间内将运行最高优先级的线程,系统中的所有线程都有自己的优先级。
线程的优先级(Priority)决定该线程的重要程度,即线程运行的顺序以及从处理器中获得的时间数量如何。
Java线程的优先级可由用户来设置,它是由一个整数表示的,这个整数越大,则线程的优先级越大。Java优先级的范围在Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之间。默认情况下线程的优先级是Thread.NORM_PRIORITY。Thread类提供了setPriority( )和getPriority( )方法来设置和读取优先级。
Java虚拟机是抢占式的,它能保证运行优先级最高的线程。在Java虚拟机中,如果把一个线程的优先级改为最高,那么它将取代当前正在运行的线程,除非这个线程结束运行或者进入休眠状态,否则将一直占用所有的处理器的时间。
【例9-9】 本例是一个模仿赛跑的程序。Runner类的run( )方法中对自身的私有成员step做自增的操作,然后进入睡眠1毫秒。Race 类的main( )方法中创建了Runner类的两个对象runner[0]和runner[1],分别把它们的优先级设置为Thread.MIN_PRIORITY和Thread.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
在Java中比较特殊的线程是被称为守护(Daemon)线程的低级别线程。这个线程具有最低的优先级,用于为系统中的其他对象和线程提供服务。将一个用户线程设置为守护线程的方式是:在线程对象创建之前调用线程对象的setDaemon( )方法。典型的守护线程例子是JVM中的系统资源自动回收线程,它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
只有调用线程对象的setDaemon (true)方法时,这个线程才会自动成为守护线程(Daemon)。从调度的角度来说,守护线程与用户线程是类似的。Java虚拟机只在用户线程退出后才检查某个特定的线程是不是守护线程。如果Java 虚拟机发现内存中只有守护线程而没有用户线程时,它会自动退出。守护线程的存在只是为了服务于用户线程。垃圾回收线程是Java 虚拟机的基准实现中的标准守护线程。只有在某一线程对象处于创建和启动之间时,函数void setDaemon ( )才能被调用。
当Java虚拟机启动时,通常只有一个非守护线程(在有些已作指定的类中,这一线程常典型地调用名为main的函数)。Java虚拟机继续执行线程,直到以下任何一种情况发生:
· 类Runtime的退出函数被调用,并且安全管理程序允许其退出。
· 所有的非守护线程都因为各种原因而终止。这些原因包括:运行调用返回或是发生越界的意外等。