Java语言程序设计-第九章-线程_第1页
Java语言程序设计-第九章-线程_第2页
Java语言程序设计-第九章-线程_第3页
Java语言程序设计-第九章-线程_第4页
Java语言程序设计-第九章-线程_第5页
已阅读5页,还剩45页未读 继续免费阅读

下载本文档

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

文档简介

1、Java语言程序设计第九章 线程第9讲 多线程 多线程基本概念 创建线程的方式 线程的生命周期及控制 线程的优先级及调度 多线程的互斥与同步 守护线程 (Daemon) 线程组 (ThreadGroup) 程序:是一段静态的代码,它是应用软件执行的蓝本。进程:是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程。线程是比进程更小的执行单位。一个进程在其执行过程中,可以产生多个线程,形成多条执行线索,每条线索,即每个线程也有它自身的产生、存在和消亡的过程,也是一个动态的概念。1、多线程基本概念操作系统为每个进程分配一段内存空间,

2、 包括:代码、数据以及堆栈等资源多任务的操作系统(OS)中,进程切换对 CPU资源消耗较大多线程是指一个进程中同时运行的多个完成不同子任务的线程。多线程不仅可以使一个程序同时完成多项任务,而且为此消耗的系统资源也比进程方法少许多1、多线程基本概念并发现象在现实生活中大量存在人体(消化、运动)计算机(同时运行多种程序)多线程在一个程序中实现并发编程语言一般提供了串行程序设计的方法计算机的并发能力由操作系统提供Java在语言级提供多线程并发的概念1、多线程基本概念1、多线程基本概念 以前所编写的程序,每个程序都有一个入口、一个出口以及一个顺序执行的序列,在程序执行过程中的任何指定时刻,都只有一个单

3、独的执行点。 事实上,在单个程序内部是可以在同一时刻进行多种运算的,这就是所谓的多线程(这与多任务的概念有相似之处)。 一个单独的线程和顺序程序相似,也有一个入口、一个出口以及一个顺序执行的序列,从概念上说,一个线程是一个程序内部的一个顺序控制流。 线程并不是程序,它自己本身并不能运行,必须在程序中运行。在一个程序中可以实现多个线程,这些线程同时运行,完成不同的功能。1、多线程基本概念 从逻辑的观点来看,多线程意味着一个程序的多行语句同时执行,但是多线程并不等于多次启动一个程序,操作系统也不会把每个线程当作独立的进程来对待: 两者的粒度不同,是两个不同层次上的概念。进程是由操作系统来管理的,而

4、线程则是在一个程序(进程)内。 不同进程的代码、内部数据和状态都是完全独立的,而一个程序内的多线程是共享同一块内存空间和同一组系统资源,有可能互相影响。 线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。1、多线程基本概念进程:程序的一次执行。程序代码程序数据程序资源线程:进程中程序代码的一个执行序列。程序调用堆栈线程局部变量可共享访问进程中的数据和资源操作系统按线程来调度程序的执行1、多线程基本概念文件输入输出装置各种系统资源数据区段程序区段只有一个地方在执行文件输入输出装置各种系统资源数据区段程序区段同时有数个地方在执行传统的进程多线程的任

5、务1、多线程基本概念1、多线程基本概念多线程的优势: 多线程编程简单,效率高(能直接共享数据和资源,多进程不能) 适合于开发服务程序(如Web服务,聊天服务等) 适合于开发有多种交互接口的程序(如聊天程序的客户端,网络下载工具) 适合于有人机交互又有计算量的程序(如字处理程序Word,Excel) 减轻编写交互频繁、涉及面多的程序的困难(如监听网络端口) 程序的吞吐量会得到改善(同时监听多种设备,如网络端口、串口、并口以及其他外设) 有多个处理器的系统,可以并发运行不同的线程(否则,任何时刻只有一个线程在运行)1、多线程基本概念 虽然各种操作系统(Unix/Linux、Windows系列等)都

