Java程序设计教程 第九章_第1页
Java程序设计教程 第九章_第2页
Java程序设计教程 第九章_第3页
Java程序设计教程 第九章_第4页
Java程序设计教程 第九章_第5页
已阅读5页,还剩51页未读 继续免费阅读

下载本文档

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

文档简介

1、Java程序设计教程,第9章 多线程与Applet,9.1 线程 9.2 HTML基础 9.3 Applet,9.1 线程,9.1.1 线程的概念 9.1.2 线程的控制 9.1.3 线程的创建 9.1.4 线程间通信,9.1.1 线程的概念,1. 程序的顺序执行与并发执行 程序的顺序执行是指一个具有独立功能的程序独占处理机直至最终结束的过程。当程序顺序执行时,在任意时刻程序中只有一个执行点,它具有如下的特点:顺序性:即程序执行过程可以看成是一系列严格按程序规定的状态转移的过程;封闭性:也就是说程序执行的最终结果由给定的初始条件决定,不受外界因素的影响;可再现性:只要输入的初始条件相同,则无论

2、何时重复执行该程序都会得到相同结果。,9.1.1 线程的概念,与程序的顺序执行相对的是程序的并发执行,即一组逻辑上互相独立的程序或程序段在执行过程中,其执行时间在客观上互相重叠。程序的并发执行可以分成两种情况:一种是多道程序系统中多道程序的并发执行,此种情况下实际上是宏观上(程序级)同时进行,微观上(指令级)顺序执行的;另一种是在某道程序段的几个程序片段中,包含着一部分可以同时执行或顺序颠倒执行的代码。程序的并发执行是实现多线程技术的基础。,9.1.1 线程的概念,2. 进程与线程的概念及其区别 进程是指一个具有独立功能的程序针对某个数据集的执行过程。进程也是系统中分配资源的基本单位。在大多数

3、操作系统中可以创建多个进程,当一个程序启动时,它将为即将开始的每项任务创建一个进程,并允许它们同时运行,这样就提高了资源利用率。例如,当一个程序因等待网络访问或用户输入而被阻塞时,另一个程序还可以继续运行。创建一个进程要占用相当一部分处理器时间和内存资源,并且在大多数操作系统中,每个进程都不能访问其他进程的内存空间。正因为如此,进程间的通信很不方便,进程间切换的负担也较重,并且不容易提出较好的编程模型。,9.1.1 线程的概念,线程与进程类似,是一段完成特定功能的代码。它是程序中单个顺序的控制流,也是一个进程内的基本调度单位。线程和进程一样拥有独立的执行控制,并由操作系统负责调度。同一进程可以

4、包含多个线程,这些线程共享属于该进程的一块内存空间和一组系统资源;而线程自身的数据通常只有微处理器的寄存器数据,以及一个供程序执行时使用的堆栈。系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小得多。此外,由于线程只是在单个进程的作用域内活动,所以线程间的通信也比进程简单。线程的实现要依靠操作系统,现代操作系统一般都支持线程技术。,9.1.1 线程的概念,3. 多线程编程 多线程编程是指将程序任务分成几个并行的子任务,由这些子任务并发执行,一起协作完成程序的功能。多线程的执行是并发的,即在逻辑上是“同时”的,而不管是否是物理上的“同时”。如果系统只有一个CPU,那么真正的“同时”是不

5、可能的,而只能采用各线程轮流使用CPU的方法来模拟“同时执行”(只是由于CPU的速度非常快,用户感觉不到其中的区别);但是如果是在多CPU系统中,则多线程的并行执行是可能的,可以把不同的线程分配到不同CPU上同时执行。,9.1.1 线程的概念,4. Java中线程的概念模型 Java内在支持多线程,它的大部分类型都是在多线程下定义的,从而使整个系统成为异步系统。Java通过java.lang.Thread类封装了线程及其上的操作,每个线程由三部分组成: (1)虚拟的CPU,封装在java.lang.Thread 类中。 (2)CPU所执行的代码。 (3)CPU所处理的数据。,9.1.2 线程的

