《Java程序设计基础》第12章:多线程处理.ppt

上传人:max****ui 文档编号:10979695 上传时间:2020-04-16 格式:PPT 页数:46 大小:243.50KB
返回 下载 相关 举报
《Java程序设计基础》第12章:多线程处理.ppt_第1页
第1页 / 共46页
《Java程序设计基础》第12章:多线程处理.ppt_第2页
第2页 / 共46页
《Java程序设计基础》第12章:多线程处理.ppt_第3页
第3页 / 共46页
点击查看更多>>
资源描述
第12章多线程处理 学习重点 程序 进程与线程的概念区别Java中Thread的4种状态Thread的典型应用 第12章多线程处理 12 1线程的基本概念12 1 1程序与进程12 1 2进程与线程12 1 3Java的线程模型12 2线程的基本结构与使用方法12 2 1线程的生命周期12 2 2定制run 方法 12 3线程的管理12 3 1同步12 3 2优先级12 3 3有关线程的其他概念12 4用于制作动画的线程12 4 1动画程序框架12 4 2帧的画法12 4 3避免闪动12 4 4使用图片12 5练习题 12 1线程的基本概念 12 1 1程序与进程程序是一个静态的概念 它是指用某种语言编写的 符合一定语法规则并具有一定功能的一些指令的集合 程序往往有开始 处理和结束3个部分组成 它的表现形式可能是一个文件 可能是一组程序的集合 如一个大的应用程序 总之它是一个完整的静态概念 进程暂时简单理解为一段正在运行的程序 它是已经开始执行 但尚未结束的一种程序的状态 因此 相对于程序来说 进程可以看作是一个动态的概念 进程通常是一个可执行程序在内存中的一个完整副本 每个进程都有自己的数据段 栈段和代码段 因此它是一段完整的程序 在内存中占据较大的空间 进程调度 能够实现多任务的操作系统通过一定的算法将这样的一个个进程排列成一个或多个队 一般情况下是采用FIFO 即先进先出的算法 有些进程由于其应用的特殊性可能会提高优先级 被排列在队伍的中间或者前面 同样 有些进程由于涉及过多IO操作 可能会被执行到IO时 就调度到队伍的最后 这些进程按照队伍排列好的顺序轮流被操作系统调入CPU执行 通常情况下 每个程序执行一个时间片就被调度下来 如果中间遇到有IO操作或者别的相对于CPU来说比较慢的操作 或者有其他优先级高的程序需要运行 该进程可能会被提前调度出CPU 时间片 操作系统自己管理的一个参数 即指通常情况下每个进程连续在CPU上执行的时间长度 12 1 2进程与线程 线程简单的说就是一种轻量级的进程 它是一个程序中实现单一功能的一个指令序列 它是一个程序的一部分 不能单独运行 它必须在一个程序之内运行 也就是说在一个进程的环境之中运行 我们可以将一个进程按不同功能划分为多个线程 将线程在CPU上进行开销很小的调度 因为线程只有自己的栈段和程序计数器 而没有独立的数据段和代码段 因此 这种调度是非常轻量级的工作 12 1 3Java的线程模型 Java的线程模型图中一个程序 Prog Cntr 有N个线程组成 它们都有自己的栈段 LocalStack 而所有的线程都可以共享GlobalData 它们必须在这个程序的环境下执行 利用多线程机制 Java使整个执行环境是异步的 在Java程序里没有主消息循环 Java语言里 线程表现为线程类 Thread 线程类封装了所有需要的线程操作控制 线程对象和运行线程 线程对象可以看做是运行线程的控制面板 线程类是控制线程行为的惟一手段 例12 1操纵当前线程 程序代码打印当前线程实际上是t toString 方法的省略写法 该方法能够将这个线程的名字 优先级和其所属的线程组 ThreadGroup 打印出来 Sleep 方法是使用类变量执行的 原因在于这个方法是一个静态方法 只能用类变量访问 在线程进入sleep状态时 可能会被其他线程唤醒 这个时候就会进入InterruptedException 运行结果如下图 12 2线程的基本结构与使用方法 和Applet以及其他一些重要Java类一样 一个线程也有一些特定的状态以及和这些状态相对应的方法 利用这些方法 我们就可以对Thread进行控制以及定义其功能 12 2 1线程的生命周期 在线程的一个完整生命周期中 共有4种可能的状态以及5种常用的方法 例12 2使用线程的时钟 一个称为Clock class的Applet 其功能是在屏幕上显示客户端的当前系统时间 这个例子利用了线程方法来实现了读取本地时间的功能 其运行结果如图所示 1 创建新线程 Clock程序是在其start 方法中创建新线程的 其源代码如下 publicvoidstart if clockThread null clockThread newThread this Clock clockThread start 在上述代码中的粗体字部分执行过后 对象clockThread就进入了新线程状态 一个新线程仅仅是一个空对象 它并没有被分配给任何系统资源 2 启动线程 publicvoidstart if clockThread null clockThread newThread this Clock clockThread start 线程的start 方法的作用是为这个线程对象创建它所需要的全部系统资源 安排它的执行时间 并调用线程的run 方法 事实上 CPU在某一时刻只能执行一个程序 所有处于running状态的线程不可能同时运行 因此 线程也必须向进程那样排好队 等待被调度到CPU上去 Clock类中的run 方法 源代码如下 publicvoidrun ThreadmyThread Thread currentThread while clockThread myThread repaint try Thread sleep 1000 catch InterruptedExceptione 在循环体中 Clock类首先把自己重画一次 然后让这个线程睡眠1秒 Clock重画自己时实际上调用的就是Applet的paint 方法 源代码如下 publicvoidpaint Graphicsg Datenow newDate g drawString now getHours now getMinutes now getSeconds 5 10 3 阻塞线程 线程进入阻塞 NonRunnable 状态可能由以下3种情况造成 调用sleep 方法 调用了wait 方法以等待某种事件的发生 线程等待IO请求的完成 对于上述3种造成阻塞的情况 分别有对应的不同条件可以使线程恢复到运行状态 对于被调用了sleep 方法的 必须等待sleep时间过去 对于调用了wait 方法的线程 必须等到其他线程通过notify或者notifyAll方法通知该线程它要等待的事件发生为止 关于线程间的通讯将在12 3节中介绍 对于被IO阻塞的线程 必须要等IO操作完成 4 停止线程 线程的停止通常是等待run 方法执行结束之后自然地停止下来 线程被停止之后就转入到dead状态 例如 在Clock类中 run 方法的循环结束条件也就是线程结束的条件 即while clockThread myThread 这个条件的含义是当前线程不是clockThread的时候 这个线程就停止 这就意味着 当用户离开这个页面的时候 Applet会调用Clock的stop 方法 源代码如下 publicvoidstop clockThread null 12 2 2定制run 方法 1 继承线程类最简单的定制run 的方法就是继承java lang Thread类 然后重载run 方法 在其中定义自己要做的事情 例12 3线程的继承 程序代码这个程序首先继承了Thread类 里面有两个方法 第一个是SimpleThread的构造器 它调用了父类的构造器 第二个是run 方法 在重载run 方法时 它实现了这样一个逻辑 即循环十次 在循环体中 打印出该线程的名称 然后取一个1秒以内的随机数 调用sleep 方法 全部运行完毕打印出 DONE 例12 4多线程的CPU调度 我们编写一段程序创建两个SimpleThread对象 看看它的执行结果 程序代码执行结果如图所示 2 使用Runnable接口 开发线程应用程序的第二个方法是实现Runnable接口 我们在上一小节中用到的例子Clock就使用了这种方法 Clock类是一个Applet 因此 我们不可能使用继承Thread类的方法 通过Runnable接口来实现多线程是一种更加实用的方法 Clock java的源代码 在使用Runnable接口时 主要注意以下3点 该类必须实现Runnable接口 即implementsRunnable 在该类中必须将新创建的线程与自己联系起来 即通过Thread的构造器的一个参数 把自己的指针交给线程 以便线程在执行的时候寻找自己的run 方法 即clockThread newThread this Clock 必须实现run 方法 在run 方法中实现需要线程去完成的功能 即publicvoidrun 12 3线程的管理 由于线程是多个小程序在同时执行的过程 这些线程之间可能需要共享数据 可能需要互通消息 即使没有任何关系 也还会涉及到哪个线程先上CPU 哪个后上等一系列问题 因此 对应用程序必须对线程进行必要的管理 12 3 1同步 那些需要共享对象和数据的多个线程 必须互相了解对方的状态以及活动 例如 不同的线程在同一时间内不能存取同一数据 又如 一个线程向文件中写数据 而另一个线程从这个文件中读取数据 它们之间必须协调好步调 否则程序就可能会出错 因此 对于这些并发执行却又共享数据的线程 我们必须采取某些方法对它们进行同步管理 1 生产者 消费者模型 假设一个生产者程序Producer java产生0 9的数字 存放在一个CubbyHole对象中 并将产生的数字打印出来 然后睡眠一个随机数产生的时间 再进行下一次工作 生产者程序代码Consumer java类的任务是一旦CubbyHole对象中的数字填写好了 就来读取这个数字 这里的CubbyHole对象就是Producer类使用的那个对象 消费者程序代码 分析一下这样的两个程序如果运行起来可能出现哪些问题 第一 假如Producer被调度进CPU的次数多 而导致Producer产生了两个数字 而Consumer只读了一个 那么下次Consumer再读取数字时就可能丢失掉一个 例如 Consumer 1got 3Producer 1put 4Producer 1put 5Consumer 1got 5第二 假如倒过来 Consumer比Producer快 那么可能导致Consumer读取重复的数字 例如 Producer 1put 4Consumer 1got 4Consumer 1got 4Producer 1put 5我们必须对这两个程序进行同步控制 所谓同步控制就是按照要求 即Producer写入一个数字 Consumer读取一个数字 让两个线程步调协调 2 对线程进行同步 在这个例子中 我们的同步控制至少要做到两点 这两个线程不能同时操作CubbyHole对象 方法 Java线程可以通过对共享对象加锁来控制其他线程对这个对象的访问 实现这类功能常用的程序框如下 程序代码 两个线程互相之间还必须能够通知对方 我已经做完了操作 你可以来了 方法 Thread类提供了3个与此相关的方法 即 wait notify 和 notifyAll 程序代码 执行这个程序的主程序如下 publicclassProducerConsumerTest publicstaticvoidmain String args CubbyHolec newCubbyHole Producerp1 newProducer c 1 Consumerc1 newConsumer c 1 p1 start c1 start 执行结果如图所示 3 同步带来的问题 同步程序如果出现问题或者遇到异常 就可能出现死锁和饥饿两类问题 所谓死锁就是指多个进程或线程协同作用 互相干涉 而导致一个或者更多进程永远等待下去 而当一个进程或者线程永久性地占有资源 使得其他进程得不到该资源 就发生了饥饿 12 3 2优先级 通常情况下 系统会为每个Java线程赋予一个介于最大优先级和最小优先级之间的数 作为该线程的优先级 通常是从1 10之间的一个数字 数字越大表明任务越紧急 Java的线程机制是不支持时间片的 优先级高的线程先被执行 直到其结束或者是由于某些原因被挂起 例如进入等待状态 睡眠状态 IO阻塞等 其他线程才可能被调度进来 Java对具有相同优先级的线程的处理是随机的 Java线程模型涉及可以动态更改线程优先级 高优先级的线程可以安排在低优先级线程之前完成 一个应用程序可以通过使用线程中的方法setPriority int 来设置线程的优先级大小 另外 线程还可以通过yield 方法将自己被执行的权限让给同样优先级的线程 12 3 3有关线程的其他概念 1 线程组线程是被个别创建的 但可以将它们归类到线程组中 以便于调试和监视 编程时只能在创建线程的同时将它与一个线程组相关联 例如 在本章开始部分讲到的操纵线程的例子中就曾经讲到 将当前线程打印出来的程序中 输出结果中的第3个参数 main 就是该线程的线程组 2 守护线程 Daemon 通常情况下 Java系统支持两类线程 用户线程和守护线程 用户线程是那些完成一定用户定义功能的线程 守护线程是那些仅提供辅助功能的线程 守护线程的功能往往比较底层 Thread类提供了setDaemon 函数 用于将一个线程设置为守护线程 守护线程是在Java程序运行到所有用户线程终止之后 由系统将它强迫终止掉的 在Java虚拟机中 即使在main 方法结束以后 如果另一个用户线程仍在运行 则守护线程仍然可以继续运行 3 避免使用的方法 在Java1 1和Java1 2版本中提供了一些控制线程的方法 如stop suspend 和resume 等 这些函数在Java虚拟机中可能引入一些无法预知或者无法调式的错误 尽量不要使用它们 4 在什么情况下使用线程 至于什么情况下使用线程通常取决于程序的需求 决定是否在应用程序中使用多线程时 首先要估计可以并行运行的代码量 另外还要记住以下两点 使用多线程并不一定会增加CPU的处理能力 基于Internet的应用有必要使用多线程 12 4用于制作动画的线程 在Java中制作动画无论使用什么方法 原理都是一样的 就是要让图片或者图像非常快的在屏幕上连续移动或者交替显示 速度大约是每秒20帧左右 要想实现每秒刷新屏幕多次 就需要使用线程 这个线程的主要任务是实现一个动画循环 在循环中跟踪当前Frame的状态 并且负责定期请求刷新屏幕 12 4 1动画程序框架 下面我们给出一个使用Applet和线程制作动画的程序框架 例12 6动画程序框架程序代码 12 4 2帧的画法 在上面的框架中 我们调用Applet的repaint 方法重画各帧 repaint 方法实质上调用的是Applet的paint 方法 例12 7文字动画publicvoidpaint Graphicsg g setColor Color black g drawString Frame frame 0 30 在paint 方法中 我们先简单地输出一个字符串 把这个方法加入到前面的框架中 运行结果如图所示 例12 8曲线动画 下面再通过一个数学的算法画一个比较复杂的图案 paint 方法的代码如下 publicvoidpaint Graphicsg Dimensiond size inth d height 2 for intx 0 x d width x inty1 int 1 0 Math sin x frame 0 05 h inty2 int 1 0 Math sin x frame 0 07 h g drawLine x y1 x y2 运行结果如图所示 12 4 3避免闪动 避免闪动的方法有两个 一是通过重载update 方法 二是使用buffer 通常情况下 AWT接收到Applet重画请求时 就调用Applet的update 方法 默认条件下 update 方法都是清空Applet的背景 然后再调用paint 方法 我们可以通过重写update 方法 避免系统将Applet的整个背景全部清空 从而减少视觉上画面闪动的感觉 源代码如下 程序代码 12 4 4使用图片 事实上比较常见的动画是通过显示或者播放图片来实现的 下面我们把程序改造成使用图片演示的动画 使用图片需要重载paintFrame 方法 而获取图片对象的指针需要使用getImage 方法 1 移动图片 例子 让汽车绕着地球移动 其中地球是背景图片 而汽车是真正移动的图片 其中 我们只需要修改update 方法和paintFrame 方法 其中update 中使用了缓冲区方法来消除闪动 程序代码运行结果如图12 9所示 2 播放图片 在初始化的时候 利用循环语句一次性地把所有图片都装载进来 源代码如下 frames newImage 10 for inti 1 i 10 i frames i 1 getImage getCodeBase T i gif 然后 在paintFrame 方法中 利用文件名的规律 使用一个取余函数 源代码如下 publicvoidpaintFrame Graphicsg g drawImage frames frame 10 0 0 null 这个程序的运行结果如图所示 可以看到Duke在向大家招手 12 5练习题 1 选择题 1 进程是指 A 一段程序B 正在运行的程序C 一个 java文件D 一个 class文件 2 实现一个线程的执行有几种方法 A 一种B 两种C 三种D 以上都不对 2 编程题 1 将TwoThreadDemo修改为启动3个线程 并改造成Applet 放在浏览器中执行 请注意应该使用什么样的方式来实现多线程处理 2 根据生产者 消费者模型编制一个程序 实现4人打牌的基本逻辑 即4个人有一个出牌顺序 第一个人出牌完毕 第二个人才能出 显示任意信息表示某人出的牌 提示 使用某个类变量存储出牌顺序 注意同步控制
展开阅读全文
相关资源
相关搜索

当前位置:首页 > 图纸专区 > 课件教案


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!