1、第第12章章 多线程处理多线程处理学习重点:l程序、进程与线程的概念区别程序、进程与线程的概念区别lJavaJava中中ThreadThread的的4 4种状态种状态lThreadThread的典型应用的典型应用第第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
2、 动画程序框架 12.4.2 帧的画法 12.4.3 避免闪动 12.4.4 使用图片 12.5 练习题 12.1 12.1 线程的基本概念线程的基本概念12.1.1 程序与进程l程序是一个静态的概念,它是指用某种语言编写的,符合一定语程序是一个静态的概念,它是指用某种语言编写的,符合一定语法规则并具有一定功能的一些指令的集合。程序往往有开始、处法规则并具有一定功能的一些指令的集合。程序往往有开始、处理和结束理和结束3 3个部分组成。它的表现形式可能是一个文件,可能是个部分组成。它的表现形式可能是一个文件,可能是一组程序的集合一组程序的集合(如一个大的应用程序如一个大的应用程序),总之它是一个
3、完整的静,总之它是一个完整的静态概念。态概念。l进程暂时简单理解为一段正在运行的程序,它是已经开始执行,进程暂时简单理解为一段正在运行的程序,它是已经开始执行,但尚未结束的一种程序的状态,因此,相对于程序来说,进程可但尚未结束的一种程序的状态,因此,相对于程序来说,进程可以看作是一个动态的概念。进程通常是一个可执行程序在内存中以看作是一个动态的概念。进程通常是一个可执行程序在内存中的一个完整副本,每个进程都有自己的数据段、栈段和代码段,的一个完整副本,每个进程都有自己的数据段、栈段和代码段,因此它是一段完整的程序,在内存中占据较大的空间。因此它是一段完整的程序,在内存中占据较大的空间。l进程调
4、度:能够实现多任务的操作系统通过一定的算法将这样的进程调度:能够实现多任务的操作系统通过一定的算法将这样的一个个进程排列成一个或多个队,一般情况下是采用一个个进程排列成一个或多个队,一般情况下是采用FIFOFIFO,即先,即先进先出的算法,有些进程由于其应用的特殊性可能会提高优先级,进先出的算法,有些进程由于其应用的特殊性可能会提高优先级,被排列在队伍的中间或者前面。同样,有些进程由于涉及过多被排列在队伍的中间或者前面。同样,有些进程由于涉及过多IOIO操作,可能会被执行到操作,可能会被执行到IOIO时,就调度到队伍的最后。这些进程按时,就调度到队伍的最后。这些进程按照队伍排列好的顺序轮流被操
5、作系统调入照队伍排列好的顺序轮流被操作系统调入CPUCPU执行。通常情况下,执行。通常情况下,每个程序执行一个时间片就被调度下来,如果中间遇到有每个程序执行一个时间片就被调度下来,如果中间遇到有IOIO操作操作或者别的相对于或者别的相对于CPUCPU来说比较慢的操作,或者有其他优先级高的来说比较慢的操作,或者有其他优先级高的程序需要运行,该进程可能会被提前调度出程序需要运行,该进程可能会被提前调度出CPUCPU。l时间片:操作系统自己管理的一个参数。即指通常情况下每个进时间片:操作系统自己管理的一个参数。即指通常情况下每个进程连续在程连续在CPUCPU上执行的时间长度。上执行的时间长度。12.
6、1.2 12.1.2 进程与线程进程与线程l线程简单的说就是一种轻量级的进程,它是一个程序线程简单的说就是一种轻量级的进程,它是一个程序中实现单一功能的一个指令序列,它是一个程序的一中实现单一功能的一个指令序列,它是一个程序的一部分,不能单独运行,它必须在一个程序之内运行,部分,不能单独运行,它必须在一个程序之内运行,也就是说在一个进程的环境之中运行。也就是说在一个进程的环境之中运行。l我们可以将一个进程按不同功能划分为多个线程,将我们可以将一个进程按不同功能划分为多个线程,将线程在线程在CPUCPU上进行开销很小的调度,因为线程只有自上进行开销很小的调度,因为线程只有自己的栈段和程序计数器,
7、而没有独立的数据段和代码己的栈段和程序计数器,而没有独立的数据段和代码段。因此,这种调度是非常轻量级的工作。段。因此,这种调度是非常轻量级的工作。12.1.3 Java的线程模型Java的线程模型l图中一个程序图中一个程序(Prog.Cntr.)(Prog.Cntr.)有有N N个线程组成,它们都有自己的栈个线程组成,它们都有自己的栈段段(Local Stack)(Local Stack),而所有的线程都可以共享,而所有的线程都可以共享Global DataGlobal Data,它们,它们必须在这个程序的环境下执行。必须在这个程序的环境下执行。l利用多线程机制,利用多线程机制,JavaJav
8、a使整个执行环境是异步的,在使整个执行环境是异步的,在JavaJava程序里没有主消息循环。程序里没有主消息循环。lJavaJava语言里,线程表现为线程类语言里,线程表现为线程类(Thread)(Thread),线程类封,线程类封装了所有需要的线程操作控制。装了所有需要的线程操作控制。l线程对象和运行线程:线程对象可以看做是运行线程线程对象和运行线程:线程对象可以看做是运行线程的控制面板。线程类是控制线程行为的惟一手段。的控制面板。线程类是控制线程行为的惟一手段。例12.1 操纵当前线程l程序代码程序代码l打印当前线程实际上是打印当前线程实际上是t.toString()t.toString(
9、)方法的省略写法,该方法能方法的省略写法,该方法能够将这个线程的名字、优先级和其所属的线程组够将这个线程的名字、优先级和其所属的线程组(ThreadGroup)(ThreadGroup)打印出来。打印出来。lSleep()Sleep()方法是使用类变量执行的,原因在于这个方法是一个静方法是使用类变量执行的,原因在于这个方法是一个静态方法,只能用类变量访问。态方法,只能用类变量访问。l在线程进入在线程进入sleepsleep状态时,可能会被其他线程唤醒,这个时候就状态时,可能会被其他线程唤醒,这个时候就会进入会进入InterruptedExceptionInterruptedException。
10、l运行结果如下图运行结果如下图 12.2 线程的基本结构与使用方法l和和AppletApplet以及其他一些重要以及其他一些重要JavaJava类一样,一个类一样,一个线程也有一些特定的状态以及和这些状态相对线程也有一些特定的状态以及和这些状态相对应的方法,利用这些方法,我们就可以对应的方法,利用这些方法,我们就可以对ThreadThread进行控制以及定义其功能。进行控制以及定义其功能。12.2.1 线程的生命周期l在线程的一个完整生命周期中,共有4种可能的状态以及5种常用的方法 例例12.2 使用线程的时钟使用线程的时钟l一个称为一个称为Clock.classClock.class的的Ap
11、pletApplet,其功能是在屏幕上显示客户端,其功能是在屏幕上显示客户端的当前系统时间。这个例子利用了线程方法来实现了读取本地时的当前系统时间。这个例子利用了线程方法来实现了读取本地时间的功能,其运行结果如图所示。间的功能,其运行结果如图所示。1.创建新线程ClockClock程序是在其程序是在其start()start()方法中创建新线程的,其源代码如下:方法中创建新线程的,其源代码如下:public void start()if(clockThread=null)clockThread=new Thread(this,Clock);clockThread.start();在上述代码中的
12、粗体字部分执行过后,对象在上述代码中的粗体字部分执行过后,对象clockThreadclockThread就进入就进入了新线程状态,一个新线程仅仅是一个空对象,它并没有被分配了新线程状态,一个新线程仅仅是一个空对象,它并没有被分配给任何系统资源。给任何系统资源。2.启动线程public void start()if(clockThread=null)clockThread=new Thread(this,Clock);clockThread.start();线程的线程的start()start()方法的作用是为这个线程对象创建它所需要的全方法的作用是为这个线程对象创建它所需要的全部系统资源,安
13、排它的执行时间,并调用线程的部系统资源,安排它的执行时间,并调用线程的run()run()方法。方法。事实上,事实上,CPU在某一时刻只能执行一个程序,所有处于在某一时刻只能执行一个程序,所有处于running状态的线程不可能同状态的线程不可能同时运行,因此,线程也必须向进程那样排好队,等待被调度到时运行,因此,线程也必须向进程那样排好队,等待被调度到CPU上去。上去。Clock类中的run()方法。源代码如下:public void run()Thread myThread=Thread.currentThread();while(clockThread=myThread)repaint()
14、;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)(NonRunnable)状态可能由以
15、下状态可能由以下3 3种情况造成:种情况造成:l调用调用sleep()sleep()方法。方法。l调用了调用了wait()wait()方法以等待某种事件的发生。方法以等待某种事件的发生。l线程等待线程等待IOIO请求的完成。请求的完成。对于上述对于上述3 3种造成阻塞的情况,分别有对应的不同条件可以种造成阻塞的情况,分别有对应的不同条件可以使线程恢复到运行状态。使线程恢复到运行状态。l对于被调用了对于被调用了sleep()sleep()方法的,必须等待方法的,必须等待sleepsleep时间过去。时间过去。l对于调用了对于调用了wait()wait()方法的线程,必须等到其他线程通过方法的线程
16、,必须等到其他线程通过notifynotify或或者者notifyAllnotifyAll方法通知该线程它要等待的事件发生为止,关于线方法通知该线程它要等待的事件发生为止,关于线程间的通讯将在程间的通讯将在12.312.3节中介绍。节中介绍。l对于被对于被IOIO阻塞的线程,必须要等阻塞的线程,必须要等IOIO操作完成。操作完成。4.停止线程l线程的停止通常是等待线程的停止通常是等待run()run()方法执行结束之后自然地停止下来。方法执行结束之后自然地停止下来。线程被停止之后就转入到线程被停止之后就转入到deaddead状态。状态。l例如,在例如,在ClockClock类中,类中,run(
17、)run()方法的循环结束条件也就是线程结束方法的循环结束条件也就是线程结束的条件,即的条件,即while(clockThread=myThread)while(clockThread=myThread),这个条件的含,这个条件的含义是当前线程不是义是当前线程不是clockThreadclockThread的时候,这个线程就停止。这就的时候,这个线程就停止。这就意味着,当用户离开这个页面的时候,意味着,当用户离开这个页面的时候,AppletApplet会调用会调用ClockClock的的stop()stop()方法。方法。l源代码如下:源代码如下:public void stop()clock
18、Thread=null;12.2.2 定制run()方法1.继承线程类l最简单的定制最简单的定制run()的方法就是继承的方法就是继承java.lang.Thread类,然后重载类,然后重载run()方法,在其中定义自己要做的事情。方法,在其中定义自己要做的事情。例例12.3 线程的继承线程的继承l程序代码l这个程序首先继承了Thread类,里面有两个方法,第一个是SimpleThread的构造器,它调用了父类的构造器;第二个是run()方法,在重载run()方法时,它实现了这样一个逻辑,即循环十次,在循环体中,打印出该线程的名称,然后取一个1秒以内的随机数,调用sleep()方法,全部运行完
19、毕打印出“DONE”。例例12.4 多线程的多线程的CPU调度调度l我们编写一段程序创建两个SimpleThread对象,看看它的执行结果。l程序代码l执行结果如图所示 2.使用使用Runnable接口接口 l开发线程应用程序的第二个方法是实现开发线程应用程序的第二个方法是实现RunnableRunnable接口。接口。我们在上一小节中用到的例子我们在上一小节中用到的例子ClockClock就使用了这种方就使用了这种方法。法。ClockClock类是一个类是一个AppletApplet,因此,我们不可能使用,因此,我们不可能使用继承继承ThreadThread类的方法。通过类的方法。通过Run
20、nableRunnable接口来实现多线接口来实现多线程是一种更加实用的方法。程是一种更加实用的方法。lClock.javaClock.java的源代码的源代码在使用在使用Runnable接口时,主要注意以下接口时,主要注意以下3点:点:l该类必须实现该类必须实现Runnable接口,即接口,即implements Runnable。l在该类中必须将新创建的线程与自己联系起来,即通在该类中必须将新创建的线程与自己联系起来,即通过过Thread的构造器的一个参数,把自己的指针交给线的构造器的一个参数,把自己的指针交给线程,以便线程在执行的时候寻找自己的程,以便线程在执行的时候寻找自己的run()
21、方法,即方法,即clockThread=new Thread(this,“Clock”)。l必须实现必须实现run()方法,在方法,在run()方法中实现需要线程去方法中实现需要线程去完成的功能,即完成的功能,即public void run()。12.3 线程的管理l由于线程是多个小程序在同时执行的过程,这由于线程是多个小程序在同时执行的过程,这些线程之间可能需要共享数据,可能需要互通些线程之间可能需要共享数据,可能需要互通消息,即使没有任何关系,也还会涉及到哪个消息,即使没有任何关系,也还会涉及到哪个线程先上线程先上CPU,哪个后上等一系列问题,因此,哪个后上等一系列问题,因此,对应用程序
22、必须对线程进行必要的管理。对应用程序必须对线程进行必要的管理。12.3.1 同步l那些需要共享对象和数据的多个线程,必须互相了解那些需要共享对象和数据的多个线程,必须互相了解对方的状态以及活动。对方的状态以及活动。l例如,不同的线程在同一时间内不能存取同一数据。例如,不同的线程在同一时间内不能存取同一数据。l又如,一个线程向文件中写数据,而另一个线程从这又如,一个线程向文件中写数据,而另一个线程从这个文件中读取数据,它们之间必须协调好步调,否则个文件中读取数据,它们之间必须协调好步调,否则程序就可能会出错。程序就可能会出错。l因此,对于这些并发执行却又共享数据的线程,我们因此,对于这些并发执行
23、却又共享数据的线程,我们必须采取某些方法对它们进行同步管理。必须采取某些方法对它们进行同步管理。1.生产者、消费者模型l假设一个生产者程序假设一个生产者程序Producer.java产生产生09的数字,存放在一个的数字,存放在一个CubbyHole对象中,并将产生的数字打印出来,然后睡眠一个随机数产对象中,并将产生的数字打印出来,然后睡眠一个随机数产生的时间,再进行下一次工作。生的时间,再进行下一次工作。l生产者程序代码生产者程序代码lConsumer.java类的任务是一旦类的任务是一旦CubbyHole对象中的数字填写好了,就对象中的数字填写好了,就来读取这个数字,这里的来读取这个数字,这
24、里的CubbyHole对象就是对象就是Producer类使用的那个对类使用的那个对象。象。l消费者消费者 程序代码程序代码 分析一下这样的两个程序如果运行起来可能出现哪些问题分析一下这样的两个程序如果运行起来可能出现哪些问题 l第一,假如第一,假如Producer被调度进被调度进CPU的次数多,而导致的次数多,而导致Producer产生了两个数字,产生了两个数字,而而Consumer只读了一个,那么下次只读了一个,那么下次Consumer再读取数字时就可能丢失掉一个,再读取数字时就可能丢失掉一个,例如:例如:Consumer#1 got:3Producer#1 put:4Producer#1
25、put:5Consumer#1 got:5l第二,假如倒过来,第二,假如倒过来,Consumer比比Producer快,那么可能导致快,那么可能导致Consumer读取重复读取重复的数字,例如:的数字,例如:Producer#1 put:4Consumer#1 got:4Consumer#1 got:4Producer#1 put:5l我们必须对这两个程序进行同步控制。所谓同步控制就是按照要求我们必须对这两个程序进行同步控制。所谓同步控制就是按照要求(即即Producer写入一个数字,写入一个数字,Consumer读取一个数字读取一个数字)让两个线程步调协调。让两个线程步调协调。2.对线程进行
26、同步在这个例子中,我们的同步控制至少要做到两点:在这个例子中,我们的同步控制至少要做到两点:l这两个线程不能同时操作这两个线程不能同时操作CubbyHoleCubbyHole对象。对象。l方法方法:JavaJava线程可以通过对共享对象加锁来控制其他线程对这线程可以通过对共享对象加锁来控制其他线程对这个对象的访问。个对象的访问。实现这类功能常用的程序框如下:实现这类功能常用的程序框如下:程序代码程序代码l两个线程互相之间还必须能够通知对方两个线程互相之间还必须能够通知对方“我已经做我已经做完了操作,你可以来了完了操作,你可以来了”。l方法方法 :ThreadThread类提供了类提供了3 3个
27、与此相关的方法,即个与此相关的方法,即(wait)(wait),(notify)(notify)和和(notifyAll)(notifyAll)。l程序代码程序代码执行这个程序的主程序如下:执行这个程序的主程序如下: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();l执行结果如图所示执行结
28、果如图所示3.同步带来的问题l同步程序如果出现问题或者遇到异常,就可能同步程序如果出现问题或者遇到异常,就可能出现死锁和饥饿两类问题。出现死锁和饥饿两类问题。l所谓死锁就是指多个进程或线程协同作用,互所谓死锁就是指多个进程或线程协同作用,互相干涉,而导致一个或者更多进程永远等待下相干涉,而导致一个或者更多进程永远等待下去。去。l而当一个进程或者线程永久性地占有资源,使而当一个进程或者线程永久性地占有资源,使得其他进程得不到该资源,就发生了饥饿。得其他进程得不到该资源,就发生了饥饿。12.3.2 优先级l通常情况下,系统会为每个通常情况下,系统会为每个JavaJava线程赋予一个介于最大优先级和
29、最小优先级之间线程赋予一个介于最大优先级和最小优先级之间的数,作为该线程的优先级,通常是从的数,作为该线程的优先级,通常是从110110之间的一个数字,数字越大表明任务之间的一个数字,数字越大表明任务越紧急。越紧急。lJavaJava的线程机制是不支持时间片的。优先级高的线程先被执行,直到其结束或者的线程机制是不支持时间片的。优先级高的线程先被执行,直到其结束或者是由于某些原因被挂起,例如进入等待状态、睡眠状态、是由于某些原因被挂起,例如进入等待状态、睡眠状态、IOIO阻塞等,其他线程才阻塞等,其他线程才可能被调度进来。可能被调度进来。lJavaJava对具有相同优先级的线程的处理是随机的。对
30、具有相同优先级的线程的处理是随机的。lJava Java 线程模型涉及可以动态更改线程优先级,高优先级的线程可以安排在低优线程模型涉及可以动态更改线程优先级,高优先级的线程可以安排在低优先级线程之前完成。一个应用程序可以通过使用线程中的方法先级线程之前完成。一个应用程序可以通过使用线程中的方法setPriority(int)setPriority(int),来设置线程的优先级大小。另外,线程还可以通过来设置线程的优先级大小。另外,线程还可以通过yield()yield()方法将自己被执行的方法将自己被执行的权限让给同样优先级的线程。权限让给同样优先级的线程。12.3.3 有关线程的其他概念1.
31、线程组l线程是被个别创建的,但可以将它们归类到线程组中,线程是被个别创建的,但可以将它们归类到线程组中,以便于调试和监视,编程时只能在创建线程的同时将以便于调试和监视,编程时只能在创建线程的同时将它与一个线程组相关联。它与一个线程组相关联。l例如,在本章开始部分讲到的操纵线程的例子中就曾例如,在本章开始部分讲到的操纵线程的例子中就曾经讲到,将当前线程打印出来的程序中,输出结果中经讲到,将当前线程打印出来的程序中,输出结果中的第的第3 3个参数个参数“main”main”就是该线程的线程组。就是该线程的线程组。2.守护线程守护线程(Daemon)l通常情况下,通常情况下,JavaJava系统支持
32、两类线程:用户线程和守护线程。用系统支持两类线程:用户线程和守护线程。用户线程是那些完成一定用户定义功能的线程,守护线程是那些仅户线程是那些完成一定用户定义功能的线程,守护线程是那些仅提供辅助功能的线程,守护线程的功能往往比较底层。提供辅助功能的线程,守护线程的功能往往比较底层。lThread Thread 类提供了类提供了setDaemon()setDaemon()函数,用于将一个线程设置为守护函数,用于将一个线程设置为守护线程。线程。l守护线程是在守护线程是在JavaJava程序运行到所有用户线程终止之后,由系统将程序运行到所有用户线程终止之后,由系统将它强迫终止掉的。在它强迫终止掉的。在
33、JavaJava虚拟机中,即使在虚拟机中,即使在main()main()方法结束以后,方法结束以后,如果另一个用户线程仍在运行,则守护线程仍然可以继续运行。如果另一个用户线程仍在运行,则守护线程仍然可以继续运行。3.避免使用的方法避免使用的方法l在在Java1.1Java1.1和和Java 1.2Java 1.2版本中提供了一些控制线程的版本中提供了一些控制线程的方法,如方法,如stop()stop(),suspend()suspend()和和 resume()resume()等等 。这些。这些函数在函数在JavaJava虚拟机中可能引入一些无法预知或者无法虚拟机中可能引入一些无法预知或者无法
34、调式的错误,尽量不要使用它们。调式的错误,尽量不要使用它们。4.在什么情况下使用线程在什么情况下使用线程 l至于什么情况下使用线程通常取决于程序的需求。至于什么情况下使用线程通常取决于程序的需求。决定是否在应用程序中使用多线程时,首先要估计决定是否在应用程序中使用多线程时,首先要估计可以并行运行的代码量。可以并行运行的代码量。l另外还要记住以下两点:另外还要记住以下两点:l使用多线程并不一定会增加使用多线程并不一定会增加CPUCPU的处理能力。的处理能力。基于基于InternetInternet的应用有必要使用多线程。的应用有必要使用多线程。12.4 用于制作动画的线程l在在JavaJava中
35、制作动画无论使用什么方法,原理都是一样中制作动画无论使用什么方法,原理都是一样的,就是要让图片或者图像非常快的在屏幕上连续移的,就是要让图片或者图像非常快的在屏幕上连续移动或者交替显示,速度大约是每秒动或者交替显示,速度大约是每秒2020帧左右。要想实帧左右。要想实现每秒刷新屏幕多次,就需要使用线程。这个线程的现每秒刷新屏幕多次,就需要使用线程。这个线程的主要任务是实现一个动画循环,在循环中跟踪当前主要任务是实现一个动画循环,在循环中跟踪当前FrameFrame的状态,并且负责定期请求刷新屏幕。的状态,并且负责定期请求刷新屏幕。12.4.1 动画程序框架l下面我们给出一个使用下面我们给出一个使
36、用AppletApplet和线程制作动画的程序和线程制作动画的程序框架。框架。l例例12.6 12.6 动画程序框架动画程序框架l程序代码程序代码12.4.2 帧的画法l在上面的框架中,我们调用在上面的框架中,我们调用AppletApplet的的repaint()repaint()方法重画各帧,方法重画各帧,repaint()repaint()方法实质上调用的是方法实质上调用的是AppletApplet的的paint()paint()方法。方法。l例例12.7 12.7 文字动画文字动画public void paint(Graphics g)g.setColor(Color.black);g
37、.drawString(Frame +frame,0,30);l在在paint()paint()方法中,我们先简单地输出方法中,我们先简单地输出一个字符串,把这个方法加入到前面的框一个字符串,把这个方法加入到前面的框架中,运行结果如图所示。架中,运行结果如图所示。例12.8 曲线动画l下面再通过一个数学的算法画一个比较复杂的图案,下面再通过一个数学的算法画一个比较复杂的图案,paint()paint()方方法的代码如下:法的代码如下:public void paint(Graphics g)Dimension d=size();int h=d.height/2;for(int x=0;x d.
38、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);l运行结果如图所示运行结果如图所示 12.4.3 避免闪动l避免闪动的方法有两个,一是通过重载避免闪动的方法有两个,一是通过重载update()update()方法,二是使用方法,二是使用bufferbuffer。l通常情况下,通常情况下,AWTAWT接收到接收到AppletApplet重画请求时,就调用重画请求时,就调用AppletApplet的的updat
39、e()update()方法,默认条件下,方法,默认条件下,update()update()方法都是清空方法都是清空AppletApplet的背的背景,然后再调用景,然后再调用paint()paint()方法。我们可以通过重写方法。我们可以通过重写update()update()方法,方法,避免系统将避免系统将AppletApplet的整个背景全部清空,从而减少视觉上画面闪的整个背景全部清空,从而减少视觉上画面闪动的感觉。动的感觉。l源代码如下:源代码如下:程序代码程序代码12.4.4 使用图片l事实上比较常见的动画是通过显示或者播放图片来实事实上比较常见的动画是通过显示或者播放图片来实现的,下
40、面我们把程序改造成使用图片演示的动画。现的,下面我们把程序改造成使用图片演示的动画。使用图片需要重载使用图片需要重载paintFrame()paintFrame()方法,而获取图片对方法,而获取图片对象的指针需要使用象的指针需要使用getImage()getImage()方法。方法。1.移动图片l例子,让汽车绕着地球移动,其中地球是背景图片,而汽车是真例子,让汽车绕着地球移动,其中地球是背景图片,而汽车是真正移动的图片。正移动的图片。l其中,我们只需要修改其中,我们只需要修改update()update()方法和方法和paintFrame()paintFrame()方法,其中方法,其中upda
41、te()update()中使用了缓冲区方法来消除闪动。中使用了缓冲区方法来消除闪动。l程序代码程序代码l运行结果如图运行结果如图12.912.9所示。所示。2.播放图片播放图片l在初始化的时候,利用循环语句一次性地把所有图片都装载进来。源代在初始化的时候,利用循环语句一次性地把所有图片都装载进来。源代码如下:码如下:frames=new Image10;for(int i=1;i=10;i+)framesi-1=getImage(getCodeBase(),T+i+.gif);l然后,在然后,在paintFrame()paintFrame()方法中,利用文件名的规律,使用一个取余函数。方法中,
42、利用文件名的规律,使用一个取余函数。源代码如下:源代码如下:public void paintFrame(Graphics g)g.drawImage(framesframe%10,0,0,null);l这个程序的运行结果如图所示,这个程序的运行结果如图所示,可以看到可以看到DukeDuke在向大家招手。在向大家招手。12.5 练练 习习 题题1.1.选择题选择题(1)(1)进程是指:进程是指:A.A.一段程序一段程序B.B.正在运行的程序正在运行的程序C.C.一个一个.java.java文件文件D.D.一个一个.class.class文件文件(2)(2)实现一个线程的执行有几种方法?实现一个
43、线程的执行有几种方法?A.A.一种一种 B.B.两种两种 C.C.三种三种 D.D.以上都不对以上都不对2.编程题编程题(1)将将TwoThreadDemoTwoThreadDemo修改为启动修改为启动3 3个线程,并改造成个线程,并改造成AppletApplet,放在浏览器中执行,请注意应该使用什么样,放在浏览器中执行,请注意应该使用什么样的方式来实现多线程处理。的方式来实现多线程处理。(2)(2)根据生产者根据生产者消费者模型编制一个程序,实现消费者模型编制一个程序,实现4 4人打人打牌的基本逻辑,即牌的基本逻辑,即4 4个人有一个出牌顺序,第一个人个人有一个出牌顺序,第一个人出牌完毕,第二个人才能出,显示任意信息表示某人出牌完毕,第二个人才能出,显示任意信息表示某人出的牌。提示,使用某个类变量存储出牌顺序,注意出的牌。提示,使用某个类变量存储出牌顺序,注意同步控制。同步控制。