6、控制,1. 线程的生存周期 在Java中,每个线程从创建到消亡为一个生存周期,它将经历四个状态: (1)新建状态(New Thread Status (2)可执行状态(Runnable) (3)阻塞状态(Blocked) (4)消亡状态(Dead thread),9.1.2 线程的控制,线程的状态及其切换的示意图如图9-2所示。,9.1.2 线程的控制,2. start()方法 start()方法的作用是启动线程,使线程由新建状态进入可运行状态,准备被CPU调度执行。对于start()的调用是立即返回的,并不影响调用了start()方法的其他线程的执行。如果一个线程的start()方法没有被调

7、用,则该线程是不可能运行的,而只能等待,因为此时该线程没有分配到系统资源。,9.1.2 线程的控制,3. 可运行状态与阻塞状态的相互转化 一旦线程进入可运行状态,则线程将可以被CPU调度执行。在系统中有一个负责线程调度与切换的调度器,它会在适当的时候选择处于可运行状态线程到CPU上运行,至于怎样调度以及选择哪个线程以供CPU执行,则与操作系统和线程的优先级有关。在抢占式操作系统中,当有优先级比当前正在执行的线程优先级高的可运行线程出现时,将会调度优先级高的线程到CPU上运行。然而,在非抢占式(或称分时)操作系统中,CPU的执行时间被分成很小的时间片,分别分配给各个线程执行。,9.1.2 线程的

8、控制,在可运行状态下,线程执行的核心代码就是run()方法,或者是它直接或间接调用的方法。在执行run()方法的过程中,线程既可能因调用了sleep()方法而进入阻塞状态,也可能因等待I/O操作的完成而进入阻塞状态,还可能因调用了wait()方法以等待某一特殊情况出现而进入阻塞状态。一旦上述方法所要求或等待的条件不再成立,则线程将重新进入可运行状态,之后将被重新调度并继续执行run()中的代码。,9.1.2 线程的控制,在Thread中定义的方法: public static void sleep(long milliseconds) throws InterruptedException /

9、让线程休眠milliseconds微秒的时间 public static void sleep(long milliseconds,int nanos) throws InterruptedException /让线程休眠milliseconds微秒加上nanos纳秒的时间 在Object中定义的方法: public final void wait(long timeout) throws InterruptedException public final void wait(long timeout,int nanos) throws InterruptedException public

10、final void wait() throws InterruptedException,9.1.2 线程的控制,4. 终止一个线程 在程序中不能任意终止一个线程,否则可能造成严重的线程安全问题。一个线程的终止必须靠run()方法的正常返回。当一个线程从run()方法返回后,它就进入消亡状态,再也不能被运行了。结束run()方法还有另一种途径,那就是在run()方法中抛出了异常,如果这些异常没有被捕获,JVM将会终止该线程。,9.1.2 线程的控制,5. 线程其他状态检测与切换 java.lang.Thread提供了用于线程控制的方法,除上面介绍的之外,还有其他一些方法,例如: public

11、 boolean isActive():用于检测线程是否活动,当线程处于新建或消亡状态时返回false,其他状态返回true。 public void interrupt():设置线程的中断标志,如果线程处于可运行状态,则只是简单的设置中断标志,并不影响线程的运行;如果该线程处于阻塞(休眠)状态,则将导致sleep()方法抛出InterruptedException异常。,9.1.2 线程的控制,public boolean isInterrupted():测试线程是否被设置了中断标志。 public static boolean interrupted():返回该方法的调用线程的中断标志,并

12、清除该标志。 public void join()/public void join(long millisecond)/public void join(long millis,int nanos):让调用该方法的线程阻塞,直到线程进入消亡状态,或者是指定的等待时间耗尽,才重新进入可执行状态。 public static void yied():让调用它的线程放弃处理机以便其他线程有机会竞争处理机使用权。该方法的调用是立即返回的,并不会使线程进入阻塞状态,线程仍然处于可执行状态,仍然参与竞争处理机使用权。,9.1.2 线程的控制,6. 线程的同步 在编写多线程程序时,不可避免地会遇到多个线程

13、并发访问一个对象或者数据的情况,此时就要十分小心的处理好线程之间的同步问题。处理不好线程同步将可能导致线程读到失效的数据,甚至引发死锁问题。 1)没有同步可能带来的问题 为了演示并发访问同一对象时没有进行同步所带来的问题,请先看看下面的程序:,9.1.2 线程的控制,【例程9-2】SynDemo.java /*演示没有进行线程同步所带来的问题*/ public class SynDemo public static void main(String args) Demostrator shareDemostrator = new Demostrator(); Thread t1 = new T

