龙空技术网

多线程之线程调度

进击猿 393

前言:

当前看官们对“线程的调度有哪些方法各有什么功能”大致比较关怀,我们都想要学习一些“线程的调度有哪些方法各有什么功能”的相关内容。那么小编在网摘上汇集了一些关于“线程的调度有哪些方法各有什么功能””的相关文章,希望同学们能喜欢,你们一起来学习一下吧!

计算机通常只有一个CPU。在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才可以执行指令。所谓线程并发运行,从宏观上看,就是各个线程轮流获得CPU使用权,分别执行各自的任务。在可运行池中,会有多个处于就绪状态的线程在等待CPU,Java虚拟机的一项任务就是负责线程的调度。线程的调度是指按照特定的机制为多个线程分配CPU的使用权,有两种调度模型:分时调度模型和抢占式调度模型。

分时调度模型是指让所有线程轮流获得CPU的使用权,并且平均分配每个线程占用CPU的时间片。

Java虚拟机采用的是抢占式调度模型,它是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中线程的优先级相同,那么就随机的选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。一个线程会因为一下原因放弃CPU:

Java虚拟机让当前线程暂时放弃CPU,转到就绪状态,使其他线程获取运行机会。当前线程因为某些原因进入阻塞状态线程运行结束

线程的调度规则不是跨平台的,它不仅取决于Java虚拟机,还依赖于操作系统。在某些操作系统中,只要运行的线程没有遇到阻塞,就不会放弃CPU;在某些操作系统中,即使运行中的线程没有遇到阻塞,也会在运行一段时间后放弃CPU,给其他线程机会。

下面的代码可以证明Java线程调度不是分时的,不能保证各个线程轮流获得CPU时间片:

public class Machine extends Thread{

private static StringBuffer log=new StringBuffer();

private static int count=0;

public void run() {

for(int a=0;a<20;a++){

log.append(currentThread().getName()+":"+a+" ");

if(++count%10==0)log.append("\n");

}

}

public static void main(String[] args) throws InterruptedException {

Machine machine1=new Machine();

Machine machine2=new Machine();

machine1.setName("m1");

machine2.setName("m2");

machine1.start();

machine2.start();

while(machine1.isAlive()||machine2.isAlive())

Thread.sleep(500);

System.out.println(log);

}

}

打印结果:

上面的打印结果,表明machine1线程先获得CPU,进入运行状态,直到machine1线程结束生命周期,machine2线程才从就绪状态转到运行状态。

如果希望machine1给machine2运行的机会,可以采取以下办法:

调整线程的优先级

让处于运行状态的线程调用Thread.sleep()方法让处于运行状态的线程调用Thread.yield()方法让处于运行状态的线程调用另一个线程的join()方法调整线程的优先级

所有处于就绪状态的线程根据优先级存放在可运行池中,优先级低的线程获得较少的运行机会,优先级高的线程获得较高的运行机会。Thread类的setPriority(int)和getPriority()分别用来设置优先级和读取优先级。优先级用整数表示,取值范围是1~10,Thread类有3个静态常量:

MAX_PRIORITY:取值为10,表示最高优先级

MIN_PRIORITY:取值为1,表示最低优先级

NORM_PRIORITY:取值为5,表示默认的优先级

我们对第一个示例做该造,分别设置machine1和machine2的优先级:

public static void main(String[] args) throws InterruptedException {

Machine machine1=new Machine();

Machine machine2=new Machine();

machine1.setName("m1");

machine2.setName("m2");

/*查看和设置线程的优先级*/

//打印线程的默认优先级

System.out.println(machine1.getPriority());

System.out.println(machine2.getPriority());

machine2.setPriority(Thread.MAX_PRIORITY);

machine1.setPriority(Thread.MIN_PRIORITY);

machine1.start();

machine2.start();

Thread.sleep(2000);

System.out.println(log);

}

