java线程机制 - staff.ustc.edu.cnstaff.ustc.edu.cn/~renkx/chp10.pdf ·...

Post on 18-Feb-2018

264 Views

Category:

Documents

5 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Java 软件设计基础

JavaJava线程机制线程机制

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

1.Java中的多线程机制

•• 基本概念基本概念– 程序

• 程序是为完成特定任务、用某种语言编写的一组指令的集合,指一段静态的代码。

– 进程• 进程是程序的一次执行过程,是系统进行调度和资源分配的一个独立单位,每个进程都有自己独立的一块内存空间、一组系统资源。在进程概念中,每个进程的内部数据和状态都是完全独立的。• 计算机上同时运行多个程序叫进程并发。操作系统按照一定的策略调度各个进程执行,以便最大限度的利用计算机的各种资源。

– 线程的基本概念• 线程(Thread)是进程中某个单个顺序的控制流,也被称为轻量进程(Lightweight Process)。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

• 线程是进程中的实体。一个进程可以有多个线程,一个线程必须有一个父进程。• 线程不拥有系统资源,只有运行必需的一些数据结构;它与父进程的其他线程共享该进程拥有的全部资源。

– 多线程• 多线程指在单个程序中可以同时运行多个不同的线程,执行不同的任务,这是实现并发机制的一种有效手段。• 操作系统使用分时管理各个进程,按时间片轮流执行每个进程。Java的多线程就是在操作系统每次分时给Java程序一个时间片的CPU时间内,在若干独立的可控制的线程之间切换。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

多个线程运行在多个CPU上

多个线程共享单个CPU

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

•• 线程的生命周期线程的生命周期– 同进程一样,线程也有从创建、运行到消亡的过程,这称为线程的生命周期。

– 创建状态• 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于创建状态,这是线程已被创建但未开始执行的特殊状态。处于创建状态的线程是一个空的线程对象,系统不为它分配资源,但有自己的内存空间。• 创建状态的线程通过调用start()方法进入就绪状态。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

– 就绪状态• 处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,因而将进入线程队列,等待系统为其分配CPU。• 线程根据自身优先级进入等待队列的相应位置。• 一旦获得CPU,线程就进入运行状态,并自动调用自己的run()方法。

– 运行状态• 进入运行状态的线程顺序执行自己run()方法中的代码,直到调用其他方法而终止,或等待某资源而阻塞,或完成任务而死亡。• 运行状态表示线程拥有了对处理器的控制权,其代码正在运行,除非运行过程的控制权被另一优先级更高的线程抢占,否则这个线程将一直持续到运行完毕。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

– 阻塞状态• 处于运行状态的线程在某些情况下让出CPU并暂时终止自己的运行,进入阻塞状态。

– 执行了sleep()方法;– 等待I/O设备等资源

• 在阻塞状态的线程不能进入就绪队列,只有当引起阻塞的原因消除时才能转入就绪状态。

– 睡眠时间已到– 等待的I/O设备空闲

• 当再次获得CPU时,从原来终止的位置开始继续运行。• 注意:若线程正在等待某个条件,那么相关联的另一个线程必须通过调用notify()或notifyAll()方法告诉正在等待的线程条件发生了变化。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

– 死亡状态• 这是线程生命周期中的最后一个阶段,表示线程已退出运行状态,并且不再进入就绪队伍。当线程的run()方法结束或由于其他原因被终止后,线程就进入消亡状态。线程的终止分两种情况:

– 自然死亡»即从线程的run()方法正常退出;

– 被强制性终止»使用Thread类中的destroy()方法或stop()方法终止»Java系统不赞成使用destroy()方法或stop()方法终止线程,因为若使用不当,可能会导致程序的死锁。

– 总结

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

•• 多线程的实现方法多线程的实现方法– 多线程有两种实现方法:

• 通过继承创建Thread类的子类来实现;• 利用Runnable接口来实现。