6、支持多线程,但若要用C、C+或其他语言编写多线程程序是十分困难的,因为它们对数据同步的支持不充分。 对多线程的综合支持是Java语言的一个重要特色,它提供了Thread类来实现多线程。在Java中,线程可以认为是由三部分组成的: 虚拟CPU,封装在java.lang.Thread类中,它控制着整个线程的运行; 执行的代码,传递给Thread类,由Thread类控制顺序执行; 处理的数据,传递给Thread类,是在代码执行过程中所要处理的数据。Java应用程序总是从主类的main方法开始执行。当JVM加载代码,发现main方法之后,就会启动一个线程,这个线程称作“主线程”,该线程负责执行main

7、方法。那么,在main方法中再创建的线程,就称为主线程中的线程。如果main方法中没有创建其他的线程,那么当main方法执行完最后一个语句,即main方法返回时,JVM就会结束我们的Java应用程序。如果main方法中又创建了其他线程,那么JVM就要在主线程和其他线程之间轮流切换,保证每个线程都有机会使用CPU资源,main方法即使执行完最后的语句,JVM也不会结束我们的程序,JVM一直要等到主线程中的所有线程都结束之后,才结束我们的Java应用程序。1、多线程基本概念 在Java语言中,用Thread类或子类创建线程对象。这一节讲述怎样用Thread子类创建对象。 用户可以扩展 Thread

8、类,但需要重写父类的run方法,其目的是规定线程的具体操作,否则线程就什么也不做,因为父类的run方法中没有任何操作语句。 下面例子中除主线程外还有两个线程,这两个线程分别在命令行窗口的左侧和右侧顺序地一行一行地输出字符串。主线程负责判断输出的行数,当其中任何一个线程输出8行后,就结束进程。本例题中用到了System类中的类方法:exit(int n),主线程使用该方法结束整个程序。 2、创建线程的方式 - Thread 的子类创建线程 使用Thread子类创建线程的优点是:我们可以在子类中增加新的成员变量,使线程具有某种属性,也可以在子类中新增加方法,使线程具有某种功能。但是,Java不支持

9、多继承,Thread类的子类不能再扩展其他的类。2、创建线程的方式 - Thread 的子类创建线程 1Runnable接口与目标对象 创建线程的另一个途径就是用Thread类直接创建线程对象。使用Thread创建线程对象时,通常使用的构造方法是: Thread(Runnable target), 该构造方法中的参数是一个Runnable类型的接口,因此,在创建线程对象时必须向构造方法的参数传递一个实现Runnable接口类的实例,该实例对象称作所创线程的目标对象,当线程调用start方法后,一旦轮到它来享用CPU资源,目标对象就会自动调用接口中的run方法(接口回调),这一过程是自动实现的,

10、用户程序只需要让线程调用start方法即可,也就是说,当线程被调度并转入运行状态时,所执行的就是run()方法中所规定的操作。例子:通过Runnable接口的方式创建线程 2、创建线程的方式 -使用Runable接口 我们知道线程间可以共享相同的内存单元(包括代码与数据),并利用这些共享单元来实现数据交换、实时通信与必要的同步操作。对于Thread(Runnable target)构造方法创建的线程,轮到它来享用CPU资源时,目标对象就会自动调用接口中的run方法,因此,对于使用同一目标对象的线程,目标对象的成员变量自然就是这些线程共享的数据单元。另外,创建目标对象类在必要时还可以是某个特定类

11、的子类,因此,使用Runnable接口比使用Thread的子类更具有灵活性。 下面的例子中,两个线程:zhang和cheng,使用同一目标对象。两个线程共享目标对象的money。当money的值小于100时,线程zhang结束自己的run方法进入死亡状态;当money的值小于0时,线程cheng结束自己的run方法进入死亡状态。2、创建线程的方式 -使用Runable接口 2关于run方法中的局部变量 对于具有相同目标对象的线程,当其中一个线程享用CPU资源时,目标对象自动调用接口中的run方法,这时,run方法中的局部变量被分配内存空间,当轮到另一个线程享用CPU资源时,目标对象会再次调用接

