收藏 分享(赏)

第9章 多线程.ppt

上传人:mcady 文档编号:10201152 上传时间:2019-10-18 格式:PPT 页数:22 大小:335.50KB
下载 相关 举报
第9章 多线程.ppt_第1页
第1页 / 共22页
第9章 多线程.ppt_第2页
第2页 / 共22页
第9章 多线程.ppt_第3页
第3页 / 共22页
第9章 多线程.ppt_第4页
第4页 / 共22页
第9章 多线程.ppt_第5页
第5页 / 共22页
点击查看更多>>
资源描述

1、JavaEE基础教程,第9章 多线程,9.1 线程概述,9.2 线程的创建,9.3 线程的调度,Java EE基础教程,2,2019年10月18日星期五,9.1 线程概述,9.1.1 进程的概念进程是程序的一次执行过程,对应了从代码的加载、执行到执行结束这样一个完整的过程,也是进程从产生、发展到消亡的过程。每个进程在计算机的内存中都对应一段专有的内存空间。现在的操作系统都支持多进程操作,比如:计算机可以同时播放视频、声音,同时还可以上网、聊天等。本章介绍的多线程技术与多进程技术是不一样的。 9.1.2 线程的概念线程是比进程更小的执行单元,单个进程的执行可以产生多个线程。每个线程都有独立的生命

2、周期,同一个进程中的线程共享同样的内存空间,并通过共享的内存空间来达到数据交换、通信和同步等工作。在基于线程的多任务处理环境中,线程是执行特定任务的最小单位。一个程序可分为多个任务,每个任务都可分配给一个线程来实现。在Java程序启动时,一个进程马上启动,同时该进程会自动启动一个线程的运行,这个线程称为程序的主线程。因为它是在程序启动后就执行的。该主线程是多线程编程的核心,它是产生其他子线程的线程。在多线程运行时,它是第一个启动的线程。由该线程控制其他线程的启动,执行各种关闭操作。,返回,Java EE基础教程,3,2019年10月18日星期五,9.2 线程的创建,Java在类和接口方面提供了

3、对线程的内置支持,任何类如果希望能够以线程的形式运行,都需要实现接口java.lang.Runnable;或者继承java.lang.Thread类。Runnable接口只有一个run()方法,实现该接口的类必须重写该方法。而Thread类也实现了Runnable接口,但该类有更丰富的方法。Thread类的常用方法包括start()方法、run()方法和join()、interrupt()方法、join()方法等。start()方法用于启动线程,而run()方法是线程的主体方法,线程完成的功能代码都写在该方法体内。Thread类定义了8个常用的构造方法。下面的两个是较为常用的:Thread()

4、 /创建一个具有默认参数值的Thread对象 Thread(String name) /创建一个线程名为name的Thread对象,返回,Java EE基础教程,4,2019年10月18日星期五,9.2.1 继承Thread类该类具有创建和运行线程的所有功能,通过重写该类的run()方法,实现用 户所需的功能。通过实例化自定义的Thread类,使用start()方法启动线程。例9-1 继承Thread类创建MyThread1类,显示主线程的信息,创建子线程并启动它 程序清单:ch09MyThread1.java public class MyThread1 extends Thread pub

5、lic static void main(String args) Thread t=Thread.currentThread(); /ASystem.out.println(“当前主线程是:“+t); /Bt.setName(“MyThread1“); /CSystem.out.println(“当前主线程是:“+t); /DMyThread1 mt=new MyThread1(); /Emt.start(); /F,Java EE基础教程,5,2019年10月18日星期五,public void run() /Gint sum=0;for(int i=0;i101;i+) sum+=i;S

6、ystem.out.println(“1+2+.+100=“+sum); 程序的执行结果如下所示: 当前主线程是:Threadmain,5,main 当前主线程是:ThreadMyThread1,5,main 1+2+.+100=5050分析上面的程序代码可知,A行代码通过调用Thread类的currentThread()静态方法获得 当前主线程的引用。然后在B行代码输出主线程的信息;输出结果“Threadmain,5,main” 的第一个main代表主线程的名称,5代表它的优先级,第二个main代表线程组。通过代码行C 修改主线程的名称,代码行D再次输出主线程的信息:第一个参数值改为修改后的

