《Java程序设计基础》第12章:多线程处理.ppt_第1页
《Java程序设计基础》第12章:多线程处理.ppt_第2页
《Java程序设计基础》第12章:多线程处理.ppt_第3页
《Java程序设计基础》第12章:多线程处理.ppt_第4页
《Java程序设计基础》第12章:多线程处理.ppt_第5页
已阅读5页,还剩41页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第12章 多线程处理,学习重点: 程序、进程与线程的概念区别 Java中Thread的4种状态 Thread的典型应用,第12章 多线程处理,12.1 线程的基本概念 12.1.1 程序与进程 12.1.2 进程与线程 12.1.3 Java的线程模型 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.3 Java的线程模型,Java的线程模型 图中一个程序(Prog. Cntr.)有N个线程组成,它们都有自己的栈段(Local Stack),而所有的线程都可以共享Global Data,它们必须在这个程序的环境下执行。,利用多线程机制,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()方法中创建新线程的,其源代码如下: public void start() if (clockThread = null) clockThread = new Thread(this, “Clock“); clockThread.start(); 在上述代码中的粗体字部分执行过后,对象clockThread就进入了新线程状态,一个新线程仅仅是一个空对象,它并没有被分配给任何系统资源。,2.启动线程,public void start() if (clockThread = null) clockThread = new Thread(this, “Clock“); clockThread.start(); 线程的start()方法的作用是为这个线程对象创建它所需要的全部系统资源,安排它的执行时间,并调用线程的run()方法。,事实上,CPU在某一时刻只能执行一个程序,所有处于running状态的线程不可能同时运行,因此,线程也必须向进程那样排好队,等待被调度到CPU上去。,Clock类中的run()方法。源代码如下: public void run() Thread myThread = Thread.currentThread(); while (clockThread = myThread) repaint(); try Thread.sleep(1000); catch (InterruptedException e) 在循环体中,Clock类首先把自己重画一次,然后让这个线程睡眠1秒。 Clock重画自己时实际上调用的就是Applet的paint()方法。源代码如下: public void paint(Graphics g) Date now = new Date(); 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()方法。 源代码如下: public void stop() 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接口,即implements Runnable。 在该类中必须将新创建的线程与自己联系起来,即通过Thread的构造器的一个参数,把自己的指针交给线程,以便线程在执行的时候寻找自己的run()方法,即clockThread = new Thread(this, “Clock”)。 必须实现run()方法,在run()方法中实现需要线程去完成的功能,即public void run() 。,12.3 线程的管理,由于线程是多个小程序在同时执行的过程,这些线程之间可能需要共享数据,可能需要互通消息,即使没有任何关系,也还会涉及到哪个线程先上CPU,哪个后上等一系列问题,因此,对应用程序必须对线程进行必要的管理。,12.3.1 同步,那些需要共享对象和数据的多个线程,必须互相了解对方的状态以及活动。 例如,不同的线程在同一时间内不能存取同一数据。 又如,一个线程向文件中写数据,而另一个线程从这个文件中读取数据,它们之间必须协调好步调,否则程序就可能会出错。 因此,对于这些并发执行却又共享数据的线程,我们必须采取某些方法对它们进行同步管理。,1. 生产者、消费者模型,假设一个生产者程序Producer.java产生09的数字,存放在一个CubbyHole对象中,并将产生的数字打印出来,然后睡眠一个随机数产生的时间,再进行下一次工作。 生产者程序代码 Consumer.java类的任务是一旦CubbyHole对象中的数字填写好了,就来读取这个数字,这里的CubbyHole对象就是Producer类使用的那个对象。 消费者 程序代码,分析一下这样的两个程序如果运行起来可能出现哪些问题,第一,假如Producer被调度进CPU的次数多,而导致Producer产生了两个数字,而Consumer只读了一个,那么下次Consumer再读取数字时就可能丢失掉一个,例如: Consumer #1 got: 3 Producer #1 put: 4 Producer #1 put: 5 Consumer #1 got: 5 第二,假如倒过来,Consumer比Producer快,那么可能导致Consumer读取重复的数字,例如: Producer #1 put: 4 Consumer #1 got: 4 Consumer #1 got: 4 Producer #1 put: 5 我们必须对这两个程序进行同步控制。所谓同步控制就是按照要求(即Producer写入一个数字,Consumer读取一个数字)让两个线程步调协调。,2. 对线程进行同步,在这个例子中,我们的同步控制至少要做到两点: 这两个线程不能同时操作CubbyHole对象。 方法 :Java线程可以通过对共享对象加锁来控制其他线程对这个对象的访问。 实现这类功能常用的程序框如下: 程序代码,两个线程互相之间还必须能够通知对方“我已经做完了操作,你可以来了”。 方法 : Thread类提供了3个与此相关的方法,即(wait),(notify)和(notifyAll)。 程序代码,执行这个程序的主程序如下:,public class ProducerConsumerTest public static void main(String args) CubbyHole c = new CubbyHole(); Producer p1 = new Producer(c, 1); Consumer c1 = new Consumer(c, 1); p1.start(); c1.start(); 执行结果如图所示,3. 同步带来的问题,同步程序如果出现问题或者遇到异常,就可能出现死锁和饥饿两类问题。 所谓死锁就是指多个进程或线程协同作用,互相干涉,而导致一个或者更多进程永远等待下去。 而当一个进程或者线程永久性地占有资源,使得其他进程得不到该资源,就发生了饥饿。,12.3.2 优先级,通常情况下,系统会为每个Java线程赋予一个介于最大优先级和最小优先级之间的数,作为该线程的优先级,通常是从110之间的一个数字,数字越大表明任务越紧急。 Java的线程机制是不支持时间片的。优先级高的线程先被执行,直到其结束或者是由于某些原因被挂起,例如进入等待状态、睡眠状态、IO阻塞等,其他线程才可能被调度进来。 Java对具有相同优先级的线程的处理是随机的。 Java 线程模型涉及可以动态更改线程优先级,高优先级的线程可以安排在低优先级线程之前完成。一个应用程序可以通过使用线程中的方法setPriority(int),来设置线程的优先级大小。另外,线程还可以通过yield()方法将自己被执行的权限让给同样优先级的线程。,12.3.3 有关线程的其他概念,1. 线程组 线程是被个别创建的,但可以将它们归类到线程组中,以便于调试和监视,编程时只能在创建线程的同时将它与一个线程组相关联。 例如,在本章开始部分讲到的操纵线程的例子中就曾经讲到,将当前线程打印出来的程序中,输出结果中的第3个参数“main”就是该线程的线程组。,2. 守护线程(Daemon),通常情况下,Java系统支持两类线程:用户线程和守护线程。用户线程是那些完成一定用户定义功能的线程,守护线程是那些仅提供辅助功能的线程,守护线程的功能往往比较底层。 Thread 类提供了setDaemon()函数,用于将一个线程设置为守护线程。 守护线程是在Java程序运行到所有用户线程终止之后,由系统将它强迫终止掉的。在Java虚拟机中,即使在main()方法结束以后,如果另一个用户线程仍在运行,则守护线程仍然可以继续运行。,3. 避免使用的方法,在Java1.1和Java 1.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 文字动画 public void paint(Graphics g) g.setColor(Color.black); g.drawString(“Frame “ + frame, 0, 30); 在paint()方法中,我们先简单地输出 一个字符串,把这个方法加入到前面的框 架中,运行结果如图所示。,例12.8 曲线动画,下面再通过一个数学的算法画一个比较复杂的图案,paint()方法的代码如下: public void paint(Graphics g) Dimension d = size(); int h = d.height / 2; for (int x = 0 ; x d.width ; x+) int y1 = (int)(1.0 + Math.sin(x - frame) * 0.05) * h); int y2 = (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 使用图片,事实上比较常见的动画是通

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论