– 当程序作为一个应用程序运行时,Java解释器为main方法启动一个线程。当程序作为一个applet运行时,Web浏览器启动一个线程来运行applet。– 在程序中还可以创建附加的线程以执行并发的任务。– Java中每个任务都是一个Runnable接口的实例,也称为可运行对象。

线程本质上就是实现任务的对象。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

•• 创建任务和线程创建任务和线程– 创建任务

• 声明一个任务类,它必须实现Runnable接口。– Runnable接口只包含一个run方法,需要实现这个方法来告诉系统线程是如何运行的。

• 一旦声明了一个任务类,就可以利用其构造方法创建一个任务。

– 在线程中执行任务• 根据任务对象创建线程;• 调用start方法告诉JVM该线程准备运行。• JVM通过调用任务中的run方法执行任务。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

– 线程的一些常用方法:• start()方法

– 线程调用该方法启动一个线程,使之从新建状态进入就绪队列排队,一旦轮到它来使用CPU资源时,就可以脱离创建它的主线程独立开始自己的生命周期。

• run()方法– 线程的所有活动都是通过线程体run()方法来定义并实现它被调用以后所执行的操作。– Runnable接口中的run()方法是由系统自动调用而用户程序不得引用的方法。

• sleep()方法– 使线程睡眠一段时间,单位为毫秒。– 正在运行的高优先级线程可以在它的run()方法体中调用sleep()方法来使自己放弃处理器资源而休眠一段时间,长短由sleep()方法的参数决定。– 如果线程在休眠时被打断,JVM就抛出InterruptedException异常,因此必须在try-catch语句块中调用sleep()方法。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

• isAlive()方法– 测试线程是否处于活动状态。

• interrupt()方法– 用来唤醒休眠的线程。系统可以通过一定方式让休眠的线程分别调用interrupt()方法,导致休眠的线程发生InterruptedException异常,从而结束休眠,重新排队等待CPU资源。

• yield()方法– 把线程移到队列的尾部。

• destroy()方法– 结束线程生命周期但不做清理工作。

• stop()方法– 结束线程生命周期并执行清理工作。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

•• 线程调度与优先级线程调度与优先级– 在CPU上以某种次序执行多个线程被称为调度。– 线程调度器

• 为了控制线程的运行策略,Java定义了线程调度器来监控系统中处于就绪状态的所有线程。• 线程调度器采用“抢占式”策略来按照线程的优先级决定哪个线程获取处理器投入运行。

– 优先级• Java将线程的优先级分为10个等级,用1~10表示,数字越大表示优先级越高。• Thread类中定义了三个成员变量:

– MIN_PRIORITY:最低级(1级)

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

– MAX_PRIORITY:最高级(10级)– NORM_PRIORITY:普通优先级(5级)

• 当线程对象被创建时,默认优先级是5级。• 线程可以通过调用setPriority方法改变优先级。

– 抢占式调度• 时间片方式• 独占方式

•• 实例实例

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

– 注意:• 并不是在所有系统中运行Java程序时都采用时间片策略调度线程。一个线程在空闲时应该主动放弃CPU,以使其他同优先级和低优先级的线程得到执行。• 在Java中比较特殊的一类线程是被称作守护(Daemon)线程的低级别线程,这种线程具有最低的优先级,用于为系统中的其他对象和线程提供服务。可以通过setDaemon方法将一个线程设置为守护线程。• JVM中的系统资源回收线程就是典型的守护线程。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

2.多线程的创建与实现

•• ThreadThread类类– 在默认情况下run()是空的,必须根据需要重新设计线程的run方法,再使用start方法启动线程,将执行权转交到run。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

– 过程• 使应用程序继承Thread类,并且在该类的run方法中实现并发性处理过程。

– 优点• 可以在子类中增加新的成员变量,使线程具有某种属性;• 可以在子类中增加新的方法,是线程具有某种功能;

