1、|线程与多线程|创建线程|线程的启动|线程的调度|线程的基本控制|多线程同步机制9.1 线程与多线程1线程的概念 线程是存在于程序中的一个单独的顺序执行流程。它类似于一个顺序执行的程序,即一个单独的线程也只有一个起始点、一个执行序列和一个结尾,在线程运行的某一特定时刻也只有一个执行点。但是,一个线程只是一个程序的一部分,它本身并不能构成一个完整的程序,换言之,程序可以独立运行,也可以拥有多个相互独立的线程,而线程则不然,它不能独立运行,也能不独立存在,而必须“寄生”于一个程序之中。 只包含一个线程的程序就是我们所熟悉的顺序执行程序,这时线程这一概念并未给我们带来什么新意。而Java使用线程的神
2、奇之处在于它使得一个程序可以使用多个线程,这些线程同时运行,而每个线程则完成不同的功能。2线程的结构 线程包含三个主要部分,第一是虚拟CPU本身,二是CPU执行的代码即Code,第三是代码操作的数据即Data。在Java中虚拟CPU体现于Thread类中。当一个线程被构造时;它由构造方法参数、执行代码、操作数据来初始化。应该特别注意的是,这三方面是各自独立的。一个线程所执行的代码与其它线程可以相同也可以不同,一个线程访问的数据与其它线程可以相同也可以不同。 二创建线程二创建线程1创建线程的方法之一继承Thread类 java.1ang.Thread是Java中用来表示进程的类,其中所定义的许多
3、方法为完成线程的处理工作提供了比较完整的功能。如果将一个类定义为Thread的子类,那么这个类也就可以用来表示线程。2创建线程的方法之二实现Runnable接口 Runnable是Java中用以实现线程的接口,从根本上讲,任何实现线程功能的类都必须实现该接口。前面所用到的Thread类实际上就是因为实现了Runnable接口,所以它的子类才相应具有线程功能的。Runnable接口中只定义了一个方法就是run()方法,也就是线程体。 Thread第二种构造方法中包含有一个Runnable实例的参数,这就是说,必须定义一个实现Runnable接口的类并产生一个该类的实例,对该实例的引用就是适合于这
4、个构造方法的参数。3关于两种创建线程方法的讨论适用于采用实现Runnable接口方法的情况 因为Java只允许单继承,如果一个类已经继承了Thread,就不能再继承其它类。在一些情况下,这就被迫采用实现Runnable接口的方法。比如对于Applet程序,由于必须继承java.applet.Applet,因此就只能采取这种实现接口的方法。再有,由于上面的原因而几次被迫采用实现Runnable接口的方法,可能会出于保持程序风格的一贯性而继续使用这种方法。适用于采用继承Thread方法的情况 当一个run()方法置于Thread类的子类中时,this实际上引用的是控制当前运行系统的Thread实例
5、,所以,代码不必写得像下面这样繁琐:Thread.currentThread().suspend();而可简单地写为:suspend(); 因为代码稍微简洁一些,所以许多Java程序员愿意使用继承Thread的方法。但是应该知道,如果采取这种简单的继承模式,在以后的继承中可能会出现麻烦。9.3 线程的启动线程的启动虽然一个线程已经被创建,但它实际上并没有立刻运行。要使线程真正在Java环境中运行,必须通过方法start()来启动,start()方法也在Thread类中。例如只要执行:Thread1.start();此时,线程中的虚拟CPU已经就绪,所以也可以把这一过程想象为打开虚拟CPU的开关
6、。9.4 线程的调度线程的调度 虽然就绪线程已经可以运行,但它并不意味着这个线程一定能够立刻运行。显然,在一台实际上只具有一个CPU的机器上,CPU在同一时间只能分配给一个线程做一件事。那么现在就必须考虑,当有多于一个的线程工作时,CPU是如何分配的。 在Java中,线程调度通常是抢占式,而不是时间片式。抢占式调度是指可能有多个线程准备运行,但只有一个在真正运行。一个线程获得执行权,这个线程将持续运行下去,直到它运行结束或因为某种原因而阻塞,再或者有另一个高优先级线程就绪。最后一种情况称为低优先级线程被高优先级线程所抢占。9.5 线程的基本控制线程的基本控制1结束线程 结束一个线程有两种情况,
7、第一种情况是当一个线程从run( )方法的结尾处返回时它自动消亡并不能再被运行,可以将其理解为自然死亡;另一种情况是利用stop( )方法强制停止,可以将其理解为强迫死亡,这种方法必须用于Thread类的特定实例中。2检查线程 有时候可能不知道一个线程的运行状态(当程序代码没有直接控制该线程时,会发生此种情况),这时可以利用方法isAlive( )来获取一个线程是否还在活动状态。活动状态不意味着这个线程正在执行,而只说明这个线程已被启动,并且既没有运行stop( ),也尚未运行完方法run( )。3挂起线程有几种方法可以用来暂停一个线程的运行。在挂起之后,必须重新唤醒线程进入运行,这从外表看来
8、好像什么也没发生,只是线程执行命令的速度非常慢。挂起线程的方法有以下几种:sleep( ) 方法sleep( )用于暂时停止一个线程的执行。通常,线程不是休眠期满后就立刻被唤醒,因为此时其它线程可能正在执行,重新调度只在以下几种情况下才会发生: 被唤醒的线程具有更高的优先级。 正在执行的线程因为其它原因被阻塞。 程序处于支持时间片的系统中。大多数情况下,后两种条件不会立刻发生。suspend( )和resume( ) 有时更好的办法是强制挂起线程,而不指定休眠时间,这种情况下由其它线程负责唤醒其继续执行。线程中有一对方法用于完成此功能,这就是suspend( )和resume( ) join(
9、 ) 方法join( )将引起现行线程等待,直至方法join( )所调用的线程结束。9.6 多线程同步机制多线程同步机制1wait( )等待和notify( )通知方法在Object类中wait( )和notify( )的定义为:public class java.lan g.Objectpublic Object(); /其他public final void notify(); /通知方法public final void notifyAll(); /通知所有的等待public String toString(); public final void wait(); /等待方法public
10、 final void wait(long timeout);public final void wait(long timeout,int nanos); 说明:wait( )和notify( )方法都属于基础类Object的一部分,不像sleep( )、suspend( )以及resume( )那样属于Thread类的一部分。Wait( )和notify( )方法是与对象的锁相关联的,即这两个方法操纵对象的锁。可以将一个wait( )方法置入任何同步方法内部,无论在类中是否准备进行涉及线程的处理。事实上,能调用wait( )方法的惟一地方是在一个同步的方法或代码块内部。若在一个不同步的方法
11、内调用wait( )或者notify( )方法,Java语言程序会通过编译,但是在运行程序时,就会出现一个“非法监视器状态”异常(IllegalMonitorStateException),而且输出一条错误消息“current thread not owner”。在线程类中,许多方法例如sleep( )、suspend( )以及resume( )等都可以在不同步的的方法内部调用,因为它们不需要对对象进行锁定操作。2线程监视器Jave语言的临视器实际上是完成线程调度工作的 , 它 是 将 所 有 的 等 待 进 入 临 视 器 执 行synchronized方法的线程进行排队。如果一个线程要调用
12、某个对象的一个synchronized方法,而此 时 另 一 个 线 程 已 经 在 执 行 该 对 象 的synchronized方法了,那么该线程将进入队列,等待执行。如果一个线程在对象中进行操作时调用了wait( )方法,那么该线程也要进入队列。然而,把由于临视器忙而阻塞的等待线程与由于在临视器中显式地调用wait( )方法而等待的线程区分开是很重要的。当一个synchronized方法执行完后,由于临视器忙而阻塞的外部线程就可以执行了。而那些显式地调用了wait( )方法的线程则只能在其他线程用ontify( )或notifyAll( )方法将其唤醒时才能继续执行。当一个进入队列的线程
13、具有了继续执行的资格时,调节器度程序就选择优先级最高的线程来执行。3一个线程的生命周期一个线程的生命周期分为生成、运行、等待、终止四个阶段。一个线程可以处于以下五种状态: 创建新线程:线程对象已经创建,但尚未启动,所以不可运行。 可运行线程:它意味着一旦时间分片机制有空闲的CPU周期提供给一个线程,则该线程便可立即开始运行。因此,线程可能在、也可能不在运行当中,但一旦条件许可,没有什么能阻止它的运行,除非该线程已经终止或被“堵塞”。 终止线程:线程从自己的run( )方法中返回后,一个线程便终止了。也可以调用stop( )方法令其终止。 线程堵塞:线程可以运行,但有某种东西阻碍了它。若线程处于
14、堵塞状态调度机制可以简单地跳过它,不给它分配任何CPU时间。除非线程再次进入“可运行”状态否则不会采取任何操作。 线程死锁:线程之间相互等待获得对方的锁。4线程堵塞造成线程被堵塞可能是由以下几个方面的原因所造成的。调用sleep( )方法,使线程进入“睡眠”状态。在规定的时间内,这个线程是不会运行的用suspend( )方法暂停了线程的执行。除非线程收到resume( )方法的消息,否则不会返回“可运行”状态。用wait( )方法暂停了线程的执行。除非线程收到nofify( )或者notifyAll( )方法的消息,否则不会变成“可运行”。线程正在等候一些I/O(输入输出)操作的完成,即若一个
15、数据流需要等候一些I/O活动时,它便会自动进入“堵塞”状态。线程试图调用另一个对象的“同步”方法,但那个对象正处于锁定状态,暂时 无 法 使 用 。 即 在 调 用 对 象 的synchronized方法时,该对象的另一个synchronized方法正执行。可调用yield( )方法自动放弃CPU,以便其他线程能够运行。5线程死锁如果Java语言程序使用了大量的线程,死锁很容易就会发生。但是Java语言并没有提供一种机制以自动检测和解决死锁,如果采取以下防备措施的话,便可以大大降低这种条件发生的可能性: 避免从另一个synchronized方法或代码块中调用一个synchronized方法。如
16、果需要嵌套synchronized方法的话,总是按照相同的顺序调用它们。 在设计类时,尽量减少对共享资源的访问。 在创建多线程程序的时候,当出现一个线程需要等待直到另一线程改变了一个 对象或变量的状态情况时,可以使用一种简单的线程协同(可以人为地协调)技术来处理这些情况。6线程的优先级线程在创建时,继承了父类的优先级。线程 创 建 后 , 你 可 以 在 任 何 时 刻 调setPriority方法改变线程的优先级。优先级为110,Thread定义了其中3个常数: (1) MAX_PRIORITY 最大优先级(值为10) (2) MIN_PRIORITY 最小优先级(值为1) (3) NORM
17、_PRIORITY 默认优先级(值为5)7线程同步Java提供了同步设定功能。共享对象可将自 己 的 成 员 方 法 定 义 为 同 步 化(synchronized)方法,通过调用同步化方法来执行单一线程,其他线程则不能同时调用同一个对象的同步化方法。8多线程的弊端对于程序员来说,必须加倍注意自己的多线程程序。由于多线程实际上是多个程序段同时运行于内存中,所以一定要理清它们的关系,不要让它们搅乱了你的头脑。如果真的出现这种情况,那么最好还是少用几个线程。其实,并不是线程越多程序就执行得越快,还有很多其他因素决定着程序的执行速度。对多线程程序本身来说,它会对系统产生以下影响:线程需要占用内存。线程过多,会消耗大量CPU时间来跟踪线程。必须考虑多线程同时访问共享资源的问题,如果没有协调好,就会产生令人意想不到的问题,例如可怕的死锁和资源竞争。因为同一个任务的所有线程都共享相同的地址空间,并共享任务的全局变量,所以程序也必须考虑多线程同时访问全局变量的问题。 |图象处理|动画效果|声音处理