美文网首页
7.4java线程深度解析:线程的调度

7.4java线程深度解析:线程的调度

作者: 文茶君 | 来源:发表于2019-11-28 14:31 被阅读0次

程序中的多个线程是并发执行的,某个线程若想被执行必须要得到CPU的使用权,Java虚拟机会按照特定的机制为程序中的每个线程分配CPU的使用权,这种机制被称作线程的调度。
Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。
两种线程调度模式:

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

  2. 抢占式调度模式:让可运行池中优先级高的线程优先占用CPU,而对于优先级相同的线程,随机选择一个线程使其占用CPU,当它失去了CPU的使用权后,再随机选择其他线程使其占用CPU。

Java虚拟机默认采用抢占式调度模型。

这里要明确的一点,不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。

一.线程的调度:休眠

线程休眠的目的是使线程让出CPU的最简单的做法之一,线程休眠时候,会将CPU资源交给其他线程,以便能轮换执行,当休眠一定时间后,线程会苏醒,进入准备状态等待执行。
线程休眠的方法是Thread.sleep(long millis)和Thread.sleep(long millis, int nanos),均为静态方法,那调用sleep休眠的哪个线程呢?简单说,哪个线程调用sleep,就休眠哪个线程。

/** 
* Java线程:线程的调度-休眠
* 
* @author leizhimin 2009-11-4 9:02:40 
*/ 
publicclass Test {
        publicstaticvoid main(String[] args) {
                Thread t1 = new MyThread1();
                Thread t2 = new Thread(new MyRunnable());
                t1.start(); 
                t2.start(); 
        } 
} 

