第9章Java多线程.ppt
第9章 Java多线程,2,学习导读,多线程机制使得程序的多个子任务能够“同时”执行多线程是指同时存在几个执行体,按几条不同的执行线索共同工作的情况。Java语言实现了对多线程的支持,它使得编程人员可以很方便地开发出能同时处理多个任务的功能强大的应用程序。在Java语言中,不仅语言本身有多线程的支持,可以方便地生成多线程的程序,而且运行环境也利用多线程的应用程序并发提供多种服务。本章介绍如何实现Java语言中的多线程机制,3,课程结构,9.1 多线程基本概念9.2 创建线程的方式9.3 线程的挂起与唤醒9.4 多线程问题,4,9.1 多线程基本概念,文件,输入输出装置,各种系统资源,数据区段,程序区段,只有一个地方在执行,文件,输入输出装置,各种系统资源,程序区段,同时有数个地方在执行,传统的进程,多线程的任务,5,9.1 多线程基本概念,多线程的优势:减轻编写交互频繁、涉及面多的程序的困难.程序的吞吐量会得到改善.由多个处理器的系统,可以并发运行不同的线程.(否则,任何时刻只有一个线程在运行),6,9.1 多线程基本概念,一、线程与进程的区别:多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响.线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。,7,9.1 多线程基本概念,二、线程的状态和生命周期1新建2就绪3运行4阻塞5死亡,8,9.1 多线程基本概念,1新建当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。此时它已经有了相应的内存空间和其他资源,并已被初始化。2就绪处于新建状态的线程被启动后,将进入线程队列排队等待CPU时间片,此时它已经具备了运行的条件,一旦轮到它来享用CPU资源时,就可以脱离创建它的主线程独立开始自己的生命周期了。另外,原来处于阻塞状态的线程被解除阻塞后也将进入就绪状态。,9,9.1 多线程基本概念,3运行当就绪状态的线程被调度并获得处理器资源时,便进入运行状态。run方法每一个Thread类及其子类的对象都有一个重要的run()方法,当线程对象被调度执行时,它将自动调用本对象的run()方法,从第一句开始顺序执行。run()方法定义了这一类线程的操作和功能。,10,9.1 多线程基本概念,4阻塞一个正在执行的线程如果在某些特殊情况下,如被人为挂起或需要执行费时的输入输出操作时,将让出CPU并暂时中止自己的执行,进入阻塞状态。阻塞时它不能进入排列队列,只有当引起阻塞的原因被消除时,线程才可以转入就绪状态,重新进到线程队列中排队等待CPU资源,以便从原来终止处开始继续执行。,11,9.1 多线程基本概念,5死亡处于死亡状态的线程不具有继续运行的能力。线程死亡的原因有两个:一个是正常运行的线程完成了它的全部工作,即执行完了run()方法的最后一个语句并退出;另一个是线程被提前强制性地终止,如通过执行stop()方法或destroy()终止线程。,12,9.1 多线程基本概念,三、线程调度与优先级处于就绪状态的线程排队等候处理器资源线程先分配CPU资源的先后,称为线程调度为了方便线程调度,多线程系统会给每个线程自动分配一个线程的优先级,任务较紧急重要的线程,其优先级就较高;相反则较低 在Java系统中,线程调度采用优先级基础上的“先到先服务”原则,13,9.1 多线程基本概念,四、线程组 在Java中,线程组是类ThreadGroup的对象,每个线程Thread都隶属于惟一一个线程组这个线程组在线程创建时指定并在线程的整个生命期内都不能更改用户可以通过调用包含 ThreadGroup 类型参数的 Thread 类构造函数来指定线程所属的线程组。,14,9.1 多线程基本概念,在创建线程时显式地制定线程组,采用下述三种构造方法之一:1)Thread(ThreadGroup,Runnable)2)Thread(ThreadGroup,String)3)Thread(ThreadGroup,Runnable,String)若没有指定,则线程默认地隶属于名为system的系统线程组 例如,下面的语句创建了一个名为myThreadGroup的线程组:ThreadGroup myThreadGroup=new ThreadGroup(“my Group of Threads”),15,9.1 多线程基本概念,线程组的作用:Java允许对一个线程组中的所有线程同时进行操作,比如可以通过调用线程组的相应方法来设置其中所有线程的优先级,也可以启动或阻塞其中的所有线程 Java的线程组机制的另一个重要作用是线程安全。线程组机制允许通过分组来区分有不同安全特性的线程,对不同组的线程进行不同的处理,还可以通过线程组的分层结构来支持不对等安全措施的采用,16,9.1 多线程基本概念,五、守护线程,17,9.1 多线程基本概念,对线程的综合支持是Java技术的一个重要特色.它提供了thread类、监视器和条件变量的技术.虽然Macintosh,Windows NT,Windows 9等操作系统支持多线程,但若要用C或C+编写多线程程序是十分困难的,因为它们对数据同步的支持不充分.,18,9.2 创建线程的方式,Java中编程实现多线程应用有两种途径:创建用户自己的线程子类,在用户自己的类中实现Runnable接口,19,9.2 创建线程的方式,Thread类1、构造函数public Thread();这个方法创建了一个默认的线程类的对象。Public Thread(Runnabletarget);这个方法在上一个构造函数的基础上,利用一个实现了Runnable接口参数对象Target中所定义的run()方法,以便初始化或覆盖新创建的线程对象的run()方法。public Thread(Stringname);利用一个String类的对象name为所创建的线程对象指定了一个名称4,20,9.2 创建线程的方式,Thread类1、构造函数Public Thread(ThreadGroupgroup,Runnabletarget)这个方法利用给出的ThreadGroup类的对象为所创建的线程指定了所属的线程组。public Thread(ThreadGroupgroup,Stringname);这个方法在第三个构造函数创建了一个指定了一个字符串名称的线程对象的基础上,利用给出的ThreadGroup类的对象为所创建的线程指定了所属的线程组。public Thread(ThreadGroupgroup,Runnabletarget,Stringname);这个方法综合了上面提到的几种情况,创建了一个属于group的线程组,用target对象中的run()方法初始化了本线程中的run()方法,同时还为线程指定了一个字符串名。,21,9.2 创建线程的方式,Thread类2、优先级Thread类有三个有关线程优先级的静态常量:public static final int MAX_PRIORITYpublic static final int MIN_PRIORITYpublic static final int NORM_PRIORITY,22,9.2 创建线程的方式,Thread类2、优先级对应一个新建线程,系统会根据如下的原则为其自定义的优先级:新建线程将继承创建它的父线程的优先级。一般情况下,主线程具有普通优先级。另外,用户可以通过调用Thread类的方法setPriority()来修改系统自动设定的线程优先级,使之符合程序的特定需要,23,9.2 创建线程的方式,Runnable接口Runnable接口只有一个方法run(),所有实现Runnable接口的用户类都必须具体实现这个run()方法当线程被调度并转入运行状态时,它所执行的就是run()方法中规定的操作。一个实现Runnable接口的类实际上定义了一个主线程之外新线程的操作,,24,9.2 创建线程的方式,1.继承类Thread public class mythread extends Thread2.public class mythread extends Applet implements Runnable(小应用或已经是某个类的子类时)3.上述两种方法中都可用类Thread产生线程的对象 Thread newthread;4.创建并启动线程 newthread=new Thread(this);newthread.start();,25,9.2 创建线程的方式,5.run方法是运行线程的主体,启动线程时,由java直接调用 public void run()6.停止线程,调用线程的stop()newthread.stop()7 sleep方法的作用,暂停线程的执行,让其它线程得到机会,sleep要丢出异常,必须抓住.Trysleep(100)catch(InterruptedException e),26,9.2 创建线程的方式,8.其它常用的方法 isAlive:判断线程目前是否正在执行状态中 if(newthread.isAlive()newthread.stop();resume:要求被暂停得线程继续执行 suspend:暂停线程的执行 join:等待线程执行完毕 thatThread.join();被等待的那个线程不结束,当前线程就一直等待.yield:将执行的权力交给其它线程,自己到队列的最后等待.,27,9.2 创建线程的方式,11.线程的状态,28,9.2 创建线程的方式,多线程并发程序如前所述,在程序中实现多线程并发程序有两个途径:一是创建Thread类的子类;另一个是实现Runnable接口。程序员应该控制的关键性操作有两个:定义用户线程的操作,即定义用户线程中的run()方法。在适当的时候建立用户线程并用start()方法启动线程,如果需要,还要在适当的时候休眠或挂起线程。,29,9.2 创建线程的方式,多线程并发程序1、使用Thread类的子类见书P181例9.1 9.2 9.32、利用实现Runnable接口实现见书P185例9.4 9.5,30,9.3 线程的挂起与唤醒,暂停线程的执行等待条件满足再执行.下面的例子显示线程的挂起和唤醒小应用程序第一次开始时,线程被启动浏览器改变页面时,小应用程序的stop()方法被调用,线程被挂起.浏览器回到原来的页面时,线程被唤醒.,31,9.3 线程的挂起与唤醒,public void start()if(mythread=null)mythread=new Thread();mythread.start();else mythread.resume();public void run()while(true)trysleep(100);catch(InterruptedException e)public void stop()mythread.suspend();.,32,9.4 多线程问题线程同步,一、什么是线程同步问题在多线程的程序中,当多个线程并发执行时,由于线程的相对执行顺序是不确定的。当多个并发线程需要共享程序的代码区域和数据区域时,由于各线程的执行顺序是不确定的,因此执行的结果就带有不确定性,这就要求线程同步,33,9.4 多线程问题线程同步,一、什么是线程同步问题见书P190例9.6 错误的原因是:在实际的存款业务中,对同一账户的两笔存款是互斥的,即只有当一笔存款结束以后,才能在其基础上进行另一笔存款;而上面的程序中两笔存款是交替进行的,它们所取得的存款余额都是最初的3000,并分别对该余额进行操作,所以得到了结果是4500元的情况,显然是错误的,34,9.4 多线程问题线程同步,二、临界区和线程同步在多线程程序设计中,我们将程序中那些不能被多个线程并发执行的代码段称为临界区当某个线程已处于临界区时,其他的线程就不允许再进入临界区 实现方法:则是在共享代码之前加入synchronized段,把共享代码包含在synchronized段中,格式如下:synchronized(objectname)statement其中,objectname用于指出该临界区的监控对象,是可选项;statement为临界区,它既可以是一个方法,称为同步方法,也可以是一段程序代码,称为同步语句块,35,9.4 多线程问题线程同步,二、临界区和线程同步下列语句定义了一个同步方法method1()synchronized int method1()下列语句定义了一个同步语句块int method1()synchronized(this),36,9.4 多线程问题线程同步,三、PV操作(wait方法和notify方法)1wait()方法wait()方法用于使当前线程放弃临界区而处于睡眠状态,直到有另一线程调用notify()方法将它唤醒或睡眠时间已到为止,其格式如下:wait();wait(millis);其中millis是睡眠时间,37,9.4 多线程问题线程同步,2notify()方法notify()方法用于将处于睡眠状态的某个等待当前对象监控器的线程唤醒。如果有多个这样的线程,则按照先进先出的原则唤醒第一个线程。Object类中还提供了另一个方法notifyAll(),用于唤醒所有因调用wait()方法而睡眠的线程。,38,9.4 多线程问题线程同步,四、生产者消费者问题 在“生产者消费者”问题中,“生产者”不断生产产品并将其放在产品队列中,而“消费者”则不断从产品队列中取出产品 这里用两个线程模拟“生产者”和“消费者”,用一个数据对象模拟产品 没有考虑线程同步的程序:书上P195例9.7 本例中由于没有考虑生产者消费者的的对临界区的访问的冲突,回得到完全错误的结果,39,9.4 多线程问题线程同步,为了解决这一问题,引入了等待通知(wait/notify)机制:1)在生产者没有生产之前,通知消费者等待;在生产者生产之后,马上通知消费者消费。2)在消费者消费了之后,通知生产者已经消费完,需要生产。见书上P195例9.8,40,9.4 多线程问题线程同步,五、死锁死锁是指两个或多个线程无休止地互相等待对方释放所占据资源的过程。错误的同步往往会引起死锁。为了防止死锁,在进行多线程程序设计时必须遵循如下原则:1)在指定的任务真正需要并发时,才采用多线程来进行程序设计。2)在对象的同步方法中需要调用其他同步方法时必须小心。3)在临界区中的时间应尽可能短,需要长时间运行的任务尽量不要放在临界区中。,41,本章小结,1.实现线程有两种方法:实现Ruannable接口继承Thread类2.在小应用中通常在start中创建线程3.当新线程被启动时,java调用该线程的run方 法,它是Thread的核心.4.线程由五个状态:新建,就绪,运行,阻塞,死亡,42,本章小结,5.两个或多个线程竞争资源时,需要用同步的方法协调资源.6.多个线程执行时,要用到同步方法,即使用 synchronized的关键字设定同步区7.wait和notify起协调作用,