14、hread(shareDemostrator,t1); Thread t2 = new Thread(shareDemostrator,t2); t1.start(); t2.start(); class Demostrator implements Runnable private int shareData = 0; public void run(),9.1.2 线程的控制,Thread t = Thread.currentThread (); for(int i = 1; i = 5; i+) int copy = shareData; try Thread.sleep (int)(M

15、ath.random ()*1000); catch(Exception e) e.printStackTrace(); System.out.println (Thread +t.getName ()+: copy=+copy +tshareData=+shareData); shareData+; ,9.1.2 线程的控制,该程序的功能很简单,主线程中创建了两个子线程,这两个子线程并发的访问同一个Runnable对象中的实例变量shareData。在该Runnable对象的run()方法中采用了一个迭代5次的for循环,在每次迭代时将shareData的值复制到临时变量copy中,之后该线

16、程休眠了一段随机的时间,接着在输出copy和shareData的值。在只有一个线程的情况下,每次输出的copy值和shareData值应该是相同。但是,在多线程(两个子线程)环境下,该程序一种可能的输出如图9-5所示。,9.1.2 线程的控制,图9-5 无同步控制的一种多线程并发执行结果,9.1.2 线程的控制,2)如何实现同步 一般将程序中不允许多个并发线程交叉执行的一段代码称为临界部分或者临界区。产生临界区的原因是属于不同并发线程的程序段共享公有数据。 Java语言中临界区采用关键字“synchronized”来标识,它既可以是若干语句组成的语句块,也可以是一个方法。一旦一个对象中含有“s

17、ynchronized”标识的语句块或者方法时,Java运行环境将为该对象分配一个惟一的对象锁。无论是哪个线程要进入对象的临界区,都必须先竞争得到该对象的对象锁;如果线程竞争不到对象锁,则线程将会进入阻塞状态,直到其他线程释放了对象锁从而重新唤醒它,让它再一次参加对象锁的竞争。,9.1.2 线程的控制,在Java中,可以用三种方式使用synchronized关键字: (1)用于标识同步方法。 例如前面例子中的run()方法可以改写为同步方法: public synchronized void run() Thread t = Thread.currentThread (); for(int i

18、 = 1; i = 5; i+) int copy = shareData; try Thread.sleep (int)(Math.random ()*1000); catch(Exception e) e.printStackTrace(); System.out.println (Thread +t.getName ()+: copy=+copy +tshareData=+shareData); shareData+; ,9.1.2 线程的控制,这样,一旦t1(或t2)进入了run()方法,则其他线程t2(或 t1)只能等待t1(或者t2)从run()返回后才能进入run()方法。 采用

