ImageVerifierCode 换一换
格式:DOCX , 页数:20 ,大小:130.52KB ,
资源ID:5544767      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bingdoc.com/d-5544767.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(第5章 多线程教学设计.docx)为本站会员(b****4)主动上传,冰点文库仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰点文库(发送邮件至service@bingdoc.com或直接QQ联系客服),我们立即给予删除!

第5章 多线程教学设计.docx

1、第5章 多线程教学设计传智播客Java基础入门教学设计课程名称: Java基础教程 授课年级: 2014年级 授课学期: 2014学年第一学期教师姓名: 某某老师 2014年05月09日课题名称第5章 多线程计划学时6课时内容分析多线程就是指一个应用程序中有多条并发执行的线索,每条线索都被称作一个线程,它们会交替执行,彼此间可以进行通信。本章将针对Java线程的相关知识进行详细地讲解,其中包括线程的创建、线程的生命周期、线程的优先级、线程的同步以及线程的通信等教学目标及基本要求要求学生熟悉进程、线程的概念,掌握线程创建的两种方式,线程的生命周期中的五种状态以及五种状态之间的转换,了解线程的调度

2、,线程的安全和同步以及多线程之间的通信。重点及措施教学重点:多线程的概念、线程的生命周期及状态转换、线程的生命周期、多线程通信难点及措施教学难点:线程的生命周期、线程的安全和同步、线程的生命周期及状态转换教学方式教学采用教师课堂讲授为主,使用教学PPT讲解教学过程第一课时(线程概念、线程的创建) 线程概念 进程在一个操作系统中,每个独立执行的程序都可称之为一个进程,也就是“正在运行的程序”。目前大部分计算机上安装的都是多任务操作系统,即能够同时执行多个应用程序,最常见的有Windows、Linux、Unix等。 线程每个运行的程序都是一个进程,在一个进程中还可以有多个执行单元同时运行,这些执行

3、单元可以看做程序执行的一条条线索,被称为线程。 单线程和多线程的区别在前面章节所接触过的程序中,代码都是按照调用顺序依次往下执行,没有出现两段程序代码交替运行的效果,这样的程序称作单线程程序。如果希望程序中实现多段程序代码交替运行的效果,则需要创建多个线程,即多线程程序。多线程程序在运行时,每个线程之间都是独立的,它们可以并发执行,如下图所示。 线程的创建 两种创建线程的方式在Java中,线程的创建方式有两种,具体如下: 继承Thread类,覆写Thread类的run()方法,示例代码如下:class MyThread extends Thread public void run() whil

4、e (true) / 通过死循环语句打印输出 System.out.println(MyThread类的run()方法在运行); 实现Runnable接口,示例代码如下:class MyThread implements Runnable / 线程的代码段,当调用start()方法时,线程从此处开始执行 public void run() while (true) System.out.println(MyThread类的run()方法在运行); 两种实现多线程方式比较实现Runnable接口相对于继承Thread类来说,有如下显著的好处:1、适合多个相同程序代码的线程去处理同一个资源的情况,

5、把线程同程序代码、数据有效的分离,很好的体现了面向对象的设计思想。2、可以避免由于Java的单继承带来的局限性。在开发中经常碰到这样一种情况,就是使用一个已经继承了某一个类的子类创建线程,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么就只能采用实现Runnable接口的方式。事实上,大部分的多线程应用都会采用第二种方式,即实现Runnable接口。第二课时(线程的生命周期、状态转换) 线程的生命周期线程整个生命周期可以分为五个阶段,具体如下: 新建状态(New)创建一个线程对象后,该线程对象就处于新建状态,此时它不能运行,和其它Java对象一样,仅仅由Java虚拟机为其

6、分配了内存,没有表现出任何线程的动态特征。 就绪状态(Runnable)当线程对象调用了start()方法后,该线程就进入就绪状态(也称可运行状态)。处于就绪状态的线程位于可运行池中,此时它只是具备了运行的条件,能否获得CPU的使用权开始运行,还需要等待系统的调度。 运行状态(Running)如果处于就绪状态的线程获得了CPU的使用权,开始执行run()方法中的线程执行体,则该线程处于运行状态。当一个线程启动后,它不可能一直处于运行状态(除非它的线程执行体足够短,瞬间就结束了),当使用完系统分配的时间后,系统就会剥夺该线程占用的CPU资源,让其它线程获得执行的机会。需要注意的是,只有处于就绪状