7、值 “MyThread1”。代码行E创建了子线程mt,代码行F启动了该子线程,执行线程类的run() 方法。从代码行G开始,重写了Thread类的run()方法,实现自定义的功能,本例是实现了 “1到100的累加和”的功能。注意:在本例中,如果不重写run()方法,程序正常运行, 只是此时的子线程不完成任何功能;子线程mt的启动是在主线程中实现的,而子线程在运行 完run()方法后也自动终止了。,Java EE基础教程,6,2019年10月18日星期五,9.2.2 实现Runnable接口在Java中,不仅可以通过继承Thread类实现多线程的功能,也可以通过实现Runnable接口 来实现同

8、样的功能。由于Java语言规定的单一继承原则,所以如果希望用户自定义的类继承其他类,此时可以通过实现Runnable接口的方式使用线程。 例9-2 创建SimpleThread类,实现Runnable接口,并在run()方法中实现规定的输出功能:在控制台输出字符“*”。 程序清单:ch09SimpleThread.java public class SimpleThread implements Runnable public static void main(String args) Thread t=new Thread(new SimpleThread(),“线程1“); /At.sta

9、rt(); System.out.println(“主线程运行结束“); /Bpublic void run() int i=1;while(i=10)try System.out.print(“*“);Thread.sleep(1000); /C,Java EE基础教程,7,2019年10月18日星期五,catch (InterruptedException e) e.printStackTrace(); i+; 类SimpleThread实现了线程接口Runnable,重写了接口中的方法run()。在代码行A中, 通过调用Thread类的构造函数:public Thread(Runnabl

10、e target) 创建了一个线程对象t;然后启动该线程。在run()方法中的代码行C,调用了Thread类的静态 方法sleep(long millis),该方法的参数代表线程休眠的毫秒数,本例中的1000代表1秒钟。 可见,run()方法实现的功能是每个1秒钟输出一个字符“*”,一共输出10个字符。在运行该 程序时,主线程在运行到代码行B时已经结束了,而子线程的run()方法此时还在运行。显 然,两个线程的运行是相互独立的,主线程只能启动子线程的运行,而不能终止它的运行。程序的输出结果如下所示:主线程运行结束 *,Java EE基础教程,8,2019年10月18日星期五,9.3 线程的调度

11、,9.3.1 线程的生命周期线程从创建到死亡的整个过程称为线程的一个“生命周期”。在某个时间点上,线程具有不同的状态,主要的状态有如下几种: 创建状态 可执行状态 非执行状态 终止状态线程的各个状态间的关系如图9-1所示:,返回,Java EE基础教程,9,2019年10月18日星期五,图9-1线程的状态转换图,Java EE基础教程,10,2019年10月18日星期五,下面根据图9-1分别介绍线程生命周期的各个状态:(1) 创建状态当使用线程类的构造函数创建某个线程类的对象时,线程处于“创建状态”,在调用了对 象的start()方法后,线程进入了“可执行”状态。 (2) 可执行状态在线程进入

12、“可执行”状态后,如果系统的CPU空闲,则线程就可以直接投入运行了。在 线程运行时,如果调用线程的wait()方法或者sleep()方法,则线程进入了“非可执行”状 态,此时系统的CPU不再分配时间片给该线程。 (3) 非可执行状态在线程进入“非可执行”状态后,可以通过调用线程的notify()方法或者notifyall()方 法、interrupt()方法再次进入“可执行”状态。 (4) 终止状态当线程的run()方法执行完毕后,线程自动消亡,该线程占用的系统资源会自动释放。该 线程的整个生命周期就此结束。,Java EE基础教程,11,2019年10月18日星期五,9.3.2 线程的优先级

13、根据前面介绍的生命周期,线程创建后调用了start()方法就进入了“可执行”状态。 如果同时有多个线程进入了“可执行”状态,而系统只有一个CPU时,或者CPU的个数少于进入 “可执行”状态的线程的个数时,如何调度线程的运行呢?此时可以通过设定线程的优先级来 决定首先执行哪个线程。线程的优先级是通过Thread类中定义的常量来实现的,Thread类中定 义了三个此类常量: MAX_PRIORITY 线程的最高优先级,代表常量值10 NORM_PRIORITY 线程的默认优先级,代表常量值5 MIN_PRIORITY 线程的最低优先级,代表常量值1在Java中的每个线程都有一个优先级。在缺省情况下