上面代码machine2的优先级高于machine1,程序运行的可能结果:

每个线程都有默认的优先级,主线程默认优先级是Thread.NORM_PRIORITY。如果线程A创建了线程B,那么线程B将和线程A具有同样的优先级。当然,对于任意一个线程,都可以通过setPriority()方法重新设置它的优先级。

需要注意的是,尽管Java提供了10个优先级,但是它与多数操作系统都不能很好地进行线程优先级映射。比如Windows有7个优先级,并且不是固定的,而Solaris操作系统有2的31次方个优先级。如果希望程序能够移植到各个操作系统中,就应该确保在设置优先级时候,只用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY这3个级别,以保证在不同操作系统中,对同样优先级别的线程采用同样的调度方式。

线程睡眠Thread.sleep()方法

当一个线程在运行中执行了sleep()方法,它就会放弃CPU,转到阻塞状态。

Thread类的sleep(long mills)方法是静态的,mills参数设定睡眠的时间,以毫秒为单位。假定一个线程对象在某一时刻获得CPU,开始执行指令,当它执行到sleep()方法时,就会放弃CPU并开始睡眠。等到结束睡眠,首先转到就绪状态,不一定会立即执行,而是在可运行池中等待获得CPU。

线程在睡眠时被中断,会抛出一个InterrupedException异常,请看下面示例:

public class Sleeper extends Thread{

@Override

public void run() {

try {

sleep(60000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("end");

}

public static void main(String[] args) throws InterruptedException {

Sleeper sleeper=new Sleeper();

sleeper.start();

Thread.sleep(10);

sleeper.interrupt();//中断Sleeper线程的睡眠

}

}

主线程启动Sleeper线程,Sleeper线程睡眠1分钟,主线程10秒后中断Sleeper线程的睡眠,程序打印结果:

线程让步:Thread.yield()方法

当线程在运行中执行了Thread的yield()静态方法时,如果此时有处于相同优先级的其他线程处于就绪状态,yield()方法就会使当前运行的线程放到可运行池中,并使另一个线程运行。如果没有相同优先级的可运行线程,yield()方法什么都不做。

sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给其他线程,两者区别在于:sleep()方法会给其他线程运行的机会,不考虑其他线程的优先级,因此会给较低优先级线程一个运行机会;yield()方法只会给相同优先级或更高优先级的线程一个运行的机会。当线程执行了sleep(long mills)方法后,将转到阻塞状态,参数mills指定睡眠时间;当线程执行了yield()方法以后,将转到就绪状态sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常sleep()方法比yield()方法有更好的可移植性。不能依靠yield()方法来提高程序的并发性能。对于大多数程序员来说,yield()方法唯一用途是在测试期间人为的提高程序的并发性能,以帮助发现一些隐藏的错误。等待其他线程结束:join()

当前运行的线程可以调用另一个线程的join()方法,当前线程将转入阻塞状态,直到另一个线程运行结束,它才会转到就绪状态。

public class Machine extends Thread{

public void run() {

for(int a=0;a<50;a++)

System.out.println(getName()+":"+a);

}

public static void main(String[] args) throws InterruptedException {

Machine machine1=new Machine();

machine1.setName("m1");

machine1.start();

System.out.println("main join machine");

machine1.join();

System.out.println("main end");

}

}

上面示例代码,主线程调用了machien1线程的join方法,主线程将等到machine1线程运行结束以后,才恢转到就绪状态,打印结果:

main join machine

m1:0

m1:1

...

m1:49

main end

join()方法有两种重载形式:

public void join()

public void join(long timeout)

上面的timeout参数设定当前线程被阻塞时间,以毫秒为单位。如果把上面示例代码的"machine1.join()"改为

machine1.join(10);

那主线程被阻塞的时间超过了10毫秒时,或者machine1线程运行结束时,主线程就转到就绪状态。

目录:Java知识目录

标签: #线程的调度有哪些方法各有什么功能