7、态的线程才可能转换到运行状态。 阻塞状态(Blocked)一个正在执行的线程在某些特殊情况下,如执行耗时的输入/输出操作时,会放弃CPU的使用权,进入阻塞状态。线程进入阻塞状态后,就不能进入排队队列。只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。 死亡状态(Terminated)线程的run()方法正常执行完毕或者线程抛出一个未捕获的异常(Exception)、错误(Error),线程就进入死亡状态。一旦进入死亡状态,线程将不再拥有运行的资格,也不能再转换到其它状态。 线程的状态转换线程的不同状态表明了线程当前正在进行的活动。在程序中,通过一些操作,可以使线程在不同状态之间转换,如下图

8、所示。 线程由运行状态转成阻塞状态和从阻塞状态转成就绪状态的情形如下: 当线程试图获取某个对象的同步锁时,如果该锁被其它线程所持有, 则当前线程会进入阻塞状态,如果想从阻塞状态进入就绪状态必须得获取到其它线程所持有的锁。 当线程调用了一个阻塞式的IO方法时,该线程就会进入阻塞状态,如 果想进入就绪状态就必须要等到这个阻塞的IO方法返回。 当线程调用了某个对象的wait()方法时,也会使线程进入阻塞状态,如果想进入就绪状态就需要使用notify()方法唤醒该线程。 当线程调用了Thread的sleep(long millis)方法时,也会使线程进入阻塞状态,在这种情况下,只需等到线程睡眠的时间到

9、了以后,线程就会自动进入就绪状态。 当在一个线程中调用了另一个线程的join()方法时,会使当前线程进入阻塞状态,在这种情况下,需要等到新加入的线程运行结束后才会结束阻塞状态,进入就绪状态。第三课时(线程的调度) 线程的调度 概念程序中的多个线程是并发执行的,某个线程若想被执行必须要得到CPU的使用权。Java虚拟机会按照特定的机制为程序中的每个线程分配CPU的使用权,这种机制被称作线程的调度。 线程调度的模型线程调度有两种模型,分别是分时调度模型和抢占式调度模型: 分时调度模型所谓分时调度模型是指让所有的线程轮流获得CPU的使用权,并且平均分配每个线程占用的CPU的时间片。 抢占式调度模型抢

10、占式调度模型是指让可运行池中优先级高的线程优先占用CPU,而对于优先级相同的线程,随机选择一个线程使其占用CPU,当它失去了CPU的使用权后,再随机选择其它线程获取CPU使用权。 线程的优先级在应用程序中,如果要对线程进行调度,最直接的方式就是设置线程的优先级。优先级越高的线程获得CPU执行的机会越大,而优先级越低的线程获得CPU执行的机会越小。线程的优先级用110之间的整数来表示,数字越大优先级越高。除此职位,还可以使用Thread类中提供的三个静态常量表示线程的优先级,具体如下: 线程休眠如果希望人为地控制线程,使正在执行的线程暂停,将CPU让给别的线程,这时可以使用静态方法sleep(l

11、ong millis),该方法可以让当前正在执行的线程暂停一段时间,进入休眠等待状态。当前线程调用sleep(long millis)方法后,在指定时间(参数millis)内该线程是不会执行的,这样其它的线程就可以得到执行的机会了。需要注意的是,sleep()是静态方法,只能控制当前正在运行的线程休眠,而不能控制其它线程休眠。当休眠时间结束后,线程就会返回到就绪状态,而不是立即开始运行。 线程让步线程让步可以通过yield()方法来实现,该方法和sleep()方法有点相似,都可以让当前正在运行的线程暂停,区别在于yield()方法不会阻塞该线程,它只是将线程转换成就绪状态,让系统的调度器重新调