14、,线程的优先级为NORM_PRIORITY或 5。设置和获取线程优先级的方法有:void setPriority(int newPriority)int getPriority()在线程运行时,一旦高优先级的线程要运行,则低优先级的线程将进入“非可执行”状 态,CPU的控制权交给高优先级的线程。,Java EE基础教程,12,2019年10月18日星期五,9.3.3 线程的同步通过线程的优先级可以设置线程占用CPU时间的策略,保证多个线程能合理地、 顺序地占用CPU时间,执行自己的run()方法。但是,如果出现多个线程同时操作 一个共享资源,比如打印机、文件等,如何分配多个线程对打印机的操作和

15、控制? 为了处理这种对共享资源的竞争,Java语言提供了线程的同步机制。所谓同步机制 是指两个或多个线程同时访问一个对象时,应该保存对象数据的统一性和完整性。 同步是基于“监视器”的概念,类似于平时说的“黑盒子”,一旦某个线程获得控 制权后,其他线程只能等待,直到原先的线程放弃对“黑盒子”的控制,其他线程 才能获得对“黑盒子”的控制权。Java语言提供了对线程同步的内置支持,通过使 用Java语言提供的synchronized关键字,可以采用同步方法或同步代码块的方法实 现线程的同步。下面分别介绍如何使用同步方法和同步代码块实现线程的同步。 (1)同步方法同步方法是指将访问共享资源的方法都标记

16、为synchronized,这样当某个线程调 用了该方法后,其他调用该方法的线程将进入阻塞状态,直到原线程完成对 synchronized方法的调用为止。,Java EE基础教程,13,2019年10月18日星期五,例9-3 创建两个线程,同时调用某个类的print()方法,把print()方法定义为同步和 非同步两种方法,分析执行结果的差异。 程序清单:ch09SyncExample.java class PrintCHpublic static /*synchronized*/ void print(char c)for(int i=0;i4;i+)System.out.print(c);

17、try Thread.sleep(1000); catch (InterruptedException e) e.printStackTrace(); public class SyncExample extends Thread private char ch;public SyncExample(char ch) this.ch = ch;,Java EE基础教程,14,2019年10月18日星期五,public void run()PrintCH.print(ch);public static void main(String args) SyncExample t1=new SyncE

18、xample(A);SyncExample t2=new SyncExample(B);t1.start();t2.start(); 如果去掉print()方法前的synchronized关键字,print()方法为非同步方法,则 执行结果如下所示: ABBABAAB 如果加上synchronized关键字,该方法为同步方法,则结果如下所示: AAAABBBB,Java EE基础教程,15,2019年10月18日星期五,(2) 同步代码块尽管可以在创建类的时候,把访问共享资源的方法定义为同步方法,实现线程的同步, 但是这种方法并不是一直有效的。比如:程序中调用了一个第三方类库中某个类的方法,

19、无法获得该类库的源代码。这样,无法在相关方法前添加synchronized关键字。那么,怎 么才能解决这类问题呢?通过使用Java语言的同步代码块机制可以解决这个问题。同步代 码块的一般格式如下:synchronized(object)/要同步的语句 其中,object是需要被同步的对象的引用。一个同步块在调用object对象的某个方法之前, 必须确保所在线程能够访问object对象,获得该对象的控制权。在某一时刻,只能有一个线 程获得该对象的控制权,从而保证一次只能有一个线程执行该同步块。例如:假定线程A和B都希望访问同步块中的代码,线程A已经进入同步块内,那么线程B 就必须等待。因为此时线

20、程A获得了对象object的控制权,而线程B只能等待线程A执行完同步 块中的代码,从同步块中退出,然后放弃对object的控制权,线程B才能进入同步块,执行其 中的代码。,Java EE基础教程,16,2019年10月18日星期五,例9-4 修改例9-3,用代码块实现线程同步的功能 程序清单:ch09SyncMassExample.java class PrintCH2public void print(char c)for(int i=0;i4;i+)System.out.print(c);try Thread.sleep(1000); catch (InterruptedException

21、 e) e.printStackTrace(); public class SyncMassExample extends Thread private char ch;private static PrintCH2 myprint=new PrintCH2(); /Apublic SyncMassExample(char ch) this.ch = ch;,Java EE基础教程,17,2019年10月18日星期五,public void run()synchronized(myprint) /Bmyprint.print(ch);public static void main(String

22、 args) SyncMassExample t1=new SyncMassExample(A);SyncMassExample t2=new SyncMassExample(B);t1.start();t2.start(); 这段代码与例9-3相比,添加了代码行A和B,其中A行创建了一个静态对象,代码行B采 用了同步块的语法结构,只有获得对象myprint的控制权,才能运行大括号里的代码。通过 采用这种方法,不用修改原始类PrintCH2,就可以实现同步功能了。以上两种同步线程的方法,可以根据实际情况灵活运用。,Java EE基础教程,18,2019年10月18日星期五,9.3.4 wait