– 注意• run()方法必须进行重写,把要在多个线程中并行处理的代码放到这个方法中。• 虽然run()方法实现了多个线程的并行处理,但不能直接调用run()方法,而是通过start()方法来调用。在调用start()的时候,start方法会首先进行与多线程相关的初始化,然后再自动调用run()方法。

– 实例

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and TechnologyPDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

•• RunnableRunnable接口接口– Runnable接口中定义的run方法

– 还可以通过向Thread()类构造方法传递一个实现了Runnable接口的对象来创建进程。Thread类有多个构造方法,其中可以接受Runnable参数的构造方法有以下两个:

– 通过Runnable接口实现线程的步骤:• 定义实现Runnable接口的类

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

• 创建实现Runnable接口的类对象• 创建线程对象• 启动线程

– 实现接口Runnable的类仍然可以继承其他父类,例如:

– 实现Runnable接口的多线程应用程序框架:

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

– 实例

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

public class RunnableEx{public static void main(String args[]){

Target first,second;first=new Target("First thread ");second=new Target("Second thread ");Thread one,two;one=new Thread(first);two=new Thread(second);one.start();two.start();

}}class Target implements Runnable{

String s;public Target(String s){

this.s=s;System.out.println(s+"Now building");

}public void run(){

System.out.println(s+"Now running");try{

Thread.sleep(1000);}catch(InterruptedException e){System.out.println(e.toString());}System.out.println(s+"Now over");

}}

First thread Now buildingSecond thread Now buildingFirst thread Now runningSecond thread Now runningSecond thread Now overFirst thread Now over

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

•• 继承继承ThreadThread类与实现类与实现RunnableRunnable接口的对比接口的对比– 使用Runnable接口:可以将CPU、代码和数据分开,形成清晰的风格;可以从其他类继承,适用面广,保持程序风格的一致性;– 直接继承Thread类:编写简单,可以直接操纵线程,但不能再从其他类继承。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and TechnologyPDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

也可以将以上模式改写如下:声明一个Thread类的子类,并实现Runnable接口。然后在客户端程序中用

这个类生成一个对象,并调用它的start方法来启动线程。

不推荐使用以上模式,因为它将任务和任务运行的机制混在一起。将任务从

线程中分离出来是比较好的设计。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

3.线程的同步与死锁

•• 线程的等待线程的等待– Java程序中的线程并发运行将共同争夺CPU资源,哪个线程抢夺到CPU资源后就开始运行。线程之间往往会有冲突,为规避这种现象,引入线程等待的概念。– 由于线程的调度执行是按照优先级高低的顺序进行,因此可使用sleep方法让高级线程休眠一段时间使得低优先级线程有机会执行。– 暂停线程执行的方法

• sleep()方法• suspend()方法和resume()方法

– 线程的暂停和恢复可通过调用suspend()方法使线程暂时由可运行状态切换到不可运行状态。若此线程想再回到可运行状态,必须由其他线程调用resume()方法来实现。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

• join()方法– 表示当前线程等待调用该方法的线程结束后再恢复执行。– 一个线程A在占有CPU资源期间,可以让其他线程B调用join()方法与本线程联合,即“B.join();”,这样称A在运行期间联合了B。– 如果线程A在占有CPU资源期间一旦联合B线程,那么A线程将立即中断运行,一直等到它联合的线程B执行完毕,A线程才再重新排队等待CPU资源。– 如果A准备联合的线程B已经结束,则“B.join();”语句将不起任何作用。

由于普遍认为上述两种方法和stop方法具有内在的不安全因素,在Java2中不提倡这些方法。为了替代stop()的使用,可以通过给Thread变量赋值为null,指出要停止它的运行。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

– 实例

class ThreadEx2 extends Thread{String s;int m,i=0;ThreadEx2(String s){

this.s=s;}public void run(){

try{for(i=0;i<4;i++){

sleep((int)(500*Math.random()));System.out.println(s);

}System.out.println(s+"finished");

}catch(InterruptedException e){return;}}public static void main(String args[]){

ThreadEx2 a=new ThreadEx2("a ");ThreadEx2 b=new ThreadEx2("b ");a.start();b.start();System.out.println("main is finished");

}}