19、了上述方式之后,程序的一种可能输出如图9-7所示。,9.1.2 线程的控制,(2)用于为对象加锁。 有时当互斥访问某个对象的语句相对于整个方法来说较少时,可能不希望把整个方法定义为同步方法,而是让里面的若干语句构成一个同步块,这时可以用“synchronized”来为该对象加锁。语法形式为: Synchronized(obj) /若干语句序列; ,9.1.2 线程的控制,(3)为某个类加锁。 有时希望某些类中的一些类方法(静态方法)必须由各线程互斥执行,则可以把这些方法声明为同步的。这样做实际上是为相应的类加了锁,使得该类中的类方法在任意时刻最多只能由一个线程执行。为类加锁的一个例子是定义单实

20、例类,这样的类只能被实例化一次。,9.1.2 线程的控制,3)同步时要注意的问题 (1)死锁问题。 Java语言提供的同步机制虽然方便灵活并且功能强大,但它仍不能解决多线程编程时必须谨慎面对的所有问题,例如死锁问题。所谓死锁,是指各并发线程彼此互相等待对方所拥有的资源,但这些并发线程在得到对方的资源之前不会释放自己所拥有的资源,从而造成大家都想得到资源而又都得不到资源,各并发线程不能继续向前推进的状态。在多线程编程中,同步问题处理不当将很容易造成死锁问题。,9.1.2 线程的控制,(2)何时需要同步。 必须注意的是,实现线程的同步需要很多额外的系统资源,此外由于要对对象进行锁操作,所以过多的使

21、用同步机制会降低程序的效率。所以,是否采用同步机制要综合衡量各方面的需要。一般的,当有多个线程要修改同一个数据时,则可以把修改公用数据的方法声明为同步,只读取公用数据的方法可以不是同步方法。,9.1.3 线程的创建,创建一个新线程之前程序员必须编写一个线程类,并将该线程所需执行的任务编写在该线程类的一个特定方法中。程序员编写的自定义线程类,要么继承自Thread类,要么实现Runnable接口。无论是用哪种方法,该线程类中都必须重定义run()方法,并将要执行的代码放在run()方法中。Runnable是一个简单的接口,其中定义了所有线程类都必须实现的抽象方法run(),如下所示: publi

22、c interface Runnable public abstract void run(); ;,9.1.3 线程的创建,两种重定义run()方法的技术。 1. 继承Thread类并重定义run()方法 定制一个线程在运行时执行的代码,可以通过定义一个Thread类的子类,并重定义从Thread中继承过来的run()方法,使之执行指定的代码。run()方法是Thread中定义的一个空方法,其原型如下: public void run(); 当启动一个新线程时,run()方法中的第一条语句就是新线程将执行的第一条语句。在本节的例子程序中,所定义的Thread子类如下:,9.1.3 线程的创建