12、度一次。当某个线程调用yield()方法之后,只有与当前线程优先级相同或者更高的线程才能获得执行的机会。 线程插队在Thread类中提供了一个join()方法来实现这个“功能”。当在某个线程中调用其它线程的join()方法时,调用的线程将被阻塞,直到被join()方法加入的线程执行完成后它才会继续运行。第四课时(多线程同步、多线程通信) 多线程同步 线程安全问题模拟窗口售票程序,假如共有10张票出售,并在售票的代码中每次售票时线程休眠10毫秒,如下所示。public class Example11 public static void main(String args) / 创建Ticket1

13、 对象 SaleThread saleThread = new SaleThread(); / 创建并开启四个线程 new Thread(saleThread, 线程一).start(); new Thread(saleThread, 线程二).start(); new Thread(saleThread, 线程三).start(); new Thread(saleThread, 线程四).start(); / 定义Ticket1类实现Runnable接口class SaleThread implements Runnable private int tickets = 10; / 10张票

14、public void run() while (tickets 0) try Thread.sleep(10); / 经过此处的线程休眠10毫秒 catch (InterruptedException e) e.printStackTrace(); System.out.println(Thread.currentThread().getName() + -卖出的票 + tickets-); 运行结果如下图所示。从上图可以看出,售出的票出现了0、-1、-2这样的票号,这种现象是不应该出现的,因此,上面的多线程程序存在安全问题。 同步代码块线程安全问题其实就是由多个线程同时处理共享资源所导致的

15、。要想解决上述线程安全问题,必须保证用于处理共享资源的代码在任何时刻只能有一个线程访问。为了实现这种限制,Java中提供了同步机制。当多个线程使用同一个共享资源时,可以将处理共享资源的代码放置在一个代码块中,使用synchronized关键字来修饰,被称作同步代码块,其语法格式如下: synchronized(lock)操作共享资源代码块上面的代码中,lock是一个锁对象,它是同步代码块的关键。当线程执行同步代码块时,首先会检查锁对象的标志位,默认情况下标志位为1,此时线程会执行同步代码块,同时将锁对象的标志位置为0。当一个新的线程执行到这段同步代码块时,由于锁对象的标志位为0,新线程会发生阻

16、塞,等待当前线程执行完同步代码块后,锁对象的标志位被置为1,新线程才能进入同步代码块执行其中的代码。循环往复,直到共享资源被处理完为止。接下来将例程中售票的代码放到synchronized区域中,示例代码如下:/定义Ticket1类继承Runnable接口class Ticket1 implements Runnable private int tickets = 10; / 定义变量tickets,并赋值10 / 定义任意一个对象,用作同步代码块的锁 Object lock = new Object(); public void run() while (true) synchronized

17、 (lock) / 定义同步代码块 try Thread.sleep(10); / 经过的线程休眠10毫秒 catch (InterruptedException e) e.printStackTrace(); if (tickets 0) System.out.println(Thread.currentThread().getName() + -卖出的票 + tickets-); else / 如果 tickets小于0,跳出循环 break; public class Example12 public static void main(String args) Ticket1 ticke

18、t = new Ticket1(); / 创建Ticket1对象 / 创建并开启四个线程 new Thread(ticket, 线程一).start(); new Thread(ticket, 线程二).start(); new Thread(ticket, 线程三).start(); new Thread(ticket, 线程四).start(); 运行结果如下图所示。 同步方法了解到同步代码块可以有效解决线程的安全问题,当把共享资源的操作放在synchronized定义的区域内时,便为这些操作加了同步锁。在方法前面同样可以使用synchronized关键字来修饰,被修饰的方法为同步方法,它

19、能实现和同步代码块同样的功能,具体语法格式如下:synchronized 返回值类型 方法名(参数1,)被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其它线程都会发生阻塞,直到当前线程访问完毕后,其它线程才有机会执行方法。使用同步方法对案例进行修改,如下所示。/ 定义Ticket1类实现Runnable接口class Ticket1 implements Runnable private int tickets = 10; public void run() while (true) saleTicket(); / 调用售票方法 if (tickets 0) t

20、ry Thread.sleep(10); / 经过的线程休眠10毫秒 catch (InterruptedException e) e.printStackTrace(); System.out.println(Thread.currentThread().getName() + -卖出的票 + tickets-); public class Example13 public static void main(String args) Ticket1 ticket = new Ticket1(); / 创建Ticket1对象 / 创建并开启四个线程 new Thread(ticket,线程一)

