Java语言程序设计基础教程(多线程)ppt.ppt_第1页
Java语言程序设计基础教程(多线程)ppt.ppt_第2页
Java语言程序设计基础教程(多线程)ppt.ppt_第3页
Java语言程序设计基础教程(多线程)ppt.ppt_第4页
Java语言程序设计基础教程(多线程)ppt.ppt_第5页
已阅读5页,还剩45页未读 继续免费阅读

下载本文档

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

文档简介

第7章 多线程 n线程简介 nThread类的子类创建线程 n实现Runnable接口 n基本的线程控制 n线程的调度 n多线程的互斥与同步 nDaemon线程 7.1线程简介 n到目前为止所介绍过的各种范例都是单 线程程序,也就是启动的Java程序在“ 同一时间”内只会做一件事。文本模式 下最常进行的就是单线程程序。有时需 要程序“同时”可以作很多事,即所谓多 线程(Multi-thread)程序,在窗口程 序、网络程序中常使用多线程功能,了 解多线程概念与注意事项是非常重要的 。 7.1.1进程与线程 n程序是一段静态的代码,它是应用软件执行的蓝本。 n进程是程序的一次动态执行过程,它对应了从代码加 载、执行至执行完毕的一个完整过程,这个过程也是 进程本身从产生、发展至消亡的过程。线程是比进程 更小的执行单位,一个进程在其执行过程中,可以产生 多个线程,形成多条执行线索,每条线索,即每个线 程也有它自身的产生、存在和消亡的过程,也是一个 动态的概念。 nJava的多线程就是在操作系统每次分时给Java程序一 个时间片的CPU时间内,在若干个独立的可控制的线 程之间切换。 7.1.2线程的状态 nJava使用Thread类及其子类的对象来表 示线程,线程在它的一个完整的生命周 期中通常要经历如下的4种状态: n1. 创建状态(new Thread) n2. 可运行状态( Runnable ) n3. 不可运行状态(Not Runnable) n4. 死亡状态(Dead) 7.2 Thread类的子类创建线 程 n用Thread类或子类创建线程对象 . n编写Thread类的子类时,需要重写父类 的run方法,其目的是规定线程的具体操 作,否则线程就什么也不做,因为父类 的run方法中没有任何操作语句。 n当JVM将CPU使用权切换给线程时,如果 线程是Thread的子类创建的,该类中的 run方法就立刻执行。 7.3实现Runnable接口 n创建线程的另一个途径就是用Thread类直接创 建线程对象。使用Thread类创建线程对象时, 常用的构造方法是: nThread(Runnable target); n该构造方法中的参数是一个Runnable类型的接 口,因此,在创建线程对象时,必须向构造方 法的参数传递一个实现Runnable接口类的实例 ,该实例对象称为所创线程的目标对象。 【例7-3】通过接口构造线 程体 nimport java.awt.Graphics; nimport java.util.Date; npublic class ep7_3 extends java.applet.Applet implements Runnable / 实现接口 nThread clockThread; npublic void start() nif (clockThread = null) nclockThread = new Thread(this, “Clock“); nclockThread.start(); / 启动线程 n n npublic void run() / run()方法中是线程执行的内 容 nwhile (clockThread != null) nrepaint(); / 刷新显示画面 ntry nclockThread.sleep(1000);/ 睡眠1秒,即每隔1秒执行一次 n catch (InterruptedException e) n n n npublic void paint(Graphics g) nDate now = new Date(); / 获得当前的时间对象 ng.drawString(now.getHours() + “:“ + now.getMinutes() + “:“ n+ now.getSeconds(), 5, 10);/ 显 示当前时间 n npublic void stop() nclockThread.stop(); nclockThread = null; n n n本程序是Applet,要运行Applet程序,必须定义一个html文件, ep7_3.html文件内容如下: n n n n n n n n n n上面这个例子是通过每隔1秒种就执行线 程的刷新画面功能,显示当前的时间; 看起来的效果就是一个时钟,每隔1秒就 变化一次。由于采用的是实现接口 Runnable的方式,所以该类Clock还继承 了Applet,Clock就可以Applet的方式运 行。 构造线程体的两种方法的 比较: n1. 使用Runnable接口 n 1) 可以将CPU,代码和数据分开,形成清晰 的模型; n 2) 还可以从其他类继承; n 3) 保持程序风格的一致性。 n2. 直接继承Thread类 n 1) 不能再从其他类继承; n 2) 编写简单,可以直接操纵线程,无需使用 Thread.currentThread()。 7.4基本的线程控制 n可以通过线程的方法进行基本的线程控 制,下面我们熟悉一下常用的方法。 n1start()方法 n线程调用该方法将启动线程,从新建状 态进入就绪队列排队。一旦CPU资源轮转 到它时,就脱离主线程开始自己的生命 周期。 n2run()方法 n系统的Thread类中,run()方法没有具体内容, 用户需要在程序中重写run()方法来覆盖原来的 run()方法,run()方法中定义线程对象被调度 之后所执行的操作,是系统自动调用而用户不 能引用的方法。当run()方法执行完毕,线程就 变成死亡状态,在线程没有结束run()方法之前 ,不要让线程再调用start()方法,否则将发生 IllegalThreadStateException异常,这一点在写 程序的时候需要注意。 n3sleep(int millsecond)方法 nsleep方法可以暂停一个线程的执行,在 适当的时候再恢复其执行。就是让当前 线程睡眠(停止执行)若干毫秒,线程 由运行中状态进入不可运行状态,停止 执行时间到后线程进入可运行状态。 n4isAlive()方法 n测试线程状态。可以通过Thread 中的 isAlive()方法来获取线程是否处于活动状 态;线程由start()方法启动后,直到其被 终止之间的任何时刻,都处于Alive状态 。线程处于新建状态时,调用isAlive()方 法返回false,线程进入死亡状态后,调 用isAlive()方法返回false。 n5currentThread()方法 ncurrentThread()方法是Thread类的类方 法,可以直接通过类名调用,该方法返 回当前正在使用CPU资源的线程。 n6Interrupt()方法 ninterrupt()方法常用来“吵醒”休眠的线程 。但线程调用sleep方法处于休眠状态时 ,一个占有CPU资源的线程可以让休眠的 线程调用interrupt方法唤醒自己。导致休 眠的线程发生InterruptedException异常 ,结束休眠,重新排队等待CPU资源。 n7stop()方法 n通过调用线程的实例方法stop()来终止线 程。线程终止后,其生命周期结束了, 即进入死亡态,终止后的线程不能再被 调度执行。 n8join()方法 n一个线程在占有CPU资源期间,可以让其他线程调用join()方法和 本线程联合。当前线程等待调用该方法的线程结束后,再重新排 队等待CPU资源,以便恢复执行。如果当前线程准备联合的线程 已经结束,也就是start方法体已经执行完,那么不会产生任何效 果。 nTimerThread tt=new TimerThread(100); ntt.start(); n npublic void timeout() ntt.join(); /当前线程等待线程tt 执行完后再继续往下执行 n 【例7-5】线程联合的例子 。 npublic class ep7_5 npublic static void main(String args) nThreadJoin a = new ThreadJoin(); na.customer.start(); na.tvMaker.start(); n n nclass ThreadJoin implements Runnable nTV tv; nThread customer, tvMaker; nThreadJoin() ncustomer = new Thread(this); ntvMaker = new Thread(this); ncustomer.setName(“顾客“); ntvMaker.setName(“电视制造厂“); n npublic void run() nif (Thread.currentThread() = customer) nSystem.out.println(customer.getName() + “等“ + tvMaker.getName() n+ “生产电视“); ntry ntvMaker.join(); / 线程customer开始等待tvMaker结束 n catch (InterruptedException e) n nSystem.out.println(customer.getName() + “买了一台电视:“ + n+ “ 价钱:“ + tv.price); n else if (Thread.currentThread() = tvMaker) nSystem.out.println(tvMaker.getName() + “开始生产电视,请等.“); ntry ntvMaker.sleep(2000); n catch (InterruptedException e) n ntv = new TV(“红星牌“, 3288); nSystem.out.println(tvMaker.getName() + “生产完毕“); n n n nclass TV nfloat price; nString name; nTV(String name, float price) = name; nthis.price = price; n n 7.5线程的调度 nJava提供一个线程调度器来监控程序中启动后 进入就绪状态的所有线程。线程调度器按照线 程的优先级决定应调度哪些线程来执行。 n线程的优先级用数字来表示,范围从1到10, 即Thread.MIN_PRIORITY到 Thread.MAX_PRIORITY。一个线程的缺省优先 级是5,即Thread.NORM_PRIORITY。 n下述方法可以对优先级进行操作: nint getPriority(); n得到线程的优先级。 nvoid setPriority(int newPriority); n当线程被创建后,可通过此方法改变线程的优先 级。 n线程调度器按线程的优先级高低选择高优先级线程( 进入运行中状态)执行,同时线程调度是抢先式调度 ,即如果在当前线程执行过程中,一个更高优先级的 线程进入可运行状态,则这个线程立即被调度执行。 n抢先式调度又分为:时间片方式和独占方式。在时间 片方式下,当前活动线程执行完当前时间片后,如果 有其他处于就绪状态的相同优先级的线程,系统会将 执行权交给其他就绪态的同优先级线程;当前活动线 程转入等待执行队列,等待下一个时间片的调度。 n在独占方式下,当前活动线程一旦获得执行权,将一 直执行下去,直到执行完毕或由于某种原因主动放弃 CPU,或者是有一高优先级的线程处于就绪状态。 n下面几种情况下,当前线程会放弃CPU: n1)线程调用了yield()或sleep()方法主动放弃 ; n2)由于当前线程进行I/O 访问,外存读写,等 待用户输入等操作,导致线程阻塞;或者是为 等候一个条件变量,以及线程调用wait()方法 ; n3)抢先式系统下,由高优先级的线程参与调 度;时间片方式下,当前时间片用完,由同优 先级的线程参与调度。 7.6多线程的互斥与同步 n经常有一些同时运行的线程需要共享数 据,此时就需考虑其他线程的状态和行 为,否则就不能保证程序的运行结果的 正确性。 7.6.1临界资源问题 n下面是一个堆栈的类定义: nclass stack nint idx = 0; / 堆栈指针的初始值为0 nchar data = new char6; / 堆栈有6个字符的空间 npublic void push(char c) / 压栈操作 ndataidx = c; / 数据入栈 nidx+; / 指针向上移动一位 n npublic char pop() / 出栈操作 nidx-; / 指针向下移动一位 nreturn dataidx; / 数据出栈 n n n两个线程A和B在同时使用Stack的同一个实例 对象,A正在往堆栈里push一个数据,B则要从 堆栈中pop一个数据。如果由于线程A和B在对 Stack对象的操作上的不完整性,会导致操作 的失败,具体过程如下所示: n1) 操作之前 n data = | p | q | | | | | idx=2 n2) A执行push中的第一个语句,将r推入堆栈; n data = | p | q | r | | | | idx=2 n3) A还未执行idx+语句,A的执行被B中 断,B执行pop方法,返回q; n data = | p | q | r | | | | idx=1 n4A继续执行push的第二个语句: n data = | p | q | r | | , | | idx=2 n最后的结果相当于r没有入栈。产生这种 问题的原因在于对共享数据访问的操作 的不完整性。 7.6.2互斥锁 n为解决操作的不完整性问题,在Java 语 言中,引入了对象互斥锁的概念,来保 证共享数据操作的完整性。每个对象都 对应于一个可称为“互斥锁”的标记,这个 标记用来保证在任一时刻,只能有一个 线程访问该对象。关键字synchronized 来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任 一时刻只能由一个线程访问。 npublic void push(char c) nsynchronized (this) / this表示Stack的当前对象 ndataidx = c; nidx+; n n npublic char pop() nsynchronized (this) / this表示Stack的当前对象 nidx-; nreturn dataidx; n n nsynchronized 除了象上面讲的放在对象前面限 制一段代码的执行外,还可以放在方法声明中 ,表示整个方法为同步方法。 npublic synchronized void push(char c) n n n如果synchronized用在类声明中,则表明该类 中的所有方法都是synchronized的。 7.6.3多线程的同步 n本小节将讨论如何控制互相交互的线程 之间的运行进度,即多线程之间的同步 问题,下面我们将通过多线程同步的模 型:生产者-消费者问题来说明怎样实现 多线程的同步。 n我们把系统中使用某类资源的线程称为 消费者,产生或释放同类资源的线程称 为生产者。 n在下面的Java的应用程序中,生产者线 程向文件中写数据,消费者从文件中读 数据,这样,在这个程序中同时运行的 两个线程共享同一个文件资源。通过这 个例子我们来了解怎样使它们同步。 nclass SyncStack / 同步堆栈类 nprivate int index = 0; / 堆栈指针初始值为0 nprivate char buffer = new char6; / 堆栈有6个字符的空间 npublic synchronized void push(char c) / 加上互斥锁 nwhile (index = buffer.length) / 堆栈已满,不能压栈 ntry nthis.wait(); / 等待,直到有数据出栈 n catch (InterruptedException e) n n nthis.notify(); / 通知其它线程把数据出栈 nbufferindex = c; / 数据入栈 nindex+; / 指针向上移动 n npublic synchronized char pop() / 加上互斥锁 nwhile (index = 0) / 堆栈无数据,不能出栈 ntry nthis.wait(); / 等待其它线程把数据 入栈 n catch (InterruptedException e) n n nthis.notify(); / 通知其它线程入栈 nindex-; / 指针向下移动 nreturn bufferindex; / 数据出栈 n n nclass Producer implements Runnable / 生产者类 nSyncStack theStack; npublic Producer(SyncStack s) ntheStack = s; n npublic void run() nchar c; nfor (int i = 0; i 10; i+) nc = (char) (Math.random() * 26 + A); ntheStack.push(c); / 把字符入栈 nSystem.out.println(“Produced: “ + c); / 打印字符 ntry nThread.sleep(int) (Math.random() * 1000); n catch (InterruptedException e) n n n n nclass Consumer implements Runnable / 消费者类 nSyncStack theStack; npublic Consumer(SyncStack s) ntheStack = s; n npublic void run() nchar c; nfor (int i = 0; i 10; i+) nc = theStack.pop(); / 从堆栈中读取字符 nSystem.out.println(“Consumed: “ + c); ntry nThread.sleep(int) (Math.random() * 1000); n catch (InterruptedException e) n n n n npublic class ep7_7 npublic static void main(String args) nSyncStack stack = new SyncStack(); n/ 下面的消费者类对象和生产者类对象所操作的是 同一个同步堆栈对象 nRunnable source = new Producer(stack); nRunnable sink = new Consumer(stack); nThread t1 = new Thread(source); / 线程实例化 nThread t2 = new Thread(sink); / 线程实例化 nt1.start(); / 线程启动 nt2.start(); / 线程启动 n n n类Producer是生产者模型,其中的 run()方法 中定义了生产者线程所做的操作,循环调用 push()方法,将生产的10个字母送入堆栈中, 每次执行完push操作后,调用sleep()方法睡眠 一段随机时间,以给其他线程执行的机会。类 Consumer是消费者模型,循环调用pop()方法 ,从堆栈中取出一个数据,一共取10次,每次 执行完pop操作后,调用sleep()方法睡眠一段 随机时间,以给其他线程执行的机会。 n在上述的例子中,通过运用wait()和 notify()方法来实现线程的同步,在同步 中还会用到notifyAll()方法,一般来说, 每个共享对象的互斥锁存在两个队列, 一个是锁等待队列,另一个是锁申请队 列,锁申请队列中的第一个线程可以对 该共享对象进行操作,而锁等待队列中 的线程在某些情况下将移入到锁申请队 列。 比较一下wait()、notify()和 notifyAll()方法: n1)wait,nofity,notifyAll:必须在已经持有 锁的情况下执行,所以它们只能出现在 synchronized作用的范围内,也就是出现在用 synchronized修饰的方法或类中。 n2)wait的作用:释放已持有的锁,进入等待队 列。 n3)notify的作用:唤醒wait队列中的第一个线 程并把它移入锁申请队列。 n4)notifyAll的作用:唤醒wait队列中的所有的 线程并把它们移入锁申请队列。 7.7 Daemon线程 n一个Daemon线程是在后台执行服务线程 ,例如网络服务器侦听连接端口的服务 ,隐藏系统线程,垃圾收集线程或其他 JVM 建立的线程。当程序中所有的非 Daemon的线程都结束了,即使Daemon 线程的run()方法中还有需要执行的语句 ,也立刻结束

温馨提示

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

评论

0/150

提交评论