23、,class CustomThread extends Thread int id; public CustomThread(int customThreadID) this.id = customThreadID; /重定义run()方法,使之执行指定代码 public void run() for(int i = 0; i 5; i+) System.out.println (CustomThread #+this.id +: +i); ,9.1.3 线程的创建,2. 实现Runnable接口 另一种定制线程执行代码的方法是让某个类实现Runnable接口,并用该类的对象实例作为targe

24、t对象构造Thread对象。在实现了Runnable接口的类中,把希望执行的代码置于run()方法中,或者置于由run()方法直接或间接调用的方法中,从而让线程执行指定代码。Thread类构造方法参数列表中有一个Runnable类型的参数,可以在构造Thread对象时,为该Thread对象提供相应的Runnable对象作为目标对象。 在本节的例子中,将采用多线程技术来设计一个简单的数字时钟。其中用于表示时间计数的ClockPane继承了javax.swing.JPanel,并且实现了Runnable接口,其run()方法定义如下:,9.1.3 线程的创建,public class ClockP

25、ane extends JPanel implements Runnable public void run() while (this.running) /用于显示当前时间的字符串 this.time = DateFormat.getTimeInstance().format(new Date(); this.repaint(); try Thread.sleep(1000); /报时间隔为一秒钟 catch (InterruptedException e) e.printStackTrace(); ,9.1.3 线程的创建,3. 两种方法的比较 一般的,如果要编写的类还要继承其他父类,此时

26、就只能采用实现Runnable接口的手段,这也更符合面向对象的原则。此外,采用实现Runnable接口的手段还不会加深类继承的层次,是一种比较经济的做法。 还有一点必须注意的是,无论采用上面哪种手段,都不能自己调用run()方法,该方法只能是由线程(或JVM)调用。,9.1.4 线程间通信,1. 等待/通知机制 在讨论等待/通知机制之前,先来看一个简单的生产者消费者问题的例子。该例子描述如下:把一个最多只能容纳一个元素的缓冲区与一群生产者(Producer)和一群消费者(Customer)联系起来,生产者不停地向缓冲区写入数据,消费者则不停地取出生产者写入的数据。 可以看到上述生产者消费者问题

27、中,生产者和消费者之间必须满足如下关系: (1)消费者接收数据时该缓冲区必须是非空的 (2)生产者想发送数据时该缓冲区必须不是满的,9.1.4 线程间通信,2. I/O管道机制 所谓管道,是指内存中的一个结构,它保存读之前所写入的数据。通常,管道都有一个固定的容量,当管道达到容量时,试图写入更多的数据将导致阻塞,直到另一个线程从管道读取(移走)了一些数据后,才能解除阻塞。同样,当管道为空时,试图从管道读取数据将被阻塞,一直到另一个线程写入数据为止。Java类库的Java.io包中,提供了四个与管道相关的类可用于线程之间的数据传输。它们是,用于字节传输的PipedInputStream和Pipe

28、dOutputStream,用于字符传输的PipedWriter和PipedReader,其中前面两个的容量都是1024字节,后面两个的容量都是1024个字符,9.2 HTML基础,HTML文件是标准的ASCII文件,它看起来像是加入了许多 被称为链接签(tag)的特殊字符串的普遍文本文件。 HTML文件由元素(element)组成,组成HTML文件的元 素有许多种,用于组织文件的内容和指导文件的输出格 式。绝大多数元素是“容器”,即它有起始标记和结尾标 记。元素的起始标记叫做起始链接签(start tag),元素 结束标记叫做结尾链接签(end tag)在起始链接签和结尾 链接签中间的部分是

29、元素体。每一个元素都有名称和可选 择的属性,元素的名称和属性都在起始链接签内标明。比 如体元素(body)。 demo This is my first html file. ,9.2 HTML基础,一般来讲,HTML的元素有下列三种表示法: (1)文件或超文本。 (2)文本或 超文本。 (3)。,9.3 Applet,9.3.1 Applet的HTML知识 9.3.2 Applet类 9.3.3 Applet的主要方法,9.3.1 Applet的HTML知识,在不需要数字签名用以访问本地资源的时候, Applet 的创建过程分为如下三步: (1)使用Windows Notepad,UtalE

30、dit等文本编辑器工具编写Java Applet源程序,或者使用JBuilder等IDE的向导建立。 (2)使用JDK的编译器,把Applet的源程序转换为字节码文件。 (3)编辑HTML文件,使用标签嵌入Applet。,9.3.1 Applet的HTML知识,当需要数字签名的时候,一个Applet的开发不但要编 写Applet的程序和网页,更需要产生证书和签名,对 Applet的Jar文件签名。 1. APPLET标签 要让Applet程序可以让别人访问,必须将之嵌入到网页中,这就需要读者对HTML有一定认识,在这里提供一些必要的,跟Applet相关的HTML知识。对网页制作有兴趣的读者可以

31、深入参考其他网页设计的书籍。 用标记将一个Applet嵌入HTML文件中。如果浏览器支持Java的话,标记内指定的Applet会占去一块区域显示,同时被标记的这段说明字符串将被浏览器略去。相反,如果浏览器不支持Java,那么这段说明字符串就会按照HTML的规范显示出来。,9.3.1 Applet的HTML知识,2. APPLET标签的属性 1)CODE标志 指定Applet的文件名(如:HelloWorld.class)。需要注意的是,这个标志要求所要运行的Applet文件与HTML文件放在同一目录中。如果所要执行的小程序文件与HTML文件不在同一目录中,就需要使用CODEBASE属性。 2)

