《面向对象程序设计项目教程》课件 项目12 认识多线程_第1页
《面向对象程序设计项目教程》课件 项目12 认识多线程_第2页
《面向对象程序设计项目教程》课件 项目12 认识多线程_第3页
《面向对象程序设计项目教程》课件 项目12 认识多线程_第4页
《面向对象程序设计项目教程》课件 项目12 认识多线程_第5页
已阅读5页,还剩36页未读 继续免费阅读

下载本文档

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

文档简介

面向对象程序设计项目教程本章学习目标:●

掌握线程创建的过程●

掌握线程的生命周期●掌了解线程同步机制以及线程通信●

了解线程的优先级●

掌握线程的同步与死锁项目12认识多线程任务1part了解线程在Windows操作系统中,右击任务栏,选择“启动任务管理器”菜单命令,可以打开“Windows任务管理器”窗口,该窗口中的“进程”选项卡中显示系统当前正在运行的进程,如图12.1所示。1.1线程和进程进程

进程具有如下三个特征:

(1)独立性:进程是操作系统中独立存在的实体,拥有自己独立的资源,每个进行都拥有自己私有的地址空间,其他进程不可以直接访问该地址空间,除非进程本身允许的情况下才能进行访问。

(2)动态性:程序只是一个静态的指令集合,只有当程序进入内存运行时,才变成一个进程。进程是一个正在内存中运行的、动态的指令集合,进程具有自己的生命周期和各种不同状态。

(3)并发性:多个进程可以在单个处理器上并发执行,多个进程之间互不影响。1.1线程和进程

线程是进程的组成部分,一个线程必须在一个进程之内,而一个进程可以拥有多个线程,一个进程中至少有一个线程。线程是最小的处理单位,线程可以拥有自己的堆栈、计数器和局部变量,当不能拥有系统资源,多个线程共享其所在进程的系统资源。线程可以完成一定的任务,使用多线程可以在一个程序中同时完成多个任务,在更低的层次中引入多任务处理。

多线程在多CPU的计算机中可以实现真正物理上的同时执行;而对于单CPU的计算机实现的只是逻辑上的同时执行,在每个时刻,真正执行的只有一个线程,由操作系统进行线程管理调度,但由于CPU的速度很快,让人感到像是多个线程在同时执行。1.1线程和进程线程

多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。因此,线程也被称作轻量级进程。多进程与多线程是多任务的两种类型,两者之间的主要区别如下:

(1)进程之间的数据块是相互独立的,彼此互不影响,进程之间需要通过信号、管道等进行交互。

(2)多线程之间的数据块可以共享,一个进程中的多个线程可以共享程序段、数据段等资源。多线程比多进程更便于资源共享,同时Java提供的同步机制还可以解决线程之间的数据完整性问题,使得多线程设计更易发挥作用。多线程编程的优点如下:

(1)多线程之间共享内存,节约系统资源成本;

(2)充分利用CPU,执行并发任务效率高;

(3)Java内置多线程功能支持,简化编程模型

(4)GUI应用通过启动单独线程收集用户界面事件,简化异步事件处理,使GUI界面的交互性更好。1.1线程和进程Java线程模型提供线程所必需的功能支持,基本的Java线程模型有Thread类、Runnable接口、Callable接口和Future接口等,这些线程模型都是面向对象的。Thread类将线程所必需的功能进行封装,其常用的方法如表12-1所示。1.2Java线程模型Thread类的run()方法是线程中最重要的方法,该方法用于执行线程要完成的任务;当创建一个线程时,要完成自己的任务,则需要重写run()方法。此外,Thread类还提供了start()方法,该方法用于负责线程的启动;当调用start()方法成功地启动线程后,系统会自动调用Thread类的run()方法来执行线程。因此,任何继承Thread类的线程都可以通过start()方法来启动。Runnable接口用于标识某个Java类可否作为线程类,该接口只有一个抽象方法run(),即线程中最重要的执行体,用于执行线程中所要完成的任务。Runnable接口定义在java.lang包中,定义代码如下所示。packagejava.lang;publicinterfaceRunnable{ publicabstractvoidrun();}1.2Java线程模型Callable接口是Java5新增的接口,该接口中提供一个call()方法作为线程的执行体。call()方法比run()方法功能更强大,call()方法可以有返回值,也可以声明抛出异常。Callable接口定义在java.util.concurrent包中,定义代码如下所示。packagejava.util.concurrent;publicinterfaceCallable<V>{ Vcall()throwsException;}1.2Java线程模型Future接口用来接收Callable接口中call()方法的返回值。Future接口提供一些方法用于控制与其关联的Callable任务。Future接口提供的方法如表12-2所示。1.2Java线程模型Callable接口有泛型限制,该接口中的泛型形参类型与call()方法返回值的类型相同;而且Callable接口是函数式接口,因此从Java8开始可以使用Lambda表达式创建Callable对象。