21、.start(); new Thread(ticket,线程二).start(); new Thread(ticket,线程三).start(); new Thread(ticket,线程四).start(); 运行结果如图所示。 死锁问题在多线程中,当两个线程在运行时都在等待对方的锁,这样便造成了程序的停滞,这种现象称为死锁。 多线程通信 问题引出模拟这样的一种应用场景,假设有两个线程同时去操作同一个存储空间,其中一个线程负责向存储空间中存入数据,另一个线程负责则取出数据。为了实现上述场景,首先定义一个类,在类中使用一个数组来表示存储空间,并提供数据的存取方法,代码实现如下所示:class

22、Storage / 数据存储数组 private int cells = new int10; / inPos表示存入时数组下标,outPos表示取出时数组下标 private int inPos, outPos; / 定义一个put()方法向数组中存入数据 public void put(int num) cellsinPos = num;System.out.println(在cells + inPos + 中放入数据- + cellsinPos); inPos+;/ 存完元素让位置加1 if (inPos = cells.length) inPos = 0; / 当inPos为数组长度时

23、,将其置为0 / 定义一个get()方法从数组中取出数据 public void get() int data = cellsoutPos; System.out.println(从celss + outPos + 中取出数据 + data); outPos+; / 取完元素让位置加1 if (outPos = cells.length) outPos = 0; 接下来实现两个线程同时访问上例中的共享数据,这两个线程都需要实现Runnable接口,具体如下所示。 Input.java和Output.javaclass Input implements Runnable/ 输入线程类 priva

24、te Storage st ; private int num; / 定义一个变量num Input(Storage st) / 通过构造方法接收一个Storage对象 this.st = st; public void run() while(true) st.put(num+); / 将num存入数组,每次存入后num自增 class Output implements Runnable /输出线程类 private Storage st ; Output(Storage st) / 通过构造方法接收一个Storage对象 this.st = st; public void run() w

25、hile(true) st.get(); / 循环取出元素 编写测试程序,用于开启两个线程,具体如下所示:public class Example17 public static void main(String args) / 创建数据存储类对象 Storage st = new Storage(); / 创建Input对象传入Storage对象 Input input = new Input(st); / 创建Output对象传入Storage对象 Output output = new Output(st); / 开启新线程 new Thread(input).start(); / 开启

26、新线程 new Thread(output).start(); 运行结果如图所示。从运行结果可以看到,在取出数字12后,紧接着取出的是23,之所以出现这种现象是因为在Input线程存入数字13时,Output线程并没有及时取出数据,Input线程一直在持续地存入数据,直到将数组放满,又从数组的第一位置开始存入21、22、23,当Output线程再次取数据时,取出的不再是13而是23。 问题如何解决如果想解决上述问题,就需要控制多个线程按照一定的顺序轮流执行,此时需要让线程间进行通信。在Object类中提供了wait()、notify()、notifyAll()方法用于解决线程间的通信问题。接下

27、来通过使用wait()和notify()方法,对例程进行改写来实现线程间的通信。class Storage private int cells = new int10; / 数据存储数组 / inPos存入时数组下标,outPos取出时数组下标 private int inPos, outPos; private int count; / 存入或者取出数据的数量 public synchronized void put(int num) try / 如果放入数据等于cells的长度,此线程等待 while (count = cells.length) this.wait(); cellsinP

28、os = num; / 向数组中放入数据 System.out.println(在cells + inPos + 中放入数据- + cellsinPos); inPos+;/ 存完元素让位置加1 / 当在cells9放完数据后再从cells0开始 if (inPos = cells.length) inPos = 0; count+; / 每放一个数据count加1 this.notify(); catch (Exception e) e.printStackTrace(); public synchronized void get() try while (count = 0) / 如果 count为0,此线程等待 this.wait(); int data = cellsoutPos; / 从数组中取出数据 System.out.println(从cells + outPos + 中取出数据 + data); cellsoutPos = 0; / 取出后,当前位置的数据置0 outPos+; / 取完元素让位置加1 / 当从cells9取完数据后再从cells

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2