12、口中的run方法,那么,run方法中的局部变量会再次分配内存空间。也就是说run方法已经启动运行了两次,分别运行在不同的线程中,即运行在不同的时间片内。不同线程的run方法中的局部变量互不干扰,一个线程改变了自己的run方法中局部变量的值不会影响其他线程的run方法中的局部变量。看例子2、创建线程的方式 -使用Runable接口例子:通过实例熟悉如何创建线程 1start() 线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的主线程独立开始自己的生命周期了。 2run() Thread类的run()方法与Runnable接口中的run()

13、方法的功能和作用相同,都用来定义线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法。系统的Thread类中,run()方法没有具体内容,所以用户程序需要创建自己的Thread类的子类,并重写run()方法来覆盖原来的run()方法。当run方法执行完毕,线程就变成死亡状态,所谓死亡状态就是线程释放了实体,即释放分配给线程对象的内存。在线程没有结束run()方法之前,不赞成让线程再调用start()方法,否则将发生ILLegalThreadStateException异常。 2、创建线程的方式 -线程的常用方法 3sleep(int millsecond) 线程的调度执行是

14、按照其优先级的高低顺序进行的,当高级线程不完成,即未死亡时,低级线程没有机会获得处理器。有时,优先级高的线程需要优先级低的线程做一些工作来配合它,或者优先级高的线程需要完成一些费时的操作,此时优先级高的线程应该让出处理器,使优先级低的线程有机会执行。为达到这个目的,优先级高的线程可以在它的run()方法中调用sleep方法来使自己放弃处理器资源,休眠一段时间。休眠时间的长短由sleep方法的参数决定,millsecond是毫秒为单位的休眠时间。如果线程在休眠时被打断,JVM就抛出InterruptedException异常。因此,必须在trycatch语句块中调用sleep方法。例子:线程的休

15、眠 2、创建线程的方式 -线程的常用方法 4isAlive() 检查线程是否处于运行状态的方法,当一个线程调用start()方法,并占有CUP资源后,该线程的run方法就开始运行。在线程的run方法结束之前,即没有进入死亡状态之前,线程调用isAlive()方法返回true,当线程进入死亡状态后(实体内存被释放),线程仍可以调用方法isAlive(),这时返回的值是false。 需要注意的是,一个已经运行的线程在没有进入死亡状态时,不要再给线程分配实体,由于线程只能引用最后分配的实体,先前的实体就会成为“垃圾”,并且不会被垃圾收集机收集掉。 2、创建线程的方式 -线程的常用方法 现在让我们看一

16、个例子,在下面的例子中一个线程每隔1秒钟在命令行窗口输出一句话:“你好!”,在输出5句之后,该线程又被分配了实体,新实体又开始运行。这时,我们在命令行每秒钟能看见两行“你好”,因为垃圾实体仍然在工作。 2、创建线程的方式 -线程的常用方法 5currentThread() currentThread()方法是Thread类中的类方法,可以用类名调用,该方法返回当前正在使用CPU资源的线程。 6interrupt() intertupt方法经常用来“吵醒”休眠的线程。当一些线程调用sleep方法处于休眠状态时,一个使用 CPU资源的其它线程在执行过程中,可以让休眠的线程分别调用interrupt

17、 方法“吵醒”自己,即导致休眠的线程发生InterruptedException异常,从而结束休眠,重新排队等待CPU资源。 2、创建线程的方式 -线程的常用方法 在下面的例子中,有3个线程:zhangXiao、zhengMing和teacher,其中2个线程:zhangXiao和zhengMing准备休眠10秒钟后,再分别输出“早上好!”和“good morning!”。teacher线程在输出5句“上课”后,“吵醒”休眠的线程:zhangXiao和zhengMing。 2、创建线程的方式 -线程的常用方法3、 线程的生命周期及控制 线程是程序内部的一个顺序控制流,它具有一个特定的生命周期。