class MyThread1 extends Thread { 
        public void run() {
                for (int i = 0; i < 3; i++) {
                        System.out.println("线程1第" + i + "次执行!");
                        try {
                                Thread.sleep(50); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                } 
        } 
} 

class MyRunnable implements Runnable {
        public void run() {
                for (int i = 0; i < 3; i++) {
                        System.out.println("线程2第" + i + "次执行!");
                        try {
                                Thread.sleep(50); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                } 
        } 
}

线程2第0次执行!
线程1第0次执行!
线程1第1次执行!
线程2第1次执行!
线程1第2次执行!
线程2第2次执行!

从上面的结果输出可以看出,无法精准保证线程执行次序。

二.线程的调度:优先级

与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。
线程的优先级用1-10之间的整数表示,数值越大优先级越高,默认的优先级为5。
在一个线程中开启另外一个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同。

/** 
* Java线程:线程的调度-优先级
* 
* @author leizhimin 2009-11-4 9:02:40 
*/ 
publicclass Test {
        publicstaticvoid main(String[] args) {
                Thread t1 = new MyThread1();
                Thread t2 = new Thread(new MyRunnable());
                t1.setPriority(10); 
                t2.setPriority(1); 

                t2.start(); 
                t1.start(); 
        } 
} 

class MyThread1 extends Thread { 
        publicvoid run() {
                for (int i = 0; i < 10; i++) {
                        System.out.println("线程1第" + i + "次执行!");
                        try {
                                Thread.sleep(100); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                } 
        } 
} 

class MyRunnableimplements Runnable {
        publicvoid run() {
                for (int i = 0; i < 10; i++) {
                        System.out.println("线程2第" + i + "次执行!");
                        try {
                                Thread.sleep(100); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                } 
        } 
}

线程1第0次执行!
线程2第0次执行!
线程2第1次执行!
线程1第1次执行!
线程2第2次执行!
线程1第2次执行!
线程1第3次执行!
线程2第3次执行!
线程2第4次执行!
线程1第4次执行!
线程1第5次执行!
线程2第5次执行!
线程1第6次执行!
线程2第6次执行!
线程1第7次执行!
线程2第7次执行!
线程1第8次执行!
线程2第8次执行!
线程1第9次执行!
线程2第9次执行!

线程的优先级用1~10之间的整数来表示,数字越大优先级越高。Thread类提供三个静态常量表示线程的优先级:

MAX_PRIORITY :相当于10

MIN_PRIORITY:相当于1

NORM_PRIORITY:相当于5

通常用setPriority(int 优先级)对线程进行设置,但优先级调整的是几率,而不是将优先级低的进程挂起。setPriority 告诉JVM这个线程的优先级,但JVM是否按你请求 (请求,不是要求)办不确定,就是结果不确定(最主要原因是这些优先级需要操作系统的支持,不同的操作系统对优先级的支持是不一样的)

三.线程的调度-让步

线程的让步含义就是使当前运行着线程让出CPU资源,但是给谁不知道,仅仅是让出,线程状态回到可运行状态。
线程的让步使用Thread.yield()方法,yield()为静态方法,功能是暂停当前正在执行的线程对象,并执行其他线程。

/** 
* Java线程:线程的调度-让步
* 
* @author leizhimin 2009-11-4 9:02:40 
*/ 
publicclass Test {
        publicstaticvoid main(String[] args) {
                Thread t1 = new MyThread1();
                Thread t2 = new Thread(new MyRunnable());

                t2.start(); 
                t1.start(); 
        } 
} 

class MyThread1 extends Thread { 
        publicvoid run() {
                for (int i = 0; i < 10; i++) {
                        System.out.println("线程1第" + i + "次执行!");
                } 
        } 
} 

class MyRunnableimplements Runnable {
        publicvoid run() {
                for (int i = 0; i < 10; i++) {
                        System.out.println("线程2第" + i + "次执行!");
                        Thread.yield(); 
                } 
        } 
}

线程2第0次执行!
线程2第1次执行!
线程2第2次执行!
线程2第3次执行!
线程1第0次执行!
线程1第1次执行!
线程1第2次执行!
线程1第3次执行!
线程1第4次执行!
线程1第5次执行!
线程1第6次执行!
线程1第7次执行!
线程1第8次执行!
线程1第9次执行!
线程2第4次执行!
线程2第5次执行!
线程2第6次执行!
线程2第7次执行!
线程2第8次执行!
线程2第9次执行!

通过yield()方法,将当前进程停下,换成就绪状态,让系统的调度器重新调度一次。

与sleep()方法相似,但yield()方法不会阻塞该线程,之后该线程与其他线程是相对公平的。调度谁看系统,有可能还是调度它自己。

四.线程的调度-合并

线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时可以使用join方法。
join为非静态方法,定义如下:

void join()
等待该线程终止。
void join(long millis)
等待该线程终止的时间最长为 millis毫秒。
void join(long millis,int nanos)
等待该线程终止的时间最长为 millis毫秒 + nanos 纳秒。

/** 
* Java线程:线程的调度-合并
* 
* @author leizhimin 2009-11-4 9:02:40 
*/ 
public class Test {
        public static void main(String[] args) {
                Thread t1 = new MyThread1();
                t1.start(); 

                for (int i = 0; i < 20; i++) {
                        System.out.println("主线程第" + i +"次执行!");
                        if (i > 2)try { 
                                //t1线程合并到主线程中,主线程停止执行过程,转而执行t1线程,直到t1执行完毕后继续。
                                t1.join(); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                } 
        } 
} 

class MyThread1 extends Thread { 
        publicvoid run() {
                for (int i = 0; i < 10; i++) {
                        System.out.println("线程1第" + i + "次执行!");
                } 
        } 
}

主线程第0次执行!
主线程第1次执行!
主线程第2次执行!
线程1第0次执行!
主线程第3次执行!
线程1第1次执行!
线程1第2次执行!
线程1第3次执行!
线程1第4次执行!
线程1第5次执行!
线程1第6次执行!
线程1第7次执行!
线程1第8次执行!
线程1第9次执行!
主线程第4次执行!
主线程第5次执行!
主线程第6次执行!
主线程第7次执行!
主线程第8次执行!
主线程第9次执行!
主线程第10次执行!
主线程第11次执行!
主线程第12次执行!
主线程第13次执行!
主线程第14次执行!
主线程第15次执行!
主线程第16次执行!
主线程第17次执行!
主线程第18次执行!
主线程第19次执行!

从结果可以看到,当main线程输出3以后,线程一就开始执行,直到线程一执行完毕,main线程才继续执行。
也有人把这个叫做线程插队,我觉得很形象,这类似于插队的效果。

线程的调度-守护线程

守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。

守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。
setDaemon方法的详细说明:

public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。
该方法必须在启动线程前调用。
该方法首先调用该线程的 checkAccess方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
参数:
on - 如果为true,则将该线程标记为守护线程。
抛出:
IllegalThreadStateException - 如果该线程处于活动状态。
SecurityException - 如果当前线程无法修改该线程。
另请参见:
isDaemon(), checkAccess()

/** 
* Java线程:线程的调度-守护线程
* 
* @author leizhimin 2009-11-4 9:02:40 
*/ 
publicclass Test {
        public static void main(String[] args) {
                Thread t1 = new MyCommon();
                Thread t2 = new Thread(new MyDaemon());
                t2.setDaemon(true);        //设置为守护线程

                t2.start(); 
                t1.start(); 
        } 
} 

class MyCommon extends Thread { 
        public void run() {
                for (int i = 0; i < 5; i++) {
                        System.out.println("线程1第" + i + "次执行!");
                        try {
                                Thread.sleep(7); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                } 
        } 
} 

class MyDaemon implements Runnable { 
        public void run() {
                for (long i = 0; i < 9999999L; i++) {
                        System.out.println("后台线程第" + i +"次执行!");
                        try {
                                Thread.sleep(7); 
                        } catch (InterruptedException e) {
                                e.printStackTrace(); 
                        } 
                } 
        } 
}

后台线程第0次执行!
线程1第0次执行!
线程1第1次执行!
后台线程第1次执行!
后台线程第2次执行!
线程1第2次执行!
线程1第3次执行!
后台线程第3次执行!
线程1第4次执行!
后台线程第4次执行!
后台线程第5次执行!
后台线程第6次执行!
后台线程第7次执行!

从上面的执行结果可以看出:
前台线程是保证执行完毕的,后台线程还没有执行完毕就退出了。
实际上:JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台县城时候一定要注意这个问题。

参考(转载)博客
Java线程详解
线程的调度(线程休眠,线程让步,线程插队)

相关文章

