Java大学实用教程 第八章
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第八章 线程
本章导读 Java中的线程 线程的生命周期 线程的优先级与调度管理 Thread的子类创建线程 Runable接口 线程同步 wait()、notify 和notifyAll()方法 挂起、恢复和终止线程 线程的联合 守护线程
1
线程概述
Java 语 言 的 一 大 特 点 就 是 内 置 对 多 线 程 的 支 持 ( ng 包 中 的 Thread类)。多线程是指同时存在几个执行体,按几条不同的执行线索共同 工作的情况,它使得编程人员可以很方便地开发出具有多线程功能、能同时 处理多个任务的功能强大的应用程序。虽然执行线程给人一种几个事件同时 发生的感觉,但这只是一种错觉,因为我们的计算机在任何给定的时刻只能 执行这些线程中的一个。为了建立这些线程正在同步执行的感觉,Java快速 地把控制从一个线程切换到另一个线程。
13
8.6 线程的常用方法_2
4.isAlive()
在 线 程 的 run 方 法 结 束 之 前 , 即 没 有 进 入 死 亡 状 态 之 前 , 线 程 调 用 isAlive() 方法返回 true,当线程进入死亡状态后(实体内存被释放),线 程仍可以调用方法isAlive(),这时返回的值是false。 需要注意的是,一个已经运行的线程在没有进入死亡状态时,不要再给 线程分配实体,由于线程只能引用最后分配的实体,先前的实体就会成为 “垃圾”,并且不会被垃圾收集机收集掉。内存示意如图8.4、图8.5所示。 例8-9中,一个线程每隔1秒钟在命令行窗口输出机器的当前时间,在输出 3秒之后,该线程又被分配了实体,新实体又开始运行。这时,我们在命令行 每秒钟能看见两行当前时间,因为垃圾实体仍然在工作(效果如图8.6所示)。
3
8.2 线程的生命周期
在 Java 语言中,Thread类及其子类创建的对象称为线程。新建的线程在它 的一个完整的生命周期中通常要经历4种状态。 1)新建:线程对象被声明并创建时 2)运行:线程对象调用start()方法 3)中断:中断的原因消除时,线程可以从中断处继续运行.
有4种原因的中断: *JVM将CPU资源从当前线程切换给其他线程 *线程使用CPU资源期间,执行了sleep(int millsecond)方法 *线程使用CPU资源期间,执行了wait()方法 *线程使用CPU资源期间,执行某个操作进入阻塞状态
class Qution{ public static void main(String args[]){ while(true){ System.out.println("123"); } while(true){ System.out.println("abc"); } 于run()方法中的局部变量
对于具有相同目标对象的线程,当其中一个线程享用CPU资源时,目标对象自动 调用接口中的 run()方法,这时run()方法中的局部变量被分配内存空间。当轮到另一个 线程享用CPU 资源时,目标对象会再次调用接口中的run()方法,那么run()方法中的局 部变量会再次分配内存空间。也就是说,run()方法已经启动运行了两次,分别运行在 不同的线程中,即运行在不同的时间片内。 我们称run()方法中的局部变量为线程的局部变量,不同线程的run()方法中的局部 变量互不干扰,一个线程改变了自己的 run()方法中局部变量的值不会影响其他线程的 run()方法中的局部变量。 例8-8演示了两个线程的run()方法中的局部变量互不干扰(效果如图8.3所示)。
3.sleep(int millsecond)
有时,优先级高的线程需要优先级低的线程做一些工作来配合它,或者优先级高的 线程需要完成一些费时的操作,此时优先级高的线程应该让出处理器,使优先级低的线 程有机会执行。为达到这个目的,优先级高的线程可以在它的 run()方法中调用sleep() 方法来使自己放弃处理器资源,休眠一段时间。休眠时间的长短由sleep()方法的参数决 定 , millsecond 是 毫 秒 为单位 的 休 眠 时间 。 如 果 线 程在休 眠 时 被打 断 , JVM 就 抛 出 InterruptedException异常。因此,必须在try-catch语句块中调用sleep()方法。
6
8.4 Thread 的子类创建线程
在Java语言中,用Thread类或子类创建线程对象。 用户可以扩展 Thread类,但需要重写父类的run()方法,其 目的是规定线程的具体操作,否则线程就什么也不做,因为父类 的run()方法中没有任何操作语句。 例子8-3中除主线程外还有两个线程,这两个线程分别在命 令行窗口的左侧和右侧顺序地一行一行地输出字符串。主线程负 责判断输出的行数,当其中任何一个线程输出 8行后,就结束进 程。 本例题中用到了System类中的类方法exit(int n),主线程 使用该方法结束整个程序。
例子8-5效果图
10
2.目标对象与线程的关
目标对象和线程的关系有以下两种情景。 1)目标对象和线程完全解藕 在上述例8-5中,创建目标对象的Bank类并没有组合zhang和cheng线程对 象,也就是说Bank创建的目标对象bank不包含对象zhang和cheng线程对象的引 用(完全解藕)。在这种情况下,目标对象经常需要通过获得线程的名字(因 为无法获得线程对象的引用): String name = Thread.currentThread().getName(); 以便确定是哪个线程正在占用CPU资源,即被JVM正在执行的线程。 2)目标对象组合线程(弱藕合) 目标对象可以组合线程,即将线程作为自己的成员(弱藕合),比如让线 程zhang和cheng在bank中。当创建目标对象类组合线程对象时,目标对象可以 通过获得线程对象的引用: Thread.currentThread(); 来确定是哪个线程正在占用CPU资源,即被JVM正在执行的线程,如下面的 例8-7中代码所示。
4
例子8-1效果图
5
8.3 线程的优先级与调度管理
Java虚拟机中的线程调度器负责管理线程,调度器把线程的优先级分为10 个级别,分别用Thread类中的类常量表示。每个Java线程的优先级都在常数 1 (Thread.MIN PRIORITY)到常数10(Thread.MAX_PRIORITY)的范围内。如果 没有明确地设置线程的优先级别,每个线程的优先级都为常数5(包括主 线)Thread.NORM_PRIORITY。 线程的优先级可以通过setPriority(int grade)方法调整,这一方法需要 一个int类型参数。有些操作系统只能识别3个级别:1,5,10。 在采用时间片的系统中,每个线程都有机会获得 CPU 的使用权,以便使用 CPU资源执行线程中的操作。当线程使用CPU资源的时间结束后,即使线程没 有完成自己的全部操作,Java调度器也会中断当前线程的执行,把CPU的使用 权切换给下一个排队等待的线程,当前线程将等待CPU资源的下一次轮回,然 后从中断处继续执行。 Java调度器的任务是使高优先级的线程能始终运行,一旦时间片有空闲,则 使具有同等优先级的线程以轮流的方式顺序使用时间片。
程序是一段静态的代码,它是应用软件执行的蓝本。进程是程序的一次动 态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个 过程也是进程本身从产生、发展至消亡的过程。线程是比进程更小的执行单 位。一个进程在其执行过程中,可以产生多个线程,形成多条执行线索,每 条线索,即每个线程也有它自身的产生、存在和消亡的过程,也是一个动态 的概念。 Java 应用程序总是从主类的 main 方法开始执行。当JVM加载代码,发现 main 方法之后,就会启动一个线程,这个线程称为“主线程”,该线程负责 执行main 方法。那么,在main 方法中再创建的线程,就称为主线程中的线程。 如果 main 方法中没有创建其他线程,那么当 main 方法执行完最后一个语句, 即main方法返回时,JVM就会结束该Java应用程序。如果main方法中又创建了 其他线程,那么JVM就要在主线程和其他线程之间轮流切换,保证每个线程 都有机会使用CPU资源,main方法即使执行完最后的语句,JVM也不会结束该 程序,JVM一直要等到主线程中的所有线程都结束之后,才结束该Java应用程 序。
7
8.5 Runnable接口
使用Thread子类创建线程的优点是:我们可以在子类中增加 新的成员变量,使线程具有某种属性,也可以在子类中新增加方 法,使线程具有某种功能。但是,Java不支持多继承,Thread类 的子类不能再扩展其他的类。
8
1.Runnable接口与目标对象
创建线程的另一个途径就是用Thread类直接创建线程对象。使用Thread创 建线程对象时,通常使用的构造方法是:
4)死亡: 释放分配给线程对象的内存。
线程死亡的原因有二: 即执行完run()方法中的全部语句或线程被提前强制性终止,即强制run()方法结束。
例8-1 Thread的子类WriteWordThread创建了两个线程。 注:上述程序在不同的计算机运行或在同一台计算机反复运行的结果不尽相同, 输出结果依赖当前 CPU 资源的使用情况。为了使结果尽量不依赖于当前 CPU 资 源的使用情况,我们应当让线程主动调用sleep()方法让出CPU的使用权进入中断 状态。 例8-2
12
8.6 线程的常用方法_1
1.start()
线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用 CPU资源时,就可以脱离创建它的主线程独立开始自己的生命周期了。
2.run()
Thread类的run()方法与Runnable 接口中的run()方法的功能和作用相同,都用来定义 线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法。 Thread 类中, run() 方法没有具体内容,所以用户程序需要创建自己的 Thread 类的子 类,并重写run()方法来覆盖原来的run()方法。当run()方法执行完毕,线程就变成死亡状 态 。在线程没 有结束 run() 方 法之前 , 不建 议让线程再调 用 start() 方法 , 否则将发 生 IllegalThreadStateException异常。
Thread(Runnable target)
该构造方法中的参数是一个 Runnable类型的接口,因此,在创建线程对象 时必须向构造方法的参数传递一个实现Runnable接口类的实例,该实例对象称 作所创线程的目标对象,当线程调用start()方法后,一旦轮到它来享用CPU资 源,目标对象就会自动调用接口中的run()方法(接口回调),这一过程是自动 实现的,用户程序只需要让线程调用start()方法即可,也就是说,当线程被调 度并转入运行状态时,所执行的就是run()方法中所规定的操作。 例8-4不使用Thread类的子类创建线程,而是使用Thread类创建left和right线程。 线程间可以共享相同的内存单元(包括代码与数据),并利用这些共享单元 来实现数据交换、实时通信和必要的同步操作。 例 8-5 中 , 线程 zhang 和 cheng 使用同一目标对象 . 两个线程共享目标对象的 money。当 money的值小于100时,线程zhang结束自己的run()方法进入死亡状态; 当money的值小于60时,线程cheng结束自己的run()方法进入死亡状态,(效果如图 8.2所示)。 例 8-6 中中共有 4 个线程 threadA、threadB、threadC 和 threadD, 其中 threadA 和 threadB 的目标对象 a1,threadC 和 threadD 的目标对象是 a2。threadA 和 threadB 共 享a1的成员number,而threadC和threadD共享 9a2的成员number。
本章导读 Java中的线程 线程的生命周期 线程的优先级与调度管理 Thread的子类创建线程 Runable接口 线程同步 wait()、notify 和notifyAll()方法 挂起、恢复和终止线程 线程的联合 守护线程
1
线程概述
Java 语 言 的 一 大 特 点 就 是 内 置 对 多 线 程 的 支 持 ( ng 包 中 的 Thread类)。多线程是指同时存在几个执行体,按几条不同的执行线索共同 工作的情况,它使得编程人员可以很方便地开发出具有多线程功能、能同时 处理多个任务的功能强大的应用程序。虽然执行线程给人一种几个事件同时 发生的感觉,但这只是一种错觉,因为我们的计算机在任何给定的时刻只能 执行这些线程中的一个。为了建立这些线程正在同步执行的感觉,Java快速 地把控制从一个线程切换到另一个线程。
13
8.6 线程的常用方法_2
4.isAlive()
在 线 程 的 run 方 法 结 束 之 前 , 即 没 有 进 入 死 亡 状 态 之 前 , 线 程 调 用 isAlive() 方法返回 true,当线程进入死亡状态后(实体内存被释放),线 程仍可以调用方法isAlive(),这时返回的值是false。 需要注意的是,一个已经运行的线程在没有进入死亡状态时,不要再给 线程分配实体,由于线程只能引用最后分配的实体,先前的实体就会成为 “垃圾”,并且不会被垃圾收集机收集掉。内存示意如图8.4、图8.5所示。 例8-9中,一个线程每隔1秒钟在命令行窗口输出机器的当前时间,在输出 3秒之后,该线程又被分配了实体,新实体又开始运行。这时,我们在命令行 每秒钟能看见两行当前时间,因为垃圾实体仍然在工作(效果如图8.6所示)。
3
8.2 线程的生命周期
在 Java 语言中,Thread类及其子类创建的对象称为线程。新建的线程在它 的一个完整的生命周期中通常要经历4种状态。 1)新建:线程对象被声明并创建时 2)运行:线程对象调用start()方法 3)中断:中断的原因消除时,线程可以从中断处继续运行.
有4种原因的中断: *JVM将CPU资源从当前线程切换给其他线程 *线程使用CPU资源期间,执行了sleep(int millsecond)方法 *线程使用CPU资源期间,执行了wait()方法 *线程使用CPU资源期间,执行某个操作进入阻塞状态
class Qution{ public static void main(String args[]){ while(true){ System.out.println("123"); } while(true){ System.out.println("abc"); } 于run()方法中的局部变量
对于具有相同目标对象的线程,当其中一个线程享用CPU资源时,目标对象自动 调用接口中的 run()方法,这时run()方法中的局部变量被分配内存空间。当轮到另一个 线程享用CPU 资源时,目标对象会再次调用接口中的run()方法,那么run()方法中的局 部变量会再次分配内存空间。也就是说,run()方法已经启动运行了两次,分别运行在 不同的线程中,即运行在不同的时间片内。 我们称run()方法中的局部变量为线程的局部变量,不同线程的run()方法中的局部 变量互不干扰,一个线程改变了自己的 run()方法中局部变量的值不会影响其他线程的 run()方法中的局部变量。 例8-8演示了两个线程的run()方法中的局部变量互不干扰(效果如图8.3所示)。
3.sleep(int millsecond)
有时,优先级高的线程需要优先级低的线程做一些工作来配合它,或者优先级高的 线程需要完成一些费时的操作,此时优先级高的线程应该让出处理器,使优先级低的线 程有机会执行。为达到这个目的,优先级高的线程可以在它的 run()方法中调用sleep() 方法来使自己放弃处理器资源,休眠一段时间。休眠时间的长短由sleep()方法的参数决 定 , millsecond 是 毫 秒 为单位 的 休 眠 时间 。 如 果 线 程在休 眠 时 被打 断 , JVM 就 抛 出 InterruptedException异常。因此,必须在try-catch语句块中调用sleep()方法。
6
8.4 Thread 的子类创建线程
在Java语言中,用Thread类或子类创建线程对象。 用户可以扩展 Thread类,但需要重写父类的run()方法,其 目的是规定线程的具体操作,否则线程就什么也不做,因为父类 的run()方法中没有任何操作语句。 例子8-3中除主线程外还有两个线程,这两个线程分别在命 令行窗口的左侧和右侧顺序地一行一行地输出字符串。主线程负 责判断输出的行数,当其中任何一个线程输出 8行后,就结束进 程。 本例题中用到了System类中的类方法exit(int n),主线程 使用该方法结束整个程序。
例子8-5效果图
10
2.目标对象与线程的关
目标对象和线程的关系有以下两种情景。 1)目标对象和线程完全解藕 在上述例8-5中,创建目标对象的Bank类并没有组合zhang和cheng线程对 象,也就是说Bank创建的目标对象bank不包含对象zhang和cheng线程对象的引 用(完全解藕)。在这种情况下,目标对象经常需要通过获得线程的名字(因 为无法获得线程对象的引用): String name = Thread.currentThread().getName(); 以便确定是哪个线程正在占用CPU资源,即被JVM正在执行的线程。 2)目标对象组合线程(弱藕合) 目标对象可以组合线程,即将线程作为自己的成员(弱藕合),比如让线 程zhang和cheng在bank中。当创建目标对象类组合线程对象时,目标对象可以 通过获得线程对象的引用: Thread.currentThread(); 来确定是哪个线程正在占用CPU资源,即被JVM正在执行的线程,如下面的 例8-7中代码所示。
4
例子8-1效果图
5
8.3 线程的优先级与调度管理
Java虚拟机中的线程调度器负责管理线程,调度器把线程的优先级分为10 个级别,分别用Thread类中的类常量表示。每个Java线程的优先级都在常数 1 (Thread.MIN PRIORITY)到常数10(Thread.MAX_PRIORITY)的范围内。如果 没有明确地设置线程的优先级别,每个线程的优先级都为常数5(包括主 线)Thread.NORM_PRIORITY。 线程的优先级可以通过setPriority(int grade)方法调整,这一方法需要 一个int类型参数。有些操作系统只能识别3个级别:1,5,10。 在采用时间片的系统中,每个线程都有机会获得 CPU 的使用权,以便使用 CPU资源执行线程中的操作。当线程使用CPU资源的时间结束后,即使线程没 有完成自己的全部操作,Java调度器也会中断当前线程的执行,把CPU的使用 权切换给下一个排队等待的线程,当前线程将等待CPU资源的下一次轮回,然 后从中断处继续执行。 Java调度器的任务是使高优先级的线程能始终运行,一旦时间片有空闲,则 使具有同等优先级的线程以轮流的方式顺序使用时间片。
程序是一段静态的代码,它是应用软件执行的蓝本。进程是程序的一次动 态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个 过程也是进程本身从产生、发展至消亡的过程。线程是比进程更小的执行单 位。一个进程在其执行过程中,可以产生多个线程,形成多条执行线索,每 条线索,即每个线程也有它自身的产生、存在和消亡的过程,也是一个动态 的概念。 Java 应用程序总是从主类的 main 方法开始执行。当JVM加载代码,发现 main 方法之后,就会启动一个线程,这个线程称为“主线程”,该线程负责 执行main 方法。那么,在main 方法中再创建的线程,就称为主线程中的线程。 如果 main 方法中没有创建其他线程,那么当 main 方法执行完最后一个语句, 即main方法返回时,JVM就会结束该Java应用程序。如果main方法中又创建了 其他线程,那么JVM就要在主线程和其他线程之间轮流切换,保证每个线程 都有机会使用CPU资源,main方法即使执行完最后的语句,JVM也不会结束该 程序,JVM一直要等到主线程中的所有线程都结束之后,才结束该Java应用程 序。
7
8.5 Runnable接口
使用Thread子类创建线程的优点是:我们可以在子类中增加 新的成员变量,使线程具有某种属性,也可以在子类中新增加方 法,使线程具有某种功能。但是,Java不支持多继承,Thread类 的子类不能再扩展其他的类。
8
1.Runnable接口与目标对象
创建线程的另一个途径就是用Thread类直接创建线程对象。使用Thread创 建线程对象时,通常使用的构造方法是:
4)死亡: 释放分配给线程对象的内存。
线程死亡的原因有二: 即执行完run()方法中的全部语句或线程被提前强制性终止,即强制run()方法结束。
例8-1 Thread的子类WriteWordThread创建了两个线程。 注:上述程序在不同的计算机运行或在同一台计算机反复运行的结果不尽相同, 输出结果依赖当前 CPU 资源的使用情况。为了使结果尽量不依赖于当前 CPU 资 源的使用情况,我们应当让线程主动调用sleep()方法让出CPU的使用权进入中断 状态。 例8-2
12
8.6 线程的常用方法_1
1.start()
线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用 CPU资源时,就可以脱离创建它的主线程独立开始自己的生命周期了。
2.run()
Thread类的run()方法与Runnable 接口中的run()方法的功能和作用相同,都用来定义 线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法。 Thread 类中, run() 方法没有具体内容,所以用户程序需要创建自己的 Thread 类的子 类,并重写run()方法来覆盖原来的run()方法。当run()方法执行完毕,线程就变成死亡状 态 。在线程没 有结束 run() 方 法之前 , 不建 议让线程再调 用 start() 方法 , 否则将发 生 IllegalThreadStateException异常。
Thread(Runnable target)
该构造方法中的参数是一个 Runnable类型的接口,因此,在创建线程对象 时必须向构造方法的参数传递一个实现Runnable接口类的实例,该实例对象称 作所创线程的目标对象,当线程调用start()方法后,一旦轮到它来享用CPU资 源,目标对象就会自动调用接口中的run()方法(接口回调),这一过程是自动 实现的,用户程序只需要让线程调用start()方法即可,也就是说,当线程被调 度并转入运行状态时,所执行的就是run()方法中所规定的操作。 例8-4不使用Thread类的子类创建线程,而是使用Thread类创建left和right线程。 线程间可以共享相同的内存单元(包括代码与数据),并利用这些共享单元 来实现数据交换、实时通信和必要的同步操作。 例 8-5 中 , 线程 zhang 和 cheng 使用同一目标对象 . 两个线程共享目标对象的 money。当 money的值小于100时,线程zhang结束自己的run()方法进入死亡状态; 当money的值小于60时,线程cheng结束自己的run()方法进入死亡状态,(效果如图 8.2所示)。 例 8-6 中中共有 4 个线程 threadA、threadB、threadC 和 threadD, 其中 threadA 和 threadB 的目标对象 a1,threadC 和 threadD 的目标对象是 a2。threadA 和 threadB 共 享a1的成员number,而threadC和threadD共享 9a2的成员number。