《jAVA2程序设计基础第十一章.ppt》由会员分享,可在线阅读,更多相关《jAVA2程序设计基础第十一章.ppt(23页珍藏版)》请在三一办公上搜索。
1、第11章 多 线 程,教学提示:在同时处理多个任务的应用中,多线程的意义显得尤其重要,本章将介绍多线程的内容,主要包括线程的概念、线程的创建、线程间的同步与通信,以及线程的生命周期和状态控制,并说明使用多线程时应该注意的问题。学习完本章之后,读者将对Java多线程机制有一个全面的了解。,教学目标:理解线程的概念,熟练掌握线程的创建、线程间的同步与通信,以及线程的生命周期和状态控制,并牢记使用多线程时应该注意的问题。,11.1 线程的概念,在介绍线程前有必要简要介绍一下进程,这将有助于读者理解线程概念。相信大多数读者都知道,在Windows操作系统中可以同时执行多个程序,比如打开一个资源管理器和
2、多个 IE 浏览器,同时使用播放器播放音乐,后台可能同时还有杀毒软件防火墙在运行,这里的每一个运行的程序都是一个进程。严格地说,这种说法是不准确的。程序一般是指保存在外部存储器(一般为硬盘)中的代码文件,当程序被执行时,系统会先在内存中为其分配一块空间,再把其代码复制到该空间 中执行,这个在由系统分配的内存空间中执行的程序才是进程。一个程序可能同时存在多个相应的进程,如同时打开多个IE浏览器,每一个浏览器窗口都是一个进程,都拥有自己独立的内存空间,而它们都来自于同一个程序。,11.2 线程的创建,对于一个用Java编写的进程来说,在Java虚拟机启动之初,会且只会产生一个独一的非守护线程(守护
3、线程将在稍后介绍),具有代表性的是类中静态方法main()产生的线程,该线程为主线程,可以创建和控制其他线程。,继承自类Thread,实现接口Runnable,两种创建线程方式的对比,守护线程Daemon,11.2.1 继承自类Thread,类Thread位于java.lang包中,由于java.lang包被自动包含入每个Java文件中,所以可以直接使用类Thread而无需编写import语句。,返回,11.2.2 实现接口Runnable,Runnable接口非常简单,其全貌如下:public interface Runnable void run();,返回,11.2.3 两种创建线程方式
4、的对比,Thread是Java已经严格封装好了的类,在面向对象思想中,继承这样的类并修改或扩充它不是十分可取的。因为可能会出现人为失误,对一个类进行继承修改或扩充,将可能导致该子类出现不可预料的错误。,返回,11.2.4 守护线程Daemon,线程分为用户线程和守护线程两种。用户线程即一般线程,守护线程是具有如下特性的线程:它会在所有用户线程(即非守护线程)结束之后立即被Java虚拟机结束,而不管其是否已执行完毕,从而结束整个进程。守护线程往往处于无限循环中,用于监听其他线程(一般是用户线程,也可以是其他守护线程)的运行,并提供某种通用性的服务,典型的如Internet中收发E-mail邮件的
5、服务。通过线程对象的 isDaemon()方法可以判断该线程是否为守护线程,通过setDaemon()方法可以改变该线程的类型setDaemon(false)将线程改为用户线程,setDaemon(true)将线程改为守护线程。该方法必须在调用线程的start()方法前调用才有效,否则线程将为默认的线程类型由用户线程创建的线程默认为用户线程,由守护线程创建的线程默认为守护线程,main()方法产生的主线程始终为用户线程。,返回,11.3 线程的同步,在单线程的进程中,一个进程一次只能执行一个任务,一次只能使用一个资源,不需要考虑两个或更多个任务同时试图使用同一个资源的问题,如两个任务同时修改同
6、一个数据,或同时进行打印操作而只有一台打印机。然而在多线程环境下,这种多个线程试图同时使用相同且有限的资源的情况,是很有可能发生的,若不提供某种机制避免这种情况的出现,后果将可能是非常严重且不可预料的,如造成某些线程数据的不一致,使某些线程陷入无限循环永远无法退出,破坏某些关键文件或数据库中的重要数据。,资源冲突,同步机制,同步效率,11.3.1 资源冲突,这是一段完整可编译的代码,提供了reduceMainData()方法用于递减私有的主数据,在run()方法中保存主数据与打印主数据之前进行了多达1 024 000次的浮点运算用于延缓线程运行,这将使得该线程在此处很可能被暂停。此时若main
7、()主线程被调度到CPU执行,它很可能会递减该线程的主数据,从而造成打印时主数据与备份数据的不一致。,返回,11.3.2 同步机制,我们永远无法知道线程什么时候开始执行(并不是在创建它的时刻,它便开始执行的),也无法知道它什么时候会被暂停,更加无从得知在暂停期间其他线程会对它进行怎样的访问控制,这是多线程环境下线程的根本性质。由此产生的资源冲突问题是代码编写者必须考虑的,至少在重要的时刻必须避免关键资源的冲突。,返回,11.3.3 同步效率,为避免可能的资源冲突,将线程中所有除 run()外的方法都声明为synchronized似乎是个可行的方法。然而获得一个线程对象的锁的代价非常高,至少是一
8、般方法调用(仅仅是方法的进入和退出,不包括方法的执行体)的四倍。因此将那些不会造成资源冲突的方法的关键字synchronized省去,是非常有利的,但单纯为了提高性能而省去某个方法的关键字synchronized可能带来灾难性的后果,又是不可取的。究竟哪些方法应该声明为synchronized,哪些方法不应该声明为synchronized,并没有一个统一的标准,,返回,11.4 线程间的通信,线程不仅仅作为单独的个体存在,它往往需要与其他线程进行数据交互,这种数据的交互便是线程间的通信。通过一些简单的方式可以实现线程间的通信,例如,在线程类中设置一个静态(static)变量,它可以作为所有由该
9、类生成的线程的共享数据,11.2节的几个实例中递减计数count就是这样一个共享数据;或者在线程类中设置一个私有(private)变量,并提供读取和修改该变量的公有(public)方法,其他线程可以通过这两个方法访问由该类生成的线程的数据。然而这样只能实现一些简单且数据量较少的线程间的通信,并有可能造成Java同步机制无法避免的资源冲突问题,特别是设置静态共享数据时。因此有必要提供一种安全高效的线程间的通信机制,这便是在介绍 I/O 系统时曾经提到过的管道流PipedInputStream/PipedOutputStream和 PipedReader/PipedWriter它们正是被设计用来实
10、现多线程环境下线程间的通信的。,返回,11.5 线程的控制,多个线程不能同时占用一个CPU运行,具体什么时间让哪个进程占用CPU运行,以及运行多长时间,其调度控制非常复杂,这些控制是由Java虚拟机的线程调度程序自动完成的。然而某些时候,我们希望能自己控制线程的运行,例如编写提供某种服务的守护线程时,往往必须对某些线程的运行进行控制,Java为此提供了线程的控制机制。,11.5.4 控制线程的状态,线程的状态,线程的优先级,线程的生命周期,11.5.1 线程的状态,控制线程的运行实际上就是控制线程的状态,让线程在不同的状态间切换。线程有如下5种基本状态:(1)新生(New)(2)可运行(Run
11、nable)(3)运行(Running)(4)死亡(Dead)(5)阻塞(Blocked),返回,11.5.2 线程的优先级,在同时有多个线程处于可运行状态等待运行时,应该让哪个线程占用CPU,是由线程调度程序决定的,我们无法直接干预。然而,通过设置线程的优先级,可以影响调度程序的调度。线程的优先级类似于运算符的优先级,优先级最高的线程会被调度程序最先调入CPU运行。对于具有同样优先级的线程,将按照先进先出(IFIO)的原则,哪个线程先进入可运行状态等待CPU,哪个线程先运行。但这并不意味着优先级较低的线程一定要在优先级较高的线程结束后才能运行,优先级低的线程只是获得CPU的机会较少而已。,返
12、回,11.5.3 线程的生命周期,所谓线程的生命周期,是指线程从创建之初到运行完毕的过程,就像人的整个生命周期从出生,经过童年、少年、青年、中年、老年,直到去世一样。线程的整个生命周期就是在各种状态间切换的,,返回,11.5.4 控制线程的状态,事实上,此前我们已经进行了一些线程的控制,如调用start()方法将线程从新生状态转为可执行状态,调用yield()方法将线程转为阻塞状态,也可以通过在run()方法中调用return语句将线程从运行状态转为死亡状态。在这些线程的状态间切换中,从可运行状态转为运行态只能由调度程序控制(通过设置线程优先级可以稍加干预),从新生状态转为可执行状态和从运行状
13、态转为死亡状态很简单,而从运行状态到阻塞状态或可运行状态,以及从阻塞状态到可运行状态的切换,才是我们真正感兴趣的,也是控制线程状态的重点,,返回,11.6 使用多线程应注意的问题,本节将讲述几个在Java多线程应用中应该特别注意的问题。,不建议使用的方法,多线程的效率,饿死,死锁,11.6.1 多线程的效率,多线程的优点是显而易见的,它使得我们可以同时处理多个任务,可以通过守护线程在后台提供某种通用性的服务,可以监视某种情况的发生等等。它的目的是让我们更有效地运用计算机资源,特别是其中最重要的CPU资源,这一优点在多CPU系统中尤其突出。这其中最经典的例子就是,在一个线程等待I/O操作期间,让
14、其他线程占用CPU运行,以提高CPU的利用率。,返回,11.6.2 饿死,在介绍线程优先级时曾提及,优先级较低的线程不是只能在优先级较高的线程结束后才能运行,优先级低的线程只是获得 CPU 的机会较少,Java虚拟机的调度程序保证每个线程都能运行。这看来似乎并没有问题,然而调度程序的这种保证并没有时间限制,也就是说它不能保证在一个可以预见的时间范围内,让优先级低的线程一定能运行,没人愿意看到一个线程在等待CPU一个小时后,仍没有得到运行机会的情况出现。这种低优先级线程在较长时间内无法获得运行机会的现象就是所谓的“饿死”。,返回,11.6.3 死锁,所谓独占性资源是指被一个线程占用后,除非该线程放弃,否则其他线程不能抢占的资源,也可以理解为同时只能完成一个任务的资源。CPU就是典型的独占性资源,其他如打印机、软盘等大部分I/O设备也都是独占性资源。,返回,11.6.4 不建议使用的方法,Thread中的方法stop(),suspend()和resume()是Java 2不建议使用的。,返回,11.7 上 机 指 导,在本次上机指导中,将给出一个计时器实例,针对前面几节中的内容进行实际上机练习,目的是让读者了解多线程在实际中的应用,并进一步熟悉线程的控制。通过理解该例,读者将会具备基本的多线程编程能力。,