main is finisheda b a b a b a a finishedb b finished

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

public class ThreadJoin{public static void main(String argsp[]){

TJoin a = new TJoin();a.threadA.start(); a.threadB.start();

}}class TJoin implements Runnable{

Thread threadA,threadB;String content[]={"各就位","预备","跑"};public TJoin(){

threadA=new Thread(this); threadB=new Thread(this);threadB.setName("发令员");

}public void run(){

if(Thread.currentThread()==threadA){System.out.println("等待"+threadB.getName()+"发令");try{threadB.join();}catch(InterruptedException e){return;}System.out.println("开始跑!");

}else if(Thread.currentThread()==threadB){

System.out.println(threadB.getName()+"发令:");for(int i=0;i<content.length;i++){

System.out.println(content[i]);try{threadB.sleep(1000);}catch(InterruptedException e){return;}

}}

}}

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

•• 线程的同步线程的同步– 线程同步概念的提出

• Java应用程序的多个线程共享同一进程的数据资源,多个用户线程在并发运行过程中可能会同时访问具有敏感性的内容。一个线程需要修改该内容时,另一个线程也要修改该内容,系统需要对以上情况作出使当的处理。• Java中定义了线程同步的概念,用来实现共享数据的一致性维护。• 线程同步的使用主要是保证一个进程中多个线程的协调工作,使得线程安全的共享数据,转换和控制线程的执行,保证内存的一致性。

– 实现同步的方法• 在共享内存变量的方法前加上synchronized修饰符,在程序的运行中,如果某一线程调用经synchronized修饰的方法,在该线程结束此方法之前,其他所有线程都不能运行该方法,只有等待到该方法被释放为止。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

• 在线程同步中要做的第一件事就是要把修改数据的方法用关键字synchronized来修饰。

– synchronized修饰符的使用• 修饰方法

• 放在对象前面限制一段代码的执行

• 作为类修饰符,表明该类中的方法都是synchronized的

– 实例:

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

public class BankEx{public static void main(String args[]) throws InterruptedException{

BankSave bs=new BankSave();Operator o1=new Operator(bs,"丈夫");Operator o2=new Operator(bs,"妻子");Thread t1=new Thread(o1);Thread t2=new Thread(o2);t1.start(); t2.start();Thread.currentThread().sleep(500);

}}class BankSave{

private static int money=1000;public void add(int i){

money=money+i;System.out.println("丈夫存入"+i+"元,余额"+money+"元");

}public void get(int i){

if(i<=money){money=money-i;System.out.println("妻子取走"+i+"元,余额"+money+"元");

}else System.out.println("余额不足");

}public int showMoney(){return money;}

}

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

丈夫存入1000元,余额2000元丈夫存入1000元,余额3000元丈夫存入1000元,余额4000元丈夫存入1000元,余额5000元丈夫存入1000元,余额6000元妻子取走1000元,余额5000元妻子取走1000元,余额4000元妻子取走1000元,余额3000元妻子取走1000元,余额2000元妻子取走1000元,余额1000元

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

•• 线程的死锁线程的死锁– 当两个或多个线程等待一个不可能满足的条件时会发生死锁。– 死锁是发生在线程间相互阻塞的现象,允许多个线程并发访问共享资源时,必须提供同步机制,然而如果对这种机制使用不当,可能会出现线程永远被阻塞的现象。– 例如两个线程分别等待对方各自占有的一个资源,就会产生死锁。– Java本身既不能预防死锁,也不能发现死锁,只能靠程序设计上的谨慎来避免。

•• 线程的调度线程的调度– 线程调度方法