32、CODEBASE 标志 CODEBASE标志指定Applet的URL(Uniform Resource Locator)。Java的统一资源定位符URL可以是绝对地址,如,也可以是相对于当前HTML所在目录的相对地址,如/AppletPath/Name。如果HTML文件不指定CODEBASE 标志,浏览器将使用和HTML文件相同的URL。,9.3.1 Applet的HTML知识,3)WIDTH和HEIGHT属性 用来指示Applet的边框,即在Web页中要为Applet保留的矩形区域的大小,其值以像素(pixel)为单位,分别表示将使用的区域的宽度和高度(缺省值为300)。这两个属性应该被赋予

33、适当大小的值,因为浏览器的限制,如果小应用程序试图使用保留区域以外的区域,那么多出来的部分将不可见,这点要注意!例如: ,9.3.1 Applet的HTML知识,4)ALT标志 考虑到不是所有的浏览器都支持Java,为了界面的友好,必须对不支持Java技术的浏览器作出最友好的提示。ALT标志就是作用于此。如果浏览器认识标志时,却不支持Java Applet,那么将显示ALT标志指定的文本信息。例如下面语句: 5)ALIGN标志 ALIGN标志可用来控制把Applet窗口显示在HTML文档窗口的指定位置。ALIGN标志指定的值可以是TOP、MIDDLE、BOTTOM、LEFT、RIGHT、TEX

34、TTOP、MIDDLE、ABSMIDDLE、BASELINE、ABSBOTTOM等。,9.3.1 Applet的HTML知识,6)VSPACE与HSPACE 标志 VSPACE和HSPACE标志指定浏览器显示在Applet窗口周围的水平和竖直空白条的尺寸,单位为像素。例如: 该语句的效果为Applet窗口之上和之下各留出20像素的空白,在其左和其右各留出25像素的空白。 7)NAME标志 NAME标志把指定的名字赋值给Applet的当前实例。当浏览器同时运行两个或多个Applet时,各Applet可通过名字进行通讯。如果忽略NAME标志,Applet的名字将对应于其类名。,9.3.1 Appl

35、et的HTML知识,8)PARAM标志 为了使Applet具有通用性,就像其他程序设计一样,需要考虑参数的传递问题。HTML文件中可以使用PARAM标志来标识参数,Applet内可调用getParameter(param name)方法获取HTML文件里设置的参数值。格式如下所示: ,9.3.2 Applet类,下面将从传统的HelloWorld程序开始讲解Applet类: 【例程9-8】HelloWorld.java import javax.swing.*; import java.awt.*; public class Hello_world extends JApplet public

36、 void paint(Graphics g) g.drawString(Hello,world!,5,10); HelloWorld.html 代码 Hello,world! ,9.3.2 Applet类,程序运行结果如图9-11所示。,9.3.3 Applet的主要方法,当支持Java的浏览器遇到标记时,它就会按照WIDTH和HEIGTH的值为Applet保留一定大小的显示空间,并按照CODE和CODEBASE的值来下载Applet的字节码文件。然后浏览器生成该Applet的一个实例。如上所述,一个Applet实例的存在要经过Applet类的init()、start()、stop()、destroy()、paint()等方法按一定的次序配合完成。因此,Applet的几个主要方法就起着比较重要的作用。,9.3.3 Applet的主要方法,1. init() 第一次打开含有小程序的Web页面(HTML)时,Java将调用该小程序的init()方法。在init()方法中,小程序执行它的一次性启动操作,比如创建和初始化对

温馨提示

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

评论

0/150

提交评论