1、第7章 多线程,Java语言程序设计,Java语言程序设计,第7章 多线程,本章位置,Java语言程序设计,第3章 流程控制语句,本章位置,掌握多线程的创建,1,掌握线程的优先级,2,能力点,第7章 多线程,Java语言程序设计,主要内容,1,多线程的基本概念,2,多线程的实现机制,3,线程的状态和线程的控制,4,线程的同步,5,案例分析,Java语言程序设计,第7章 多线程,6,任务训练,序列化程序的特点是只有一个入口、一个可执行的命令序列、一个出口。在程序执行的任何时刻,只有一个执行点。线程和序列化程序类似,也有一个入口、执行序列和出口,执行时也只有一个执行点。但是线程不是程序,它不能自己
2、独立运行,只有在程序中执行。线程是一个程序内部的顺序控制流,即程序中的一条执行路径。当同一个程序中有多条执行路径并发执行时,就称之为多线程(Multi-Thread)。换句话说,在多线程中允许一个程序创建多个并发执行的线程来完成各自的任务。,多线程的应用范围很广。在一般情况下,程序的一些部分同特定的事件或资源联系在一起,同时又不想为它而暂停程序其他部分的执行,这种情况下,就可以考虑创建一个线程,令它与那个事件或资源关联到一起,并让它独立于主程序运行。通过使用线程,可以避免用户在运行程序和得到结果之间的停顿,还可以让一些任务(如打印任务)在后台运行,而用户则在前台继续执行一些其他的工作。总之,利
3、用多线程技术,可以使编程人员方便地开发出能同时处理多个任务的功能强大的应用程序。,7.1多线程的基本概念,Java语言程序设计,第7章 多线程,7.2 多线程的实现机制,创建多线程有两种方法:继承Thread类和实现Runnable接口。在下面的小节里,将分别详细讲解。,Java语言程序设计,第7章 多线程,一个Thread类的一个实例对象就是Java程序的一个线程,所以Thread类的子类的实例对象也是Java程序的一个线程。因此构造Java程序可以通过构造类Thread的子类的实例对象来实现。构造类Thread的子类主要目的是为了让线程类的实例对象能够完成线程程序所需要的功能。 通过这种方
4、法构造出来的线程在程序执行时的代码被封装在类Thread或其子类的成员方法run中。为了使新构造出来的线程能完成所需要的功能。新构造出来的线程类应覆盖Thread类的成员方法run()。 线程的启动或运行并不是调用成员方法run()。而是调用成员方法start()达到间接调run()方法,线程的运行实际上就是执行线程的成员方法run()。 直接方式创建线程的步骤如下: (1)定义一个线程(Thread)子类; (2)在该线程子类中定义run()方法; (3)在run()方法中定义此线程的具体操作; (4)在其他类的方法中创建此线程的实例对象,并用start()方法启动线程。,7.2.1 继承T
5、hread类,7.2 多线程的实现机制,第7章 多线程,Java语言程序设计,【例10-1】通过继承Thread类创建线程,在主控程序中同时运行两个线程。输出奇数和偶数。 结果如下:,Java语言程序设计,第7章 多线程,说明:(1)本程序的运行结果会因机器性能不同而不同。 (2)要覆盖Thread类的成员方法run(),使线程完成相应的功能。 (3)在main()方法中创建两个线程,线程被创建后不会自动执行线程,而需要调用start()方法来启动这两个线程。 (4)main()方法本身也是一个线程,在main()方法中产生并启动两个线程后,输出活动线程个数为3。,7.2 多线程的实现机制,由
6、于Java不支持多继承性,如果用户需要类以线程方式运行且继承其他所需要的类,就必须实现Runnable接口。Runnable接口包含了与Thread类一致的基本方法。事实上,Runnable接口只有一个run()方法,所以实现这个接口的程序必须要定义run()方法的具体内容,用户新建线程的操作也由这个方法来决定。定义好接口类后,程序中如果需要使用线程时,只要以这个实现了run()方法的类为参数创建系统类Thread的对象,就可以把实现的run()方法继承过来。间接方式创建线程的步骤如下:(1)定义一个Runnable接口类;(2)在此接口中定义一个run()方法;(3)在run()方法中定义线
7、程的操作;(4)在其他类的方法中创建此Runnable接口类的实例对象,并以此实例对象作为参数创建线程类对象。(5)用start()方法启动线程。,7.2.2 实现Runnable接口,第7章 多线程,Java语言程序设计,7.2 多线程的实现机制,【例10-2】使用Runnable接口方法创建线程和启动线程。 结果如下:,Java语言程序设计,第7章 多线程,说明:Runnable接口只定义了一个方法run(),通过声明自己的类实现Runnable接口并提供这一方法,将我们的线程代码写入其中,就完成了这一部分的任务。但是Runnable接口并没有任何对线程的支持,还必须创建Thread类的实
8、例,这一点通过Thread类的构造函数public Thread(Runnable target)来实现。,7.2 多线程的实现机制,7.3线程的状态和线程的控制,Java语言程序设计,第7章 多线程,一个线程从创建、启动到终止的整个过程就叫做一个生命周期,在其间的任何时刻,线程总是处于某个特定的状态。这些状态有如下的五个,它们之间的转换如图10-1所示。 图10-1 线程基本状态转换图,7.3.1 线程的状态和生命周期,7.3线程的状态和线程的控制,Java语言程序设计,第7章 多线程,1、新建状态也称之为新线程状态,即创建了一个线程类的对象后,产生的新线程进入创建状态。实现语句如下:Thr
9、ead myThread=new myThreadClass();这是一个空的线程对象,run()方法还没有执行,若要执行它,系统还需对这个线程进行登记并为它分配系统资源,这些工作由start()方法来完成。2、就绪状态 该状态也叫可执行状态,当一个被创建的线程调用start()方法后便进入可执行状态。对应的程序语句为: myThread.start( );/产生所需系统资源,安排运行,并调用run()方法此时该线程处于准备占用处理机运行的状态。即它们已经被放到就绪队列中等待执行。至于该线程何时才被真正执行,则取决于线程的优先级和就绪队列的当前状况。只有操作系统调度到该线程时,才真正占用了处理
10、机并运行run()方法。所以这种状态并不是执行中的状态。,3、执行状态当处于可执行状态的线程被调度并获得了CPU等执行必需的资源时,便进入到该状态,即运行了run()方法。4、阻塞状态又叫不可执行状态,当下面的四种情况之一发生时,线程就会进入阻塞状态:调用了sleep()方法。调用了wait()方法,为的是等待一个条件变量。调用了suspend()方法。输入输出流中发生线程阻塞。例如: Thread myThread=new myThreadClass(); myThread.start( ); try myThread.sleep(20000); catch(InterruptedExcep
11、tion e) ,上面的例子当中调用了sleep()方法使myThread线程休眠了20秒,20秒后又可恢复运行。如果一个线程处于阻塞状态,那么这个线程暂时无法进入就绪队列。处于阻塞状态的线程通常需要由某些事件才能唤醒,至于由什么事件唤醒该线程,则取决于挂起的原因。针对上面四种情况,都有特定的唤醒方法与之对应,对应方法如下:若调用了sleep()方法后,线程处于睡眠状态,该方法的参数为睡眠时间,当这个时间过去后,线程进入可执行状态。若线程在等待一个条件变量,那么要想停止等待的话,需要该条件变量所在的对象调用notify或notifyAll()方法。若线程调用了suspend()方法,须有其他线
12、程调用resume()方法来恢复该线程的执行。若I/O流中发生线程阻塞,则规定的I/O指令将结束这种不可执行状态。5、死亡状态死亡状态又称作终止状态或停止状态。处于这种状态的线程已经不能够再继续执行。其中的原因可能是线程已经执行完毕,正常的撤销;也可能是被强制终止,例如通过执行stop()或destroy()方法来终止线程。,7.3.1 线程的状态和生命周期,Java语言程序设计,第7章 多线程,7.3线程的状态和线程的控制,1、终止线程 当一个线程终止后,其生命周期就结束了,便进入死亡状态。终止线程的执行可以用stop()方法,需要注意的是此时并没有消灭这个线程,只是停止了线程的执行,并且这
13、个线程不能用start()方法重新启动。一般情况下不用stop()方法终止一个线程,只是简单的让它执行完而已。很多复杂的线程程序将需要控制每一个线程,在这种情况下会用到stop()方法。2、测试线程状态 一个已经停止运行的线程是不能用start()方法重新启动的。因此,为了避免出错可以测试一个线程是否处于被激活的状态,方法为isAlive()。一个线程已经启动而且没有停止就被认为是激活的。如果线程t是激活的,t.isAlive()将返回true,但该线程是可运行的或是不可运行的,不能做进一步的解释分析;如果返回false,则该线程是新创建或已被终止的。,7.3.2 线程的控制,Java语言程序
14、设计,第7章 多线程,3、线程的暂停和恢复(1)sleep()通过调用该方法可以指定线程睡眠一段时间。当线程睡眠到指定的时间后,不会立即进入执行状态,而只是参与调度执行。这是因为当前线程正在运行时,不会立刻放弃处理机,除非这时有更高优先级的线程参与调度或者是当前线程由于某种原因被阻塞,在时间片方式下也可以是当前的时间片用完。(2)suspend()和resume()调用suspend()方法使线程暂停,并不是永久的停止线程,这可以用resume()方法重新激活线程。(3)join()join()方法将使当前线程进入等待状态,直至join()方法所调用的线程结束。例如已经生成并运行了一个线程tt
15、,而在另一个线程中执行timeout()方法,其定义如下: public void timeout( ) /暂停该线程,等待其他线程(tt)结束 tt.join( ); /其他线程结束后,继续执行该线程 这样,在执行timeout()方法以后,现在的线程将被阻塞,直到tt运行结束。 也可以使用”join(long timeout)”限定等待时间,单位为毫秒。,7.3线程的状态和线程的控制,7.4 线程的同步,Java语言程序设计,第7章 多线程,在多线程的程序中,要求各个线程对共享资源的访问是互斥的。比如,铁路售票系统,有4个售票点发售某日某次列车的100张车票,票(ticket)是共享资源,
16、其中,一个售票点在发售某张票的时候哦,其余售票点便不能进行售票,必须等这个售票点发售完这张票,并释放对共享资源(ticket)的使用权后才能进行售票。如下面的代码: if(ticket0) System.out.println(Thread.currentThread( ).getName( )+”is sailing ticket”+tickets-);即当一个售票线程运行到if(ticket0)语句后,CPU必须等到if语句执行完毕才去执行其他售票线程的相应代码段。Java中对共享数据操作的并发控制是采用传统的封锁技术。在Java中为保证线程对共享资源操作的完整性,用synchronize
17、d关键字为共享资源加锁来解决,称为互斥锁。每个共享资源对象都有一个互斥锁标记,保证任一时刻只能有一个线程访问该对象。,7.4.1 共享受限资源,7.4 线程的同步,Java语言程序设计,第7章 多线程,synchronized关键字的语法格式有两种:synchronized(object)同步代码段 /object可以是任意一个对象将前面的售票代码修改一下,使之具有同步的效果: String str=new String(“ “); synchronized(str) if(ticket0) System.out.println(Thread.currentThread( ).getName(
18、 )+”is sailing ticket”+tickets-); 程序中用String str=new String(“ “)语句随便产生了一个对象,用于后面的同步代码段。(2)synchronized作为方法的修饰字,使该方法成为同步方法。当一个线程在使用实例对象的某个同步方法时,试图调用该实例对象任何同步方法的其他线程都必须等待,直至该线程退出同步方法。然后该实例对象的不同步方法仍然可以被调用。例如定义pop( )为同步方法: public synchronized void pop( ) . ,上一节介绍了线程之间在访问对象的临界区时,需要使用同步以实现线程的互斥。有时,多个线程之间往
19、往需要共同协作。比如,线程A往缓冲区中写数据,线程B从缓冲区中取数据,当缓冲区中没有数据时,线程B必须等待,当缓冲区满时,线程A必须等待。 Java通过wait()方法、notify()方法和notifyAll()方法实现线程间的协作。这些方法在对象中是用final方法实现的,所以所有的类都包含它们。这三个方法仅在synchronized方法中才能被调用。wait():告知被调用的线程进入睡眠,直到其他线程进入并且调用notify()方法。notify():恢复相同对象中第一个调用wait()方法的线程。notifyAll():恢复相同对象中所有调用wait()方法的线程。具有最高优先级的线程
20、将最先运行。,这些方法在Object中被声明,如下所示:final void wait( )throws InterrupedExceptionfinal void notify( )final void notifyAll( )下面的例子程序错误地实现了一个简单生产者/消费者的应用问题。它由四个类组成:Q类设法获得同步的序列;Producer类产生排队的线程对象;Consumer类消费序列的线程对象;Exam10_3类创建单个Q、Producer和Consumer类的小类。,7.4.2 线程间的协作,Java语言程序设计,第7章 多线程,7.4 线程的同步,【例10-3】生产者/消费者问题。
21、,Java语言程序设计,第7章 多线程,结果:尽管Q类中的put()方法和get()方法是同步的,但是没有东西阻止生产者超越消费者,也没有东西阻止消费者消费同样的序列两次。这样,就得到下面的错误输出(输出将随处理器速度和装载的任务而改变):,7.4 线程的同步,【例10-3】同步后的生产者/消费者问题。,Java语言程序设计,第7章 多线程,内部的get()方法、wait()方法被调用。这时执行挂起操作,直到Producer告知数据已经预备好。此时内部的get()方法被恢复执行。获得数据后,get()方法调用notify()方法,并告诉Producer可以向序列中输入更多数据。在put()方法
22、内,wait()方法挂起执行,直到Consumer取走了序列中的项目。当继续执行时,下一个数据项目会被放入序列,notify()方法被调用,从而通知Consumer应该移走相应数据。,下面是该序列的输出,它清楚地显示了同步行为。,7.4 线程的同步,在多线程竞争使用多资源的程序中,有可能出现死锁的情况。这种情况发生在一个线程等待另一个线程所持有的锁,而那个线程又在等待第一个线程持有的锁的时候。每个线程都不能继续运行,除非另一线程运行完同步程序块。而恰恰因为任何一个线程都不能继续运行,所以这些线程都无法运行完同步程序块。图10-2示意了上面问题的产生机制,其程序代码如下:,第7章 多线程,Jav
23、a语言程序设计,图10-2 死锁程序结构示意图,7.4 线程的同步,【例10-5】 死锁。程序运行结果如下:,Java语言程序设计,第7章 多线程,如果运行该程序,就会发现上述信息后发生死锁的情况。Java既不监测也不采取办法避免这种状态,因此,保证死锁状态不会发生就成了程序员的职责。一个避免死锁发生的较麻烦的办法是:如果有多个对象要被同步,就应对于获得这些锁的顺序做一个综合决定,并在整个程序中遵循这个顺序。当然,关于它的更详细的讨论已经超出了本书的范围,有兴趣的读者可以参考操作系统方面的书籍。,7.4 线程的同步,Java虚拟机允许一个应用程序可以拥有多个同时执行的线程,而众多的线程中,哪一
24、个线程先执行,哪一个线程后执行,取决于线程的优先级(Priority)。线程的优先级由整数值110来表示,优先级越高,越先执行;优先级越低,越晚执行;优先级相同时,则遵循队列的”先进先出”的原则。有几个与优先级相关的整数常量:MIN_PRIORITY:线程能具有的最小优先级(1)。MAX_PRIORITY:线程能具有的最大优先级(10)。NORM-PRIORITY:线程的常规优先级(5)。当线程创建时,优先级默认是由NORM-PRIORITY标识的整数。Thread类与优先级相关的方法有:setPriotity()和getPriotity()。 setPriotity()方法用来设置线程的优先
25、级,带一个整数型参数作为线程的优先级,其范围必须在MIN_PRIORITY和MAX_PRIORITY之间,并且不大于线程的Thread对象所属线程组的优先级。当一个在可执行状态队列中排队的线程被分配到了CPU等资源而进入运行状态后,这个线程就称为是被”调度”或称为被线程调度管理器选中了。Java支持一种”抢占式”(preemptive)调度方式。抢占式是和协作式(cooperative)相对的概念。所谓协作式,是指一个执行单元一旦获得某个资源的使用权,别的执行单元就无法剥夺,即使其他线程的优先级更高,而抢占式则与之相反。比如,在一个低优先级线程的执行过程中,来了一个高优先级线程,若在协作式调度
26、系统中,这个高优先级线程必须等待低优先级线程的时间片执行完毕,而抢占式调度方式则不必,可以直接把控制权抢占过来。由于Java的线程调度遵循的是抢占式,因此,为使低优先级线程能够有机会运行,较高优先级线程可以进入”睡眠”(sleep)状态。进入睡眠状态的线程必须在被唤醒之后才能继续执行。,7.4.3 线程的调度和优先级,第7章 多线程,Java语言程序设计,7.4 线程的同步,7.5 案例分析,利用本章所学的多线程相关知识完成一个具有一定功能的综合实例。,Java语言程序设计,第7章 多线程,5个人排队买电影票,售票员只有一张5元的零钱,电影票5元一张。假设5个人的名字及排队顺序是:赵、钱、孙、
27、李、周。赵拿一张20元的人民币买2张票;钱拿1张20元的人民币买1张票;孙拿1张10元的人民币买1张票;李拿一张10元的人民币买2张票;周拿1张5元的人民币买1张票。售票员必须按如下规则找零钱: (1)如果20元买2张票,允许找零:1张10元;不允许找零:2张5元。(2)如果20元买1张票,允许找零:1张5元,1张10元;不允许找零:3张5元。(3)如果10元买1张票,允许找零:1张5元。,7.5.1 案例情景模拟排队买票,7.5 案例分析,第7章 多线程,Java语言程序设计,程序运行的结果可能如下:,7.5.2 运行结果,第7章 多线程,Java语言程序设计,7.5 案例分析,7.6 任务
28、训练多线程使用,Java语言程序设计,第7章 多线程,(1)掌握多线程的含义;(2)掌握线程的创建;(3)掌握线程的生命周期;(4)掌握多线程的同步。,7.6.1训练目的,7.6 任务训练多线程使用,第7章 多线程,Java语言程序设计,1对正文中各段代码编写完整的程序段代码。2完成思考与练习中程序的编写与调试。3模拟学生上课:答疑课上,老师逐一回答学生的问题,同一时刻教师只能回答一个学生的问题,其他学生等到前面同学的问题解决后,再向老师提出问题。 【参考程序】:package chapter10;public class Classroom public static void main(S
29、tring args) Teacher th=new Teacher( ); Student st1=new Student(th,张一号的问题); Student st2=new Student(th,李二号的问题); Student st3=new Student(th,陈三号的问题);,try st1.t.join( );st2.t.join( ); st3.t.join( ); catch(InterruptedException e) class Teacher public void answer(String msg) System.out.print(+msg); try Th
30、read.sleep(int)(Math.random( )*200); catch(InterruptedException e) System.out.println(回答完毕。);,class Student implements Runnable private String msg; private Teacher th; Thread t; public Student(Teacher th,String s) this.th=th; msg=s; t=new Thread(this); t.start( ); public void run( ) synchronized(th)
31、 th.answer(msg); ,7.6.2训练内容,第7章 多线程,Java语言程序设计,结果:,7.6 任务训练多线程使用,7.7 拓展知识,Java语言程序设计,第7章 多线程,1、问:线程与进程有什么关系?,7.7 拓展知识,第7章 多线程,Java语言程序设计,答:线程是比进程更小的执行单位。一个进程在其执行过程中可以产生多个线程,每一个线程就是一个程序内部的一条执行线索,这些线程可以交替运行。多任务与多线程是两个不同的概念。前者是针对操作系统而言的,表示操作系统可以同时运行多个应用程序;后者是针对一个程序而言的,表示在一个程序内部可以同时执行多个线程。,2、线程安全与不安全的区别
32、。,第7章 多线程,Java语言程序设计,答:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。,7.7 拓展知识,7.8 思考与练习,Java语言程序设计,第7章 多线程,1.线程调用了sleep方法后将进入( )。 A.运行状态 B. 堵塞状态 C. 终止状态 D. 初始状态,7.8 思考与练习,第7章 多线程,Java语言程序设计,一选择题,B,2.关于java线程,下列说法错
33、误的是( )。A. 线程是以CPU为主体的行为 B. 线程是比进程更小的执行单位 C. 创建线程有两种方法:继承Thread类和实现Runnable接口 D. 新线程一旦被创建,将自动开始运行,7.8 思考与练习,第7章 多线程,Java语言程序设计,一选择题,D,3.实现线程同步时,应加关键字( ) public B. class C.synchronized D.main,7.8 思考与练习,第7章 多线程,Java语言程序设计,一选择题,C,Thread myThread=new MyThreadClass();myThread.start();try myThread.sleep(10
34、000); catch(InterruptedException e)myThread.stop();,7.8 思考与练习,第7章 多线程,Java语言程序设计,二程序分析题(对下列程序进行分析并完成填空),程序执行完第一行后进入 状态,程序执行完第二行后进入 状态,程序开始执行第五行时进入 运行状态,程序执行完第五行后进入 状态,程序执行完第九行后进入 状态。(新建,运行,堵塞,终止),7.8 思考与练习,第7章 多线程,Java语言程序设计,二程序分析题(对下列程序进行分析并完成填空),运行,新建,运行,终止,堵塞,重庆航天职业技术学院,Thank You !,Java语言程序设计第7章 多线程,