  • 7.4java线程深度解析:线程的调度

    程序中的多个线程是并发执行的,某个线程若想被执行必须要得到CPU的使用权,Java虚拟机会按照特定的机制为程序中的...

  • 网络编程(三)

    Volley用法完全解析 从上图可以看到Volley分为三个线程,分别是主线程、缓存调度线程、和网络调度线程,首先...

  • java虚拟机读书笔记之线程调度

    java线程调度 线程调度主要有两种方式,协同式线程调度和抢占式线程调度。1、协同式: 线程的执行时间由线程本身...

  • [Java]线程和锁

    0x00 线程调度 线程调度指的是系统为线程分配CPU使用权。分为两种: 协同式线程调度线程想用CPU多久就用多久...

  • 2018-04-03 线程基础

    线程调度 是指系统分配CPU使用权限的方式,分为协同式线程调度和抢占式线程调度 进程、线程概念 进程是应用程序的一...

  • 线程模型以及goroutine协程的实现方式

    一、线程模型 1.1 内核级线程模型 每个线程由内核调度器独立的调度,所以如果一个线程阻塞则不影响其他的线程。 优...

  • iOS 多线程技术最牛图解

    了解更多 iOS 多线程技术,点击阅读以下文章 iOS 多线程技术深度解析 最浅显易懂的iOS多线程技术 - GC...

  • Java基础知识11-多线程3

    线程调度 线程调度程序根据线程优先级决定每个线程应当何时运行。理论上,优先级更高的线程比优先级更低的线程会获得更多...

  • EffectiveJava第十章第七节

    不要依赖于线程调度器 当有多个线程可以运行时,由线程调度器(thread scheduler)决定哪些线程将会运行...

  • Java线程池的使用

    线程类型: 固定线程 cached线程 定时线程 固定线程池使用 cache线程池使用 定时调度线程池使用

网友评论

      本文标题:7.4java线程深度解析:线程的调度

      本文链接:https://www.haomeiwen.com/subject/kkwpwctx.html