18、在一个线程的生命周期中,它总处于某一种状态中。线程的状态表示了线程正在进行的活动以及在这段时间内线程能完成的任务。 1线程的4种状态 在Java语言中,Thread类及其子类创建的对象称作线程,新建的线程在它的一个完整的生命周期中通常要经历4种状态,(1)新建(new Thread) 当创建了一个新的线程时( myThread thd = new myThread(); ),它就处于创建状态,此时它仅仅是一个空的线程对象,系统不为它分配资源。处于这种状态时只能启动或终止该线程,调用除这两种以外的其它线程状态相关的方法都会失败并且会引起非法状态例外IllegalThreadStateExcept

19、ion(对于其它状态,若所调用的方法与状态不符,都会引起非法状态例外)。 3、线程的生命周期及控制(2)运行 线程创建后仅仅是占有了内存资源,在JVM管理的线程中还没有这个线程,此线程必须调用start()方法(从父类继承的方法)通知JVM,这样JVM就会知道又有一个新一个线程排队等候切换了。 当JVM将CUP使用权切换给线程时,如果线程是Thread的子类创建的,该类中的run方法就立刻执行。所以我们必须在子类中重写父类的run方法,Thread类中的run()方法没有具体内容,程序要在Thread类的子类中重写run()方法来覆盖父类的run()方法,run方法规定了该线程的具体使命。 在

20、线程没有结束run方法之前,不要让线程再调用start方法,否则将发生ILLegalThreadStateException异常。 3、线程的生命周期及控制(3)中断 有4种原因的中断:(a) JVM将CPU资源从当前线程切换给其他线程,使本线程让出CPU的使用权处于中断状态。(b)线程使用CPU资源期间,执行了sleep(int millsecond)方法,使当前线程进入休眠状态。sleep(int millsecond)方法是Thread类中的一个类方法,线程一旦执行了sleep(int millsecond)方法,就立刻让出CPU的使用权,使当前线程处于中断状态。经过参数millseco

21、nd指定的豪秒数之后,该线程就重新进到线程队列中排队等待CPU资源,以便从中断处继续运行。 3、线程的生命周期及控制(c)线程使用CPU资源期间,执行了wait()方法,使得当前线程进入等待状态。等待状态的线程不会主动进到线程队列中排队等待CPU资源,必须由其他线程调用notify()方法通知它,使得它重新进到线程队列中排队等待CPU资源,以便从中断处继续运行。有关wait、noftify和notifyAll方法将在后面详细讨论 (d) 线程使用CPU资源期间,执行某个操作进入阻塞状态,比如执行读/写操作引起阻塞。进入阻塞状态时线程不能进入排队队列,只有当引起阻塞的原因消除时,线程才重新进到线

22、程队列中排队等待CPU资源,以便从原来中断处开始继续运行。 3、线程的生命周期及控制 (4)死亡 处于死亡状态的线程不具有继续运行的能力。线程死亡的原因有二,一个是正常运行的线程完成了它的全部工作,即执行完run方法中的全部语句,结束了run方法。另一个原因是线程被提前强制性地终止,即强制run方法结束。所谓死亡状态就是线程释放了实体,即释放分配给线程对象的内存。 现在,我们看一个完整的例子,通过分析运行结果阐述线程的4种状态。该例子中我们用Thread的子类:WriteWordThread创建了两个线程。 3、线程的生命周期及控制 上述程序在不同的计算机运行或在同一台计算机反复运行的结果不尽

23、相同,输出结果依赖当前CPU资源的使用情况。为了使结果尽量不依赖于当前CPU资源的使用情况,我们应当让线程主动调用sleep方法让出CPU的使用权进入中断状态,如下列代码所示: 3、线程的生命周期及控制3、线程的生命周期及控制 isAlive()方法:可以用来判断线程目前是否正在执行中。如果线程已被启动并且未被终止,那么isAlive( )返回true,但该线程是可运行或是不可运行的,是不能作进一步的分辨。如果返回false,则该线程是新创建或是已被终止的(同样不能作进一步的分辨)。 join()方法:等待线程执行完毕。 yield()方法:将执行的权力交给其它优先级相同的线程,当前执行线程到