每个能够独立运行的程序就是一个进程,每个进程至少包含一个线程,即主线程。在Java语言中,每个能够独立运行的Java程序都至少有一个主线程,且在程序启动时,JVM会自动创建一个主线程来执行该程序中的main()方法。因此,主线程有以下两个特点:

(1)一个进程肯定包含一个主线程

(2)主线程用来执行main()方法

1.3主线程主线程任务2part创建线程

基于Java线程模型,创建线程的方式有三种:

(1)第一种方式是继承Thread类,重写Thread类中的run()方法,直接创建线程。

(2)第二种方式是实现Runnable接口,再通过Thread类和Runnable的实现类间接创建一个线程。

(3)第三种方式是使用Callable接口或Future接口间接创建线程。

创建线程2.1继承Thread类

通过继承Thread类来创建并启动线程的步骤如下:

(1)定义一个子类继承Thread类,并重写run()方法。

(2)创建子类的实例,即实例化线程对象。

(3)调用线程对象的start()方法启动该线程。Thread类的start()方法将调用run()方法,该方法用于启动线程并运行。因此start()方法不能多次调用,当多次调用td.start()方法时会抛出一个IllegalThreadStateException异常。

继承Thread类2.2实现Runable接口

创建线程的第二种方式是实现Runnable接口。Runnable接口中只有一个run()方法,一个类实现Runnable接口后,并不代表该类是个“线程”类,不能直接启动线程,必须通过Thread类的实例来创建并启动线程。通过Runnable接口创建并启动线程的步骤如下:

(1)定义一个类实现Runnable接口,并实现该接口中的run()方法;

(2)创建一个Thread类的实例,将Runnable接口的实现类所创建的对象作为参数传入Thread类的构造方法中;

(3)调用Thread对象的start()方法启动该线程。

2.3使用Callable和Future接口

创建线程的第三种方式是使用Callable和Future接口。Callable接口提供一个call()方法作为线程的执行体,该方法的返回值使用Future接口来代表。从Java5开始,为Future接口提供一个FutureTask实现类,该类同时实现了Future和Runnable两个接口,因此可以作为Thread类的target参数。使用Callable和Future接口的最大优势在于可以在线程执行完成之后获得执行结果。

使用Callable和Future接口创建并启动线程的步骤如下:

(1)创建Callable接口的实现类,并实现call()方法,该方法将作为线程的执行体,并具有返回值;然后创建Callable实现类的实例。

(2)使用FutureTask类来包装Callable对象,在FutureTask对象中封装了Callable对象的call()方法的返回值。

(3)使用FutureTask对象作为Thread对象的target,创建并启动新线程。

