1、第十章 多线程教案名称: 教案大小:教案类型: WORD 文档 星级评定: 教案简介: 本讲介绍了 Java 线程的一些基本知识和简单应用,通过对线程简介,阐明了线程与进程的区别,通过描述线程的概念模型的基本原理以及线程体的构造方法和应用实例,讲解了线程的基本特性和线程的不同状态的转换关系和调用方法,明确了线程的使用方法,然后,我们又进一步讲述了线程的几种调度策略,在不同的调度策略下优先级的作用。以及如何进行基本的线程的控制,线程的重点和难点在于多线程的互斥与同步,首先我们必须明白互斥锁的概念和作用,如何使用互斥锁来控制和处理多线程的同步问题。下载一【课前思考】1. 什么是线程?它和进程有什么
2、区别?适用方向是什么?2. Java 的线程是如何实现的?3. Java 的线程是如何调度的?4. Java 中的多线程有什么特点?同步和互斥的原理是如何实现的?【学习目标】学习 java 中线程的使用,掌握线程的调度和控制方法,清楚地理解多线程的互斥和同步的实现原理,以及多线程的应用。【学习指南】掌握线程之间的相互调度关系,尤其是通过线程睡眠来使其它线程获得执行机会的机制,以及互斥和同步的实现机制。【难 重 点】 1. 多线程的调度和控制。2. 多线程的互斥和同步。【知 识 点】10.1 线程简介10.1.1 线程的概念模型10.1.2 线程体10.1.3 线程的调度10.1.4 基本的线程
3、控制10.2 多线程的互斥与同步10.2.1 互斥锁10.2.2 多线程的同步第十章 多线程101 线程简介随着计算机的飞速发展,个人计算机上的操作系统也纷纷采用多任务和分时设计,将早期只有大型计算机才具有的系统特性带到了个人计算机系统中。一般可以在同一时间内执行多个程序的操作系统都有进程的概念。一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间、一组系统资源。在进程概念中,每一个进程的内部数据和状态都是完全独立的。Java 程序通过流控制来执行程序流,程序中单个顺序的流控制称为线程,多线程则指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务。多线程意味着一个程序的
4、多行语句可以看上去几乎在同一时间内同时运行。线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数据通常只有微处理器的寄存器数据,以及一个供程序执行时使用的堆栈。所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小的多,正因如此,线程被称为轻负荷进程(light-weight process)。一个进程中可以包含多个线程。一个线程是一个程序内部的顺序控制流。1. 进程:每个进程都有独立的代码和数据空间(进程上下文) ,进程切换的开销大。 2. 线程:轻量的进程,同一类线程共享代码和数据
5、空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。 3. 多进程:在操作系统中,能同时运行多个任务程序。 4. 多线程:在同一应用程序中,有多个顺序流同时执行。1011 线程的概念模型Java 内在支持多线程,它的所有类都是在多线程下定义的,Java 利用多线程使整个系统成为异步系统。Java 中的线程由三部分组成,如图 6.1 所示。1. 虚拟的 CPU,封装在 java.lang.Thread 类中。2. CPU 所执行的代码,传递给 Thread 类。3. CPU 所处理的数据,传递给 Thread 类。图 10.1 线程1012 线程体Java 的线程是通过 java
6、.lang.Thread 类来实现的。当我们生成一个 Thread 类的对象之后,一个新的线程就产生了。此线程实例表示 Java 解释器中的真正的线程,通过它可以启动线程、终止线程、线程挂起等,每个线程都是通过类 Thread 在 Java 的软件包 Java.lang 中定义,它的构造方法为:public Thread (ThreadGroup group,Runnable target,String name);其中,group 指明该线程所属的线程组;target 实际执行线程体的目标对象,它必须实现接口 Runnable; name 为线程名。Java 中的每个线程都有自己的名称,Ja
7、va 提供了不同 Thread 类构造器,允许给线程指定名称。如果 name 为 null 时,则 Java 自动提供唯一的名称。当上述构造方法的某个参数为 null 时,我们可得到下面的几个构造方法:public Thread ();public Thread (Runnable target);public Thread (Runnable target,String name);public Thread (String name);public Thread (ThreadGroup group,Runnable target);public Thread (ThreadGroup g
8、roup,String name);一个类声明实现 Runnable 接口就可以充当线程体,在接口 Runnable 中只定义了一个方法 run(): public void run();任何实现接口 Runnable 的对象都可以作为一个线程的目标对象,类 Thread 本身也实现了接口 Runnable,因此我们可以通过两种方法实现线程体。(一)定义一个线程类,它继承线程类 Thread 并重写其中的方法 run(),这时在初始化这个类的实例时,目标 target 可为 null,表示由这个实例对来执行线程体。由于 Java 只支持单重继承,用这种方法定义的类不能再继承其它父类。(二)提供
9、一个实现接口 Runnable 的类作为一个线程的目标对象,在初始化一个 Thread 类或者 Thread 子类的线程对象时,把目标对象传递给这个线程实例,由该目标对象提供线程体 run()。这时,实现接口 Runnable 的类仍然可以继承其它父类。每个线程都是通过某个特定 Thread 对象的方法 run( )来完成其操作的,方法 run( )称为线程体。图 6.2 表示了 java 线程的不同状态以及状态之间转换所调用的方法。图 10.2 线程的状态1. 创建状态(new Thread)执行下列语句时,线程就处于创建状态:Thread myThread = new MyThreadCl
10、ass( );当一个线程处于创建状态时,它仅仅是一个空的线程对象,系统不为它分配资源。2. 可运行状态( Runnable )Thread myThread = new MyThreadClass( );myThread.start( );当一个线程处于可运行状态时,系统为这个线程分配了它需的系统资源,安排其运行并调用线程运行方法,这样就使得该线程处于可运行( Runnable )状态。需要注意的是这一状态并不是运行中状态(Running ),因为线程也许实际上并未真正运行。由于很多计算机都是单处理器的,所以要在同一时刻运行所有的处于可运行状态的线程是不可能的,Java 的运行系统必须实现调度
11、来保证这些线程共享处理器。3. 不可运行状态(Not Runnable)进入不可运行状态的原因有如下几条:1) 调用了 sleep()方法;2) 调用了 suspend()方法;3) 为等候一个条件变量,线程调用 wait()方法;4) 输入输出流中发生线程阻塞;不可运行状态也称为阻塞状态(Blocked)。因为某种原因(输入/输出、等待消息或其它阻塞情况),系统不能执行线程的状态。这时即使处理器空闲,也不能执行该线程。4. 死亡状态(Dead)线程的终止一般可通过两种方法实现:自然撤消(线程执行完)或是被停止(调用 stop()方法)。目前不推荐通过调用 stop()来终止线程的执行,而是让
12、线程执行完。线程体的构造任何实现接口 Runnable 的对象都可以作为一个线程的目标对象,上面已讲过构造线程体有两种方法,下面通过实例来说明如何构造线程体的。例 101 通过继承类 Thread 构造线程体class SimpleThread extends Thread public SimpleThread(String str) super(str); /调用其父类的构造方法public void run() /重写 run 方法for (int i = 0; i 10; i+) System.out.println(i + “ “ + getName(); /打印次数和线程的名字tr
13、y sleep(int)(Math.random() * 1000); /线程睡眠,把控制权交出去 catch (InterruptedException e) System.out.println(“DONE! “ + getName(); /线程执行结束public class TwoThreadsTest public static void main (String args) new SimpleThread(“First“).start(); /第一个线程的名字为 Firstnew SimpleThread(“Second“).start();/第二个线程的名字为 Second 运
14、行结果:0 First0 Second1 Second1 First2 First2 Second3 Second3 First4 First4 Second5 First5 Second6 Second6 First7 First7 Second8 Second9 Second8 FirstDONE! Second9 FirstDONE! First仔细分析一下运行结果,会发现两个线程是交错运行的,感觉就象是两个线程在同时运行。但是实际上一台计算机通常就只有一个 CPU,在某个时刻只能是只有一个线程在运行,而 java 语言在设计时就充分考虑到线程的并发调度执行。对于程序员来说,在编程时要注
15、意给每个线程执行的时间和机会,主要是通过让线程睡眠的办法(调用 sleep()方法)来让当前线程暂停执行,然后由其它线程来争夺执行的机会。如果上面的程序中没有用到 sleep()方法,则就是第一个线程先执行完毕,然后第二个线程再执行完毕。所以用活 sleep()方法是学习线程的一个关键。例 102 通过接口构造线程体public class Clock extends java.applet.Applet implements Runnable /实现接口Thread clockThread; public void start() /该方法是 Applet 的方法,不是线程的方法if (cl
16、ockThread = null) clockThread = new Thread(this, “Clock“);/*线程体是 Clock 对象本身,线程名字为“Clock“*/clockThread.start(); /启动线程 public void run() /run()方法中是线程执行的内容while (clockThread != null) repaint(); /刷新显示画面try clockThread.sleep(1000); /睡眠 1 秒,即每隔 1 秒执行一次 catch (InterruptedException e) public void paint(Grap
17、hics g) Date now = new Date(); /获得当前的时间对象g.drawString(now.getHours() + “:“ + now.getMinutes()+ “:“ +now.getSeconds(), 5, 10);/显示当前时间public void stop() /该方法是 Applet 的方法,不是线程的方法clockThread.stop(); clockThread = null; 上面这个例子是通过每隔 1 秒种就执行线程的刷新画面功能,显示当前的时间;看起来的效果就是一个时钟,每隔 1 秒就变化一次。由于采用的是实现接口 Runnable 的方式
18、,所以该类Clock 还继承了 Applet, Clock 就可以 Applet 的方式运行。构造线程体的两种方法的比较:1. 使用 Runnable 接口1) 可以将 CPU,代码和数据分开,形成清晰的模型;2) 还可以从其他类继承;3) 保持程序风格的一致性。2. 直接继承 Thread 类1) 不能再从其他类继承;2) 编写简单,可以直接操纵线程,无需使用 Thread.currentThread()。1013 线程的调度Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪些线程来执行。线程调度器按线程的优先级高低选择高优先级线程(
19、进入运行中状态)执行,同时线程调度是抢先式调度,即如果在当前线程执行过程中,一个更高优先级的线程进入可运行状态,则这个线程立即被调度执行。线程的优先级线程的优先级用数字来表示,范围从 1 到 10,即 Thread.MIN_PRIORITY 到Thread.MAX_PRIORITY。一个线程的缺省优先级是 5,即 Thread.NORM_PRIORITY。下述方法可以对优先级进行操作:int getPriority(); /得到线程的优先级void setPriority(int newPriority); /当线程被创建后,可通过此方法改变线程的优先级例 103 中生成三个不同线程,其中一个
20、线程在最低优先级下运行,而另两个线程在最高优先级下运行。例 103class ThreadTestpublic static void main( String args ) Thread t1 = new MyThread(“T1“);t1.setPriority( Thread.MIN_PRIORITY ); /设置优先级为最小t1.start( );Thread t2 = new MyThread(“T2“);t2.setPriority( Thread.MAX_PRIORITY ); /设置优先级为最大t2.start( );Thread t3 = new MyThread(“T3“);t3.setPriority( Thread.MAX_PRIORITY ); /设置优先级为最大t3.start( );class MyThread extends Thread String message;MyThread ( String message ) this.message = message;