24、可运行线程队列(ready)的最后等待,若队列空,该方法无效。myThread thd = new MyThread();thd.start();thd.join(); /等待线程thd执行完后再继续往下执行join(int time):最多等待time所指定的时间。 sleep()方法可以暂停线程的执行,让其它线程得到机会。但sleep()要丢出例外InterruptedException ,必须抓住。try sleep(100)catch(InterruptedException e) /本例外可不作处理 suspend()方法和resume()可以用来暂停线程或恢复线程。可以由线程自身在

25、线程体中调用suspend()方法暂停自己,也可以在其他线程中通过线程实例调用suspend()方法暂停线程的执行。但是要恢复由suspend()方法暂停的线程,只能在其他线程中通过线程实例调用resume()方法。3、线程的生命周期及控制 Java提供一个线程调度器来监控程序中启动后进入可运行状态的所有线程。线程调度器按照线程的优先级决定调度哪些线程来执行,具有高优先级的线程会在较低优先级的线程之前得到执行。同时线程的调度是抢先式的,即如果当前线程在执行过程中,一个具有更高优先级的线程进入可执行状态,则该高优先级的线程会被立即调度执行。 多个线程运行时,若线程的优先级相同,由操作系统按时间片

26、轮转方式或独占方式来分配线程的执行时间。 4、线程的优先级及调度 在Java中线程的优先级是用数字来表示的,分为三个级别: 低优先级:Thread.MIN_PRIORITY,数值为1 (24) 缺省优先级: Thread. NORM_PRIORITY,数值为5 高优先级:Thread.MAX_PRIORITY,数值为10 (69) 具有相同优先级的多个线程,若它们都为高优先级Thread.MAX_PRIORITY,则每个线程都是独占式的,也就是说这些线程将被顺序执行;若该优先级不为高优先级,则这些线程将同时执行,也就是说这些线程的执行是无序的。 线程被创建后,其缺省的优先级是缺省优先级Thre

27、ad. NORM_PRIORITY。可以用方法 int getPriority()来获得线程的优先级,同时也可以用方法 void setPriority( int p ) 在线程被创建后改变线程的优先级。 4、线程的优先级及调度在实际软件系统中,许多线程之间有时会共享一些数据,并且它们之间的状态、行为是相互影响的。线程间共享的数据以及线程状态、行为的相互影响有两种:一种是线程间的互斥,一种是线程间的同步 5.1 共享资源问题5.2 互斥线程的设计方法5、多线程的互斥与同步 线程间的互斥5.1 共享资源问题共享资源是指在程序中并行运行的若干个线程操作的数据资源相同在程序中,并行运行的若干个线程操

28、作共享资源时可能出错。例如,在一个民航的售票系统中,一个售票处工作人员(甲)欲给一个客户分配一个座位,于是调出飞机的座位分配图,但当甲察看座位分配图时,另外一个售票处的工作人员(乙)也在为其他客户分配座位,乙也调出了这张飞机座位分配图。乙为客户分配了座位6A,同时在图中标记此座位(6A)已被分配。此时,甲还在察看那张未被更新的座位分配图,甲并不知道座位分配图已经发生了变化(座位6A已被分配),甲也为客户分配了座位6A。这时问题就出现了,一个座位被两个人同时拥有那么,问题出在哪里呢?显然是飞机的座位分配图,这是一个共享的数据。在一个工作人员使用它时,应该禁止其他工作人员使用它这类问题实际是一种生