23、-notify机制在前面的实例中,两个线程都试图访问某个共享资源,通过采用同步方法或 者代码块可以解决多个线程访问共享资源的问题。那么,假如有两个线程A和B, A线程需要首先访问共享资源M,然后访问共享资源N;线程B也需要访问共享资源 M和N。现在,A线程已经拥有资源M的控制权,需要资源N,线程才能正常运行; 而线程B已经获得资源N的控制权,需要资源M。此时,线程A在等待线程B释放资 源N,而线程B在等待线程A释放资源M。这样两个线程互相等待,永远不会结束, 程序进入“死锁”状态。为了解决这类问题,Java语言提供了“wait-notify机制”。Java语言通过 使用wait()、notif

24、y()和notifyAll()方法实现线程间的通信,从而尽量避 免多线程运行时出现“死锁”的情况。wait()方法通知被调用的线程放弃对共享资源的控制,进入等待状态,直 到其他线程释放了共享资源并调用notify()方法。 notify()方法唤醒同一 对象上第一次调用了wait()方法的线程。notifyAll()方法唤醒所有调用了 wait()方法的线程,此时退出睡眠状态的、优先级最高的线程将恢复执行。,Java EE基础教程,19,2019年10月18日星期五,在使用这三个方法时要注意以下几点:(1)线程调用wait()方法并进入等待状态时,会释放已经控制的共享资源,必须 由当前线程自己

25、调用wait()方法,即:是线程本身在得不到需要的资源时,主动放 弃对已有资源的控制,进入等待状态。(2)线程调用notify()和notifyAll()方法时,是在当前线程已经使用完所控制 的共享资源,并且已经放弃了对共享资源的控制时,通知其他线程恢复执行。(3)线程不能自己调用notify()或者notifyAll()方法唤醒自己;线程不能调用 wait()方法要求其他线程进入等待状态。,Java EE基础教程,20,2019年10月18日星期五,例9-5 4位哲学家用4根筷子吃饭的问题 程序清单:ch09WaitNotiExample.java本程序是Java语言使用wait-notif

26、y机制的典型例题。4位哲学家一边就餐,一边思考, 每个人之间只有一根筷子,而哲学家要就餐必须有两个筷子,所以任一时刻,只能有两个 人就餐,两个人思考。本程序创建了三个类,其中ChopStick类中定义了一个变量available,指明是否有筷 子,方法takeup()和putdown()均被定义为同步方法,takeup()方法表示已经有一根 筷子,需要等待第二根筷子,因而在方法体中调用了wait()等待另一个筷子;putdown ()方法表示放下筷子,将筷子让给别人,并调用了notify()方法通知其他被阻塞的线 程。类Philosopher是一个自定义线程类,方法eat()模拟了就餐的过程,

27、先拿两根筷 子,然后就餐;方法think()模拟了思考的过程,放下两根筷子,然后思考;run()方 法先就餐,然后睡眠1秒钟,再思考、睡眠,一直循环下去。类WaitNotiExample是测试 类,初始化两个数组:chopsticks和philos,模拟筷子和哲学家。其中四位哲学家对应了 四个线程,它们的优先级是一样的。,Java EE基础教程,21,2019年10月18日星期五,通过使用wait()和notify()方法,四个线程间实现了通信功能,避免出现死锁的 情况。在程序运行时,由于所有的筷子都空着,所以第一个线程对应的哲学家能顺利拿到 两根筷子开始就餐,而第二个线程对应的哲学家就必须等

28、待了,此时调用了wait()方法 进入等待状态;当第一位哲学家用餐后,放下筷子,调用了notify()方法,通知其他等 待的线程,可以访问共享资源:筷子。不断循环下去,每位哲学家都能顺利地获得就餐的 机会。也就是说,每个线程都能正常地运行,不会发生死锁的情况。程序的运行结果如下 所示: 哲学家 1 在用餐 哲学家等待另一根筷子 哲学家等待另一根筷子 哲学家等待另一根筷子 哲学家等待另一根筷子 哲学家 4 在用餐 哲学家 1 在思考 以上的输出结果在每次运行后可能会有所不同,但是每一位哲学家都有机会用餐、思 考,而且每一位获得的机会都是均等的。这就是线程间的wait-notify机制。,Java EE基础教程,22,2019年10月18日星期五,The End,

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报