好文档就是一把金锄头!
欢迎来到金锄头文库![会员中心]
电子文档交易市场
安卓APP | ios版本
电子文档交易市场
安卓APP | ios版本

Java语言程序设计实验指导电子教案第08章.ppt

24页
  • 卖家[上传人]:人***
  • 文档编号:575181329
  • 上传时间:2024-08-17
  • 文档格式:PPT
  • 文档大小:431.52KB
  • / 24 举报 版权申诉 马上下载
  • 文本预览
  • 下载提示
  • 常见问题
    • 第第8章章 多线程编程多线程编程 支持多线程编程是Java语言的又一大特色多线程是相对于进程或单线程而言的,它具有并发性、执行效率高的特点本章将对Java中的多线程编程作初步介绍 8.1 8.1 多线程编程概述多线程编程概述8.2 8.2 线程的创建线程的创建8.3 8.3 线程的优先级线程的优先级8.4 8.4 线程同步线程同步8.5 8.5 线程间通信线程间通信8.6 8.6 线程的控制线程的控制[Return] 8.1 8.1 多线程编程概述多线程编程概述 本节介绍多线程编程的基础知识,包括多线程的基本概念、Java的线程模型(线程优先级、同步性、消息传递)等方面的内容 8.1.1 8.1.1 什么是多线程什么是多线程8.1.2 8.1.2 JavaJava线程模型线程模型[Return] 8.1.1 8.1.1 什么是多线程什么是多线程    同 其 他 大 多 数 编 程 语 言 不 同 , Java内 置 支 持 多 线 程 编 程(multithreaded programming)多线程程序包含两条或两条以上并发运行的部分,把程序中每个这样的部分都叫作一个线程(thread)。

      每个线程都有独立的执行路径,因此多线程是多任务处理的一种特殊形式   读者可能知道多任务处理,它实际上被所有的现代操作系统所支持然而,多任务处理有两种截然不同的类型:基于进程的和基于线程的搞清楚两者的区别是很重要的对大多数读者来说,基于进程的多任务处理是更熟悉的形式进程(process)本质上是一个执行的程序因此基于进程的多任务处理的特点是允许你的计算机同时运行两个或更多的程序举例来说,基于进程的多任务处理使你在运用文本编辑器的时候可以同时运行Java编译器在基于进程的多任务处理中,程序是调度程序所分派的最小代码单位    而在基于线程(thread-based)的多任务处理环境中,线程是最小的执行单位这意味着一个程序可以同时执行两个或者多个任务的功能例如,一个文本编辑器可以在打印的同时格式化文本所以,多进程程序处理“大图片”,而多线程程序处理细节问题[Return]    多线程程序比多进程程序需要更少的管理费用进程是重量级的任务,需要分配给它们独立的地址空间进程间通信是昂贵和受限的进程间的转换也是很需要花费的另一方面,线程是轻量级的选手它们共享相同的地址空间并且共同分享同一个进程线程间通信是便宜的,线程间的转换也是低成本的。

      当Java程序使用多进程任务处理环境时,多进程程序不受Java的控制,而多线程则受Java控制    多线程可帮助你编写出CPU最大利用率的高效程序,使得空闲时间保持最低这对Java运行的交互式的网络互连环境是至关重要的,因为空闲时间是公共的例如,网络的数据传输速率远低于计算机处理能力,而本地文件系统资源的读写速度也远低于CPU的处理能力当然,用户输入也比计算机慢很多在传统的单线程环境中,程序必须等待每一个这样的任务完成以后才能执行下一步——尽管CPU有很多空闲时间多线程使你能够获得并充分利用这些空闲时间 8.1.2 8.1.2 JavaJava线程模型线程模型 Java运行系统在很多方面依赖于线程,所有的类库设计都考虑到多线程实际上,Java使用线程来使整个环境异步这有利于通过防止CPU循环的浪费来减少无效部分   为更好地理解多线程环境的优势,我们可以将它与它的对照物相比较单线程系统的处理途径是使用一种叫作轮询的事件循环方法在该模型中,单线程控制在一无限循环中运行,轮询一个事件序列来决定下一步做什么一旦轮询装置返回信号表明已准备好读取网络文件,事件循环调度控制管理到适当的事件处理程序。

      直到事件处理程序返回,系统中没有其他事件发生这就浪费了CPU时间这导致了程序的一部分独占了系统,阻止了其他事件的执行总的来说,单线程环境,当一个线程因为等待资源时阻塞(block,挂起执行),整个程序停止运行 Java多线程的优点就在于取消了主循环/轮询机制一个线程可以暂停而不影响程序的其他部分例如,当一个线程从网络读取数据或等待用户输入时产生的空闲时间可以被利用到其他地方多线程允许活的循环在每一帧间隙中沉睡一秒而不暂停整个系统在Java程序中出现线程阻塞,仅有一个线程暂停,其他线程继续运行 线程存在多种状态线程可以正在运行(running),只要获得了CPU时间它就可以运行;运行的线程可以被挂起(suspend),并临时中断它的执行;一个挂起的线程可以被恢复(resume),允许它从停止的地方继续运行;一个线程可以在等待资源时被阻塞(block);在任何时候,线程可以被终止(terminate),这将立即中断运行一旦终止,线程不能被恢复线程的各状态间关系见教材P190页图8-1所示 下面简要介绍与下面简要介绍与JavaJava线程相关的几个概念线程相关的几个概念 Java给每个线程安排优先级以决定与其他线程比较时该如何对待该线程。

      线程优先级是详细说明线程间优先关系的整数作为绝对值,优先级是毫无意义的;当只有一个线程时,优先级高的线程并不比优先级低的线程运行的快相反,线程的优先级是用来决定何时从一个运行的线程切换到另一个这叫“上下文转换”(context switch)决定上下文转换发生的规则很简单: l线程可以自动放弃控制在I/O未决定的情况下,睡眠或阻塞由明确的让步来完成在这种假定下,所有其他的线程被检测,准备运行的最高优先级线程被授予CPU l线程可以被高优先级的线程抢占在这种情况下,低优先级线程不主动放弃,处理器只是被先占——无论它正在干什么——处理器被高优先级的线程占据基本上,一旦高优先级线程要运行,它就执行这叫做有优先级的多任务处理 当两个相同优先级的线程竞争CPU周期时,情形有一点复杂对于Windows这样的操作系统,等优先级的线程是在循环模式下自动划分时间的对于其他一些非Windows操作系统而,如Solaris 2.x,等优先级线程相对于它们的对等体自动放弃如果不这样,其他的线程就不会运行 1 1.线程优先级.线程优先级 2 2.同步性.同步性    由于多线程在程序中引入了一个异步行为,故在需要的时候必须有加强同步性的方法。

      举例来说,如果你希望两个线程相互通信并共享一个复杂的数据结构,例如链表序列,就需要某些方法来确保它们没有相互冲突也就是说,你必须防止一个线程写入数据而另一个线程正在读取链表中的数据为此,Java在进程间同步性的老模式基础上实 行 了 另 外 的 一 种 方 法 : 管 程 ( monitor) 管 程 是 一 种 由C.A.R.Hoare首先定义的控制机制你可以把管程想象成一个仅控制一个线程的小盒子一旦线程进入管程,所有线程必须等待直到该线程退出了管程用这种方法,管程可以用来防止共享的资源被多个线程操纵 很多多线程系统将管程作为程序必须明确的引用和操作的对象但Java提供一个清晰的解决方案,不提供“Monitor”类;相反,每个对象都拥有自己的隐式管程,当对象的同步方法被调用时管程自动载入一旦一个线程包含在一个同步方法中,没有其他线程可以调用相同对象的同步方法这就使你可以编写非常清晰和简洁的多线程代码,因为同步支持是语言内置的 3 3.消息传递.消息传递    当把程序分成若干线程后,就要定义各线程之间的联系用大多数其他语言规划时必须依赖于操作系统来确立线程间通信,这样当然要增加花费。

      然而,Java提供了多线程间谈话清洁的、低成本的途径——通过调用所有对象都有的预先确定的方法Java的消息传递系统允许一个线程进入一个对象的一个同步方法,然后在那里等待,一直等到其他线程明确通知它出来[Return] Java的多线程系统建立于Thread类、方法以及共伴接口Runnable基础上Thread类封装了线程的执行既然不能直接引用运行着的线程的状态,就要通过它的代理处理它于是Thread 实例产生了为创建一个新的线程,程序中必须扩展Thread 或实现Runnable接口Thread类定义了好几种方法来帮助管理线程,见教材P192页表8-1中所列4 4..ThreadThread类和类和RunnableRunnable接口接口 8.2 8.2 线程的创建线程的创建 本节介绍在Java中如何创建线程主要内容包括主线程、多线程的创建、相关方法的使用等 8.2.1 8.2.1 关于主线程关于主线程8.2.2 8.2.2 创建一个线程创建一个线程8.2.3 8.2.3 创建多线程创建多线程8.2.4 8.2.4 使用使用isAliveisAlive()()和和join()join()[Return] 8.2.1 8.2.1 关于主线程关于主线程      当Java程序启动时,一个线程立刻运行,该线程通常就叫做程序的主线程(main thread),因为它是程序开始时就执行的。

      主线程的重要性主要体现在两方面: l它是产生其他子线程的线程; l通常它必须最后完成执行,因为它执行各种关闭动作      尽管主线程在程序启动时自动创建,但它可以由一个Thread对象控制为此,必须调用方法currentThread()获得它的一个引用,currentThread()是Thread类的公有的静态成员它的一般形式如下    static Thread currentThread()      该方法返回一个调用它的线程的引用一旦获得主线程的引用,就可以像控制其他线程那样控制主线程 下面我们考察一下教材P192~193页的程序代码       在上面的程序中,当前线程(当然是主线程)的引用通过调用currentThread()获得,该引用保存在局部变量t中然后,程序显示了线程的信息接着,程序调用setName()改变线程的内部名称,线程信息又被显示然后,一个循环数从5开始递减,每数一次暂停一秒暂停是由sleep()方法来完成的,sleep()语句明确规定延迟时间是1毫秒请读者注意循环外的try/ catch块Thread类的sleep()方法可能引发一个InterruptedException异常,这种情形会在其他线程想要打搅沉睡线程时发生。

      本例只是打印了它是否被打断的消息在实际的程序中,必须灵活处理此类问题 [Return] 8.2.2 8.2.2 创建一个线程创建一个线程大多数情况,通过实例化一个Thread对象来创建一个线程Java定义了两种方式: l实现Runnable 接口; l以继承Thread类的方式创建线程最简单的方法就是创建一个实现Runnable 接口的类,Runnable抽象了一个执行代码单元可以通过实现Runnable接口的方法创建每一个对象的线程为实现 Runnable 接口,一个类仅需实现一个run()的简单方法,该方法声明如下:public void run()在run()中,可以定义代码来构建新的线程重要的是:run()方法能够像主线程那样调用其他方法,引用其他类,声明变量仅有的不同是:run()在程序中确立另一个并发的线程执行入口当run()返回时,该线程结束在已经创建了实现Runnable接口的类以后,需要在类内部实例化一个Thread类的对象Thread 类定义了好几种构造函数我们会用到的如下:Thread(Runnable threadOb, String threadName)在该构造函数中,threadOb是一个实现Runnable接口类的实例。

      这定义了线程执行的起点,新线程的名称由threadName定义建立新的线程后,它并不运行直到调用其start()方法,该方法在Thread 类中定义从本质上讲,start()执行的是一个对run()的调用start()方法声明如下:void start( )下面我们分别对这两种方法进行介绍: 1 1.实现.实现RunnableRunnable接口接口 2 2.扩展.扩展ThreadThread 创建线程的另一个途径是创建一个新类来扩展Thread类,然后再创建该类的实例当一个类继承Thread时,它必须重载run()方法,这个run()方法是新线程的入口同时,它也必须调用start()方法去启动新线程执行 [Return] 到这里,读者可能会奇怪为什么Java有两种创建子线程的方法,哪一种更好呢所有的问题都归于一点Thread类定义了多种方法可以被派生类重载对于所有的方法,唯一的必须被重载的是run()方法这当然是实现Runnable接口所需的同样的方法很多Java程序员认为类仅在它们被加强或修改时被扩展因此,如果你不重载Thread的其他方法,最好只实现Runnable 接口,这当然由自己决定。

      在本章的其他部分,我们应用实现Runnable接口的类来创建线程 3 3.选择合适的方法.选择合适的方法 8.2.3 8.2.3 创建多线程创建多线程 到目前为止,我们仅用到两个线程:主线程和一个子线程然而,我们完全可以创建所需的更多线程例如,教材P197~198页的程序创建了3个子线程详细分析该程序 [Return] 8.2.4 8.2.4 使用使用isAliveisAlive()()和和join()join()   如前所述,我们一般是希望主线程最后结束在上面的例子中,这点是通过在main()中调用sleep()来实现的,经过足够长时间的延迟以确保所有子线程都先于主线程结束然而,这并不是一个好的解决方法因为有时候存在这个问题:一个线程如何知道另一线程已经结束?幸运的是,Thread类提供了解决此问题的有效方法  有两种方法可以判定一个线程是否结束:第一,可以程中调用isAlive()这种方法由Thread定义,它的一般形式如下final boolean isAlive()  如果所调用线程仍在运行,isAlive()方法返回true,如果不是则返回false  但isAlive()很少用到,等待线程结束的更常用的方法是调用join(),描述如下final void join() throws InterruptedException 该方法等待所调用线程结束,该名字来自于要求线程等待直到指定线程参与的概念。

      join()的附加形式允许给等待指定线程结束定义一个最大时间详细分析教材P199~200页的程序 [Return] 8.3 8.3 线程的优先级线程的优先级        线程优先级被线程调度用来判定何时某个线程允许运行理论上,优先级高的线程比优先级低的线程获得更多的CPU时间实际上,线程获得的CPU时间通常由包括优先级在内的多个因素决定一个优先级高的线程自然比优先级低的线程优先        理论上,等优先级线程有同等的权利使用CPU,但你必须小心需要记住的是,Java是被设计成能在很多环境下工作的不同环境下实现多任务处理从本质上来看是可能的为安全起见,等优先级线程有时候也受到控制这保证了所有线程在无优先级的操作系统下都有机会运行实际上,在无优先级的环境下,多数线程仍然有机会运行,因为很多线程不可避免地会遭遇阻塞,例如等待输入输出遇到这种情形,阻塞的线程挂起,其他线程运行但是如果你希望多线程执行得顺利的话,最好不要采用这种方法同样,有些类型的任务是占CPU的对于这些支配CPU类型的线程,有时你希望能够支配它们,以便使其他线程可以运行       设置线程的优先级,用setPriority()方法,该方法也是Thread的成员。

      它的通常形式为 final void setPriority(int level)这里,level指定了对所调用的线程的新的优先权的设置Level的值必须在MIN_PRIORITY到MAX_PRIORITY范围内通常,它们的值分别是1和10要返回一个线程为默认的优先级,指定NORM_PRIORITY,通常值为5这些优先级在Thread中都被定义为final型变量 [Return] 8.4 8.4 线程同步线程同步 当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用,达到此目的的过程叫做同步(synchronization)Java为此提供了独特的、很有效的支持机制 8.4.1 8.4.1 使用同步方法使用同步方法8.4.2 8.4.2 同步语句同步语句[Return] 8.4.1 8.4.1 使用同步方法使用同步方法 同步的关键是管程(也叫信号量,即semaphore)的概念管程是一个互斥独占锁定的对象,或称互斥体(mutex)在给定的时间,仅有一个线程可以获得管程当一个线程需要锁定时,它必须进入管程所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程。

      这些其他的线程被称为等待管程  我们可以用两种方法同步化代码通过调用sleep(),call()方法允许执行转换到另一个线程两者都包括synchronized关键字的运用分析教材P204页的示例[Return] 8.4.2 8.4.2 同步语句同步语句    尽管在创建的类的内部创建同步方法是获得同步的简单和有效的方法,但它并非在任何时候都有效假设你想获得不为多线程访问设计的类对象的同步访问,也就是该类没有用到synchronized方法而且,该类不是你自己,而是第三方创建的,就不能获得它的源代码这样,就不能在相关方法前加synchronized修饰符怎样才能使该类的一个对象同步化呢?解决的方法很简单:只需将对这个类定义的方法的调用放入一个synchronized块内就可以了    下面是synchronized语句的一般形式   synchronized(object) {   // statements to be synchronized   } 其中,object是对同步对象的引用如果你想要同步的只是一个语句,那么不需要花括号一个同步块确保对object成员方法的调用仅在当前线程成功进入object管程后发生。

      [Return] 8.5 8.5 线程间通信线程间通信 前面的例子无条件地阻塞了其他线程异步访问某个方法Java对象中隐式管程的应用是很强大的,但是我们可以通过进程间通信达到更微妙的境界,这在Java中是很简单的 8.5.1 8.5.1 JavaJava中的线程通讯中的线程通讯8.5.2 8.5.2 关于死锁关于死锁[Return] 8.5.1 8.5.1 JavaJava中的线程通讯中的线程通讯   多线程通过把任务分成离散的和合乎逻辑的单元代替了事件循环程序线程还有另外一个优点:它远离了轮询轮询通常由重复监测条件的循环实现一旦条件成立,就要采取适当的行动这浪费了CPU时间   为避免轮询,Java包含了通过wait(),notify()和notifyAll()方法实现的一个进程间通信机制这些方法在对象中是用final方法实现的,所以所有的类都含有它们这三个方法仅在synchronized方法中才能被调用尽管这些方法从计算机科学远景方向上来说具有概念的高度先进性,实际中用起来却是很简单的 wait()wait()告知被调用的线程放弃管程进入睡眠直到其他线程进入相同管程并且调用notify()。

              notify()notify()恢复相同对象中第一个调用wait()的线程         notifyAllnotifyAll()()恢复相同对象中所有调用wait()的线程这些方法在Object中被声明,如下所示final void wait() throws InterruptedExceptionfinal void notify()final void notifyAll()wait()存在的另外的形式允许你定义等待时间分析教材P207~210页的程序段 [Return] 8.5.2 8.5.2 关于死锁关于死锁   需要避免的与多任务处理有关的特殊错误类型是死锁(deadlock)死锁发生在当两个线程对一对同步对象有循环依赖关系时例如,假定一个线程进入了对象X的管程而另一个线程进入了对象Y的管程如果X的线程试图调用Y的同步方法,它将像预料的一样被锁定而Y的线程同样希望调用X的一些同步方法,线程永远等待,因为为到达X,必须释放自己的Y的锁定以使第一个线程可以完成死锁是很难调试的错误,这是因为:第一,通常它极少发生,只有到两线程的时间段刚好符合时才能发生;第二,它可能包含多于两个的线程和同步对象。

      也就是说,死锁在比刚讲述的例子有更多复杂的事件序列的时候可以发生 为充分理解死锁,观察它的行为是很有用的教材P211~212页的例子生成了两个类,A和B,分别有foo()和bar()方法这两种方法在调用其他类的方法前有一个短暂的停顿主类,名为Deadlock,创建了A和B的实例,然后启动第二个线程去设置死锁环境foo()和bar()方法使用sleep()强迫死锁现象发生 程序死锁,需要按CTRL-C来结束程序在PC机上按CTRL-BREAK(或在Solaris下按CTRL-\)可以看到全线程和管程缓冲堆 [Return] 8.6 8.6 线程的控制线程的控制 本节讨论有关线程控制的问题,包括线程的挂起、恢复、终止等方面的问题 8.6.1 8.6.1 挂起、恢复和终止线程挂起、恢复和终止线程8.6.2 8.6.2 Java 2Java 2中的线程控制中的线程控制8.6.3 8.6.3 使用使用instanceofinstanceof[Return] 8.6.1 8.6.1 挂起、恢复和终止线程挂起、恢复和终止线程  有时,线程的挂起是很有用的例如,一个独立的线程可以用来显示当日的时间。

      如果用户不希望用时钟,线程被挂起在任何情形下,挂起线程是很简单的,一旦挂起,重新启动线程也是一件简单的事  挂起、终止和恢复线程机制在Java 2和早期版本中有所不同尽管你运用Java 2的途径编写代码,仍需了解这些操作在早期Java环境下是如何完成的例如,也许你需要更新或维护老的代码,就需要了解为什么Java 2会有这样的变化因为这些原因,下面内容说明了执行线程控制的原始方法,接着是Java 2的方法  先于Java2的版本(Java 1.1或更早版本),程序用Thread 定义的suspend() 和 resume() 来暂停和再启动线程它们的形式如下 final void suspend() final void resume() Thread类同样定义了stop()来终止线程,其形式如下: void stop() 一旦线程被终止,它不能被resume() 恢复继续运行 [Return] 8.6.2 8.6.2 Java 2Java 2中的线程控制中的线程控制  在Java 2中不能使用suspend(),resume()和stop() 方法来控制线程,读者也许会想那就没有办法来停止、恢复和结束线程,其实不然。

      相反,线程必须被设计成使用run()方法定期检查来判定线程是否应该被挂起,恢复或终止它自己的执行有代表性的,这由建立一个指示线程状态的标志变量来完成只要该标志设为“running”,run()方法必须继续让线程执行如果标志设为“suspend”,线程必须暂停若设为“stop”,线程必须终止当然,编写这样的代码有很多方法,但中心主题对所有的程序应该是相同的  教材P216~217页的例子显示了从Object继承的wait()和notify()方法怎样控制线程的执行该例与前面讲过的程序很像然而,不被赞同的方法都没有用到让我们思考程序的执行 NewTread类包含了用来控制线程执行的布尔型的实例变量suspendFlag它被构造函数初始化为falseRun()方法包含一个监测suspendFlag的同步声明的块如果变量是true,wait()方法被调用以挂起线程Mysuspend()方法设置suspendFlag为trueMyresume()方法设置suspendFlag为false并且调用notify()方法来唤起线程最后,main()方法被修改以调用mysuspend()和myresume()方法。

      [Return] 8.6.3 8.6.3 使用使用instanceofinstanceof   最后,我们附带介绍一下关键字instanceof有时,在运行时间内知道对象类型是很有用的例如,你有一个执行线程生成各种类型的对象,其他线程处理这些对象这种情况下,让处理线程在接受对象时知道每一个对象的类型是大有裨益的另一种在运行时间内知道对象的类型是很有用的情形是强制类型转换Java中非法强制类型转换导致运行时错误很多非法的强制类型转换在编译时发生然而包括类层次结构的强制类型转换可能是仅能在运行时间里被察觉的非法强制类型转换   例如,一个名为A的父类能生成两个子类B和C这样,在强制B对象转换为类型A或强制C对象转换为类型A都是合法的,但强制B对象转换为C对象(或相反)都是不合法的因为类型A的一个对象可以引用B或C但是你怎么知道,在运行时,在强制转换为C之前哪类对象被引用?它可能是A、B或C的一个对象如果它是B的对象,一个运行时异常被引发Java 提供运行时运算符instanceof来解决这个问题 instanceof运算符具有下面的一般形式:  object instanceof type   这里,object是类的实例,而type是类的类型。

      如果object是指定的类型或者可以被强制转换成指定类型,instanceof将它评估成true,若不是,则结果为false这样,instanceof是程序获得对象运行时类型信息的方法教材P218~219页的程序说明了instanceof的应用 [Return] 。

      点击阅读更多内容
      关于金锄头网 - 版权申诉 - 免责声明 - 诚邀英才 - 联系我们
      手机版 | 川公网安备 51140202000112号 | 经营许可证(蜀ICP备13022795号)
      ©2008-2016 by Sichuan Goldhoe Inc. All Rights Reserved.