29、产者-消费者问题。生产者一次或多次提供货物,若干个消费者同时消费。我们定义这种生产者-消费者问题是生产者-消费者模式 共享资源的互斥基于上述分析,应当认识到,当并行运行的几个线程操作共享资源,且问题的模型属于生产者-消费者模式时,一定要保证操作的互斥,即一个共享资源每次只能由一个线程操作,当这个线程还没有操作完时,不允许其他线程操作共享资源。只有这样才能保证并行运行的多个线程对共享资源操作的正确性5.2 互斥线程的设计方法互斥锁是基于共享资源的互斥性设计的,用来标记那些多个并行运行的线程共享的资源。被用互斥锁标记的共享资源,在一个时间段内,只能有一个线程使用;只有当加互斥锁的线程使用完了该共享

30、资源,另一个线程才可以使用。这样就可以保证线程对共享资源操作的正确性在Java语言中,引入了“对象互斥锁”的概念(又称为监视器、管程)来实现不同线程对共享数据操作的同步。 “对象互斥锁”阻止多个线程同时访问同一个条件变量。 在Java语言中,有两种方法可以实现“对象互斥锁”: 用关键字volatile来声明一个共享数据(变量); 用关键字synchronized来声明一个操作共享数据的方法或一段代码。为共享资源加互斥锁的两种方法:(1)锁定一个对象和一段代码声明格式为: synchronized (对象名) 对象表示要锁定的共享资源,一对花括号内的语句组表示锁定对象期间执行的语句。此格式可以用

31、来在一个线程的一部分代码上加互斥锁当一个线程执行这段代码时,就锁定了指定的对象这就形成了多个线程对同一个对象的“互斥”使用方式,因此该对象也称为互斥对象(2)锁定一个方法声明格式为: synchronized 方法声明 方法体 这里虽然没有指出锁定的对象,但是一个方法必然属于一个类,因此,此格式锁定的是该方法所属类的对象,锁定的范围是整个方法 在下面的例子中有两个线程:accountant和cashier,他俩共同拥有一个帐本。他俩都可以使用saveOrTake(int number)对帐本进行访问,会计使用saveOrTake方法时,向帐本上写入存钱记录;出纳使用saveOrTake方法时,

32、向帐本写入取钱记录。因此,当会计正在使用saveOrTake方法时,出纳被禁止使用,反之也是这样。 为共享资源加互斥锁的两种方法:还有一种线程间对共享资源的相互影响,是一个线程的运行要依赖于另一个线程对共享资源的处理结果,这称为线程间的同步 5.3 资源同步问题5.4 同步线程的设计方法5、多线程的互斥与同步 线程间的同步5.3 资源同步问题一个计算机模拟生产-销售问题。规定:(1)由于仓库有限,必须把仓库中的商品销售完后才能再生产;(2)由于担心不能及时供货被罚款,只有当仓库中有货时才能销售。在这类问题中,两个操作(生产和销售)中的任何一个操作执行的前提,是另一个操作已经完成。另一个操作已经

33、完成的标志是共享资源(仓库)满足本操作的执行条件。这样一类问题也称作生产者-消费者问题。我们定义这种生产者-消费者问题是生产者-消费者模式。消费者操作执行的前提条件是生产者操作已经生产了;生产者操作执行的前提条件是消费者已经消费了。在存在共享资源同步问题的程序设计中如果不考虑共享资源的同步问题,必然引起错误 5.4 线程同步的设计方法线程同步的设计方法,是除了加互斥锁外,还要在并行运行的线程上加信号量信号量是一个标志,表示一种操作(如生产或销售)是否已执行完,另一种操作(如销售或生产)是否可以执行了。Object类提供了一组关于线程同步的方法:l void wait() 引起当前线程等待l void notify()唤醒一个正在等待这个对象的线程l void notifyAll()唤醒所有正在等待这个对象的线程wait()方法的所谓等待,是把当前线程从运行状态转为阻塞状态notify()方法的所谓唤醒,是把等待线程从阻塞状态转为可运行状态,从而有机会让线程调度程序调度进入运行状态还有一点要说明的是:wait()方法所在的代码段一定要加互斥锁synchronized。

温馨提示

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

评论

0/150

提交评论