(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

任务3part掌握线程的生命周期

线程具有生命周期,当线程被创建并启动后,不会立即进入执行状态,也不会一直处于执行状态。在线程的生命周期中,要经过5种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。线程状态之间的转换如图12-3所示。线程的生命周期本节介绍3.1新建和就绪状态

当程序使用new关键字创建一个线程之后,该线程就处于新建状态。新建状态的线程没有表现出任何动态特征,程序也不会执行线程的执行体。当线程对象调用start()方法之后,线程就处于就绪状态,相当于“等待执行”。此时,调度程序就可以把CPU分配给该线程,JVM会为线程创建方法调用栈和程序计数器。处于就绪状态的线程并没有开始运行,只是表示该线程准备就绪等待执行。

注意只能对新建状态的线程调用start()方法,即new完一个线程后,只能调用一次start()方法,否则将引发IllegalThreadStateException异常。

3.2运行和阻塞状态

处于就绪状态的线程获得CPU后,开始执行run()方法的线程执行体,此时该线程处于运行状态。如果计算机的CPU是单核的,则在任何时刻只有一个线程处于运行状态。一个线程开始运行后,不可能一直处于运行状态。线程在运行过程中需要被中断,目的是使其他线程获得执行的机会,线程调度的细节取决于底层平台所采用的策略。

目前UNIX系统采用的是时间片算法策略,Windows系统采用的则是抢占式策略,另外一种小型设备(手机)则可能采用协作式调度策略。对于采用抢占式策略的系统而言,系统会给每个可执行的线程一个小时间段来处理任务;当该时间段用完后,系统就会剥夺该线程所占用的资源,让其他线程获得执行的机会。在选择下一个线程时,系统会考虑线程的优先级。运行和阻塞状态当线程出现以下情况时,会进入阻塞状态:(1)调用sleep()方法,主动放弃所占用的处理器资源;(2)调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞;(3)线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有;(4)执行条件还未满足,调用wait()方法使线程进入等待状态,等待其他线程的通知;(5)程序调用了线程的suspend()方法将该线程挂起,但该方法容易导致死锁,因此应该尽量避免使用。3.2运行和阻塞状态

正在执行的线程被阻塞之后,其他线程就可以获得执行的机会,被阻塞的线程会在合适的时机重新进入就绪状态,等待线程调度器再次调度。

当线程出现如下几种情况时,线程可以解除阻塞进入就绪状态:

(1)调用sleep()方法的线程经过了指定的时间;

(2)线程调用的阻塞式IO方法以经返回;

(3)线程成功地获得了同步监视器;

(4)线程处于等待状态,其他线程调用notify()或notifyAll()方法发出了一个通知时,则线程回到就绪状态;

(5)处于挂起状态的线程被调用了resume()恢复方法。3.2运行和阻塞状态

在线程运行的过程中,可以通过sleep()方法使线程暂时停止运行,进入休眠状态。在使用sleep()方法时需要注意以下两点:

(1)sleep()方法的参数是以毫秒为基本单位,例如sleep(2000)则休眠2秒钟;

(2)sleep()方法声明了InterruptedException异常,因此调用sleep()方法时要么放在try…catch语句中捕获该异常并处理,要么在方法后使用throws显式声明抛出该异常。

可以通过Thread类的isAlive()方法来判断线程是否处于运行状态。当线程处于就绪、运行和阻塞三种状态时,isAlive()方法的返回值为true;当线程处于新建、死亡两种状态时,isAlive()方法的返回值为false。3.2死亡状态线程结束后就处于死亡状态,结束线程有以下三种方式:(1)线程执行完成run()或call()方法,线程正常结束;(2)线程抛出一个未捕获的Exception或Error;(3)调用stop()方法直接停止线程,该方法容易导致死锁,通常不推荐使用。3.3死亡状态

主线程结束时,其他子线程不受任何影响,并不会随主线程的结束而结束。一旦子线程启动起来,子线程就拥有和主线程相同的地位,子线程不会受主线程的影响。

为了测试某个线程是否死亡,可以通过线程对象的isAlive()方法来获得线程状态,当方法返回值为false时,线程处于死亡或新建状态。不要试图对一个已经死亡的线程调用start()方法使其重新启动,线程死亡就是死亡,该线程不可再次作为线程执行。Thread类中的join()方法可以让一个线程等待另一个线程完成后,继续执行原线程中的任务。当在某个程序执行流中调用其他线程的join()方法时,当前线程将被阻塞,直到另一个线程执行完为止。join()方法通常由使用线程的程序调用,当其他线程都执行结束后,再调用主线程进一步操作。

3.3任务4part了解线程的优先级Thread类提供三个静态常量来标识线程的优先级:

(1)MAX_PRIORITY:最高优先级,其值为10;

(2)NORM_PRIORITY:普通优先级,其值为5;

(3)MIN_PRIORITY:最低优先级,其值为1。线程的优先级Thread类提供了setPriority()方法来对线程的优先级进行设置,而getPriority()方法来获取线程的优先级。setPriority()方法的参数是一个整数(1~10),也可以使用Thread类提供的三个优先级静态常量。

线程的优先级高度依赖于操作系统,并不是所有的操作系统都支持Java的10个优先级,例如Windows2000仅提供7个优先级。因此,尽量避免直接使用整数给线程指定优先级,提倡使用MAX_PRIORITY、NORM_PRIORITY和MIN_PRIORITY三个优先级静态常量。线程的优先级任务5part掌握线程的同步5.1同步代码块

使用同步代码块实现同步功能,只需将对实例的访问语句放入一个同步块中,其语法格式如下:synchronized(object){ //需要同步的代码块}

其中:synchronized是同步关键字;object是同步监视器,其数据类型不能是基本数据类型。线程开始执行同步代码之前,必须先获得同步监视器的锁定,并且,任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。5.2同步方法

同步方法是使用synchronized关键字修饰的方法,其声明的语法格式如下:[访问修饰符]synchronized返回类型方法名([参数列表]){ //方法体}

其中:synchronized关键字修饰的实例方法无须显式地指定同步监视器,同步方法的同步监视器是this,即该方法所属的对象。一旦一个线程进入一个实例的任何同步方法,其他线程将不能进入该实例的所有同步方法,但该实例的非同步方法仍然能够被调用。

5.3同步锁

同步锁Lock是一种更强大的线程同步机制,通过显式定义同步锁对象来实现线程同步。同步锁提供了比同步代码块、同步方法更广泛的锁定操作,实现更灵活。Lock是控制多个线程对共享资源进行访问的工具,能够对共享资源进行独占访问。每次只能有一个线程对Lock对象加锁,线程访问共享资源之前需要先获得Lock对象。某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁)。Lock和ReadWriteLock是Java5提供的关于锁的两个根接口,并为Lock提供了ReentrantLock(可重入锁)实现类,为ReadWriteLock提供了ReentrantReadWriteLock实现类。从Java8开始,又新增了StampedeLock类,可以替代传统的ReentrantReadWriteLock类。同步锁ReentrantLock类是常用的可重入同步锁,该类对象可以显式地加锁、释放锁。使用ReentrantLock类的步骤如下:

(1)定义一个ReentrantLock锁对象,该对象是final常量;privatefinalReentrantLocklock=newReentrantLock();

(2)在需要保证线程安全的代码之前增加“加锁”操作;lock.lock();

(3)在执行完线程安全的代码后“释放锁”。lock.unlock();5.3同步锁下述代码示例了使用ReentrantLock锁的基本步骤://1.定义锁对象privatefinalReentrantLocklock=newReentrantLock();...//定义需要保证线程安全的方法publicvoidmyMethod(){//2.加锁lock.lock();try{//需要保证线程安全的代码...}finally{//3.释放锁lock.unlock();}}

其中:加锁和释放锁都需要放在线程安全的方法中;lock.unlock()放在finally语句中,不管发生异常与否,都需要释放锁。5.3任务6part实现线程通信线程通信可以使用Object类中定义的wait()、notify()和notifyAll()方法,使线程之间相互进行事件通知。在执行这些方法时,必须同时拥有相关对象的锁。

(1)wait()方法:让当前线程等待,并释放对象锁,直到其他线程调用该监视器的notify()或notifyAll()方法来唤醒该线程。wait()方法也可以带一个参数,用于指明等待的时间,使用这种方式不需要notify()或notifyAll()方法来唤醒。wait()方法只能在同步方法中调用。

温馨提示

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

评论

0/150

提交评论