• Java的线程同步运行会竞争资源,但是它们的任务间存在一定的关联,这就需要调度。• Java提供了三个方法实现线程的调度和通信:

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

– wait():等待方法。可让当前线程放弃监视器并进入睡眠状态,直到其他线程进入同一监视器并调用notify()方法为止。– notify():唤醒方法。可唤醒同一对象监视器中调用了wait()方法的第一个线程,并把它移入锁申请队列。– notifyAll():唤醒所有线程的方法。唤醒同一对象监视器中调用了wait()方法的所有线程,具有最高优先级的线程首先被唤醒。

– 线程的调度规则• 如果两个或两个以上的线程都修改一个对象,那么把执行修改的方法定义为synchronized的,若对象更新影响到只读方法,那么只读方法应该也定义为同步的;• 如果一个线程必须等待一个对象状态发生变化,那么它应该在对象内部等待,而不是在外部等待,它可以调用一个被同步的方法,并让这个方法调用wait();• 每当一个方法改变某个对象的状态时,它应该调用notifyAll()方法,这给等待队列的线程提供机会来看一看执行环境是否已发生变化。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

• wait()、notify()、notifyAll()方法属于Object类而非Thread类,在使用时要仔细检查是否每个wait()方法都有相应的notify()或notifyAll()方法且作用于相同的对象。• 对于多个线程同时等待某个条件的情况,当条件满足时应该使用notifyAll()方法唤醒。否则某些线程会一直等待下去导致死锁。更为保险的做法是,任何情况下都使用notifyAll()方法唤醒等待的线程。• 应当区分由于得不到锁而等待的线程和由于调用了wait()方法而等待的线程。• wait()和notify()只能在一个同步的方法或代码块内部调用,若在一个不同步的方法内调用,运行时将产生非法监视器状态异常(IllegalMonitorStateException)。• Java中的每个类都有一个主线程。要执行一个程序,那么这个类当中一定要有main方法,main方法就是Java类中的主线程。

– 实例:模拟三人买票• 说明:

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

– 售货员最初只有一张五元钱,电影票五元一张。– 排队的三人中,A有一张二十元人民币,B有一张十元人民币,C有一张五元人民币。如果给售票员的钱不是零钱,而售票员又没有零钱找,那么此人必须等待,并允许后面的人买票,以便售票员获得零钱。

• 分析– A/B/C三人 买票就像三个线程,每个线程必须满足以上条件(售票员有足够的找零或者提供售票员的是五元零钱)才能执行;– A/B/C三人同时只能有一人向售票员买票;不满足条件时需要等待。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and TechnologyPDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

else if(receiveMoney==20){while(five<1||ten<1){

try{System.out.println(Thread.currentThread().getName()+" 请您一旁等待");wait();System.out.println(Thread.currentThread().getName()+"结束等待");

}catch(InterruptedException e){return;

}}five--;ten--;twenty++;

System.out.println(Thread.currentThread().getName()+"给我二十元钱,售票一张,找零十五元");}notifyAll();

}}public class TicketEx{

public static void main(String args[]){Cinema a = new Cinema();a.A.start();a.B.start();a.C.start();

}}

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

School of Computer Science and Technology

•• 思考题:等待电梯的程序思考题:等待电梯的程序– 假设电梯停靠楼层为1~8,8个楼层都有可能有人要求搭乘电梯去往目的楼层。– 电梯控制台在同一时刻只可能对一个方向(上、下)作出响应,并到达得到响应的楼层。– 再复杂一些,在响应后的某个行进方向上如果有楼层的搭乘要求符合该方向,则“顺便”完成该要求。

• 如当前在8楼的电梯响应1楼的要求向下行进,如果此时4楼的向下键被按下,则在4楼停靠,顺路搭载4楼的用户。

– 电梯的运行时间上的延迟可以用sleep方法完成,未得到响应的用户则通过wait来等待。

PDF 文件使用 "pdfFactory Pro" 试用版本创建 www.fineprint.cn

top related