《Java语言实用教程第10章.ppt》由会员分享,可在线阅读,更多相关《Java语言实用教程第10章.ppt(52页珍藏版)》请在三一办公上搜索。
1、第10章 多线程机制,10.1 多线程的概念10.2 创建线程10.3 线程的优先级10.4 线程的控制10.5 线程的同步机制与共享资源10.6 何时使用多线程及注意问题习题,程序(program)是对数据描述与操作的代码的集合,是应用程序执行的脚本。进程(process)是程序的一次执行过程,是操作系统运行程序的基本单位。程序是静态的,进程是动态的。系统运行一个程序就是一个进程从创建、运行到消亡的过程。系统可以为一个程序同时创建多个进程。每一个进程都有自己独立的一块内存空间和一组系统资源,即使同类进程之间也不会共享系统资源。,10.1 多线程的概念 10.1.1 程序、进程和多任务,多任务
2、是指在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每一个任务对应一个进程。由于一个CPU在同一时刻只能执行一个程序中的一条指令。实际上,多任务运行的并发机制使这些任务交替运行,因间隔时间短,所以感觉就是多个程序在同时运行。,运行一个程序时,程序内部的代码都是按顺序先后执行的。如果能够将一个进程划分为更小的运行单位,则程序中一些彼此相对独立的代码段可以重叠运行,将会获得更高的执行效率。线程就是解决这个问题的。线程是比进程更小的运行单位,是程序中单个顺序的流控制。一个进程中可以包含多个线程。线程是一种特殊的多任务方式。当一个程序执行多线程时,可以运行两个或更多的由同一个程序启动的任务。
3、这样,一个程序可以使得多个活动任务同时发生。,10.1.2 线程,线程与任何一个程序一样有一个开始、一系列可执行的命令序列、一个结束。在执行的任何时刻,只有一个执行点。线程与程序不同的是线程本身不能运行,它只能包含在程序中,只能在程序中执行。一个线程在程序运行时,必须争取到为自己分配的系统资源,如执行堆栈、程序计数器,等等。,多线程是相对于单线程而言的,指的是在一个程序中可以定义多个线程并同时运行它们,每个线程可以执行不同的任务。与进程不同的是,同类多线程共享一块内存空间和一组系统资源,所以,系统创建多线程开销相对较小。因此,也称线程为轻负荷进程。多线程和多任务是两个既有联系又有区别的概念。多
4、任务是针对操作系统而言的,代表着操作系统可以同时执行的程序个数;多线程是针对一个程序而言的,代表着一个程序内部可以同时执行的线程个数,而每个线程可以完成不同的任务。,10.1.3 多线程,1.线程的生命周期与状态 一个线程也有从创建、运行到消亡的过程,称为线程的生命周期。使用线程的状态表明线程处于生命周期的哪个阶段。线程有创建(New)、可运行(Runnable)、运行中(Running)、挂起(Not Runnable)、死亡(Dead)5种状态。通过线程的控制和调度可使线程在这几种状态间转化。Java语言内在支持多线程,所有的类都是在多线程思想下定义的。Java的每个程序自动拥有一个线程,
5、称为主线程。当程序加载到内存时,启动主线程。要加载其他线程,程序就要使用Thread类(专门用来创建和控制线程的类)或Runnable接口。,10.1.4 线程的生命周期与Java的多线程机制,2.Java的多线程机制java.lang中的线程类Thread封装了所有需要的线程操作控制,有很多方法用来控制一个线程的运行、休眠、挂起或停止。这就是Java的多线程机制。使用Java的多线程机制编程可将程序的任务分解为几个并行的子任务,通过线程的并发执行来加速程序运行,提高CPU的利用率。,例10.1 在程序中通过继承Thread类创建一个线程子类testThread,通过Thread1主类同时运行
6、两个线程对象t1和t2,运行结果如图10.1所示。class Thread1 public static void main(String args)testThread t1=new testThread(thread1);testThread t2=new testThread(thread2);t1.start();t2.start();,10.2 创建线程 10.2.1 通过继承Thread类创建线程,class testThread extends Thread public testThread(String str)super(str);/调用父类的构造方法为线程对象命名 pub
7、lic void run()for(int i=0;i3;i+)System.out.println(getName()+在运行);try sleep(1000);/用休眠1000毫秒来区分哪个线程在运行 System.out.println(getName()+在休眠);catch(InterruptedException e)System.out.println(getName()+已结束);,图 10.1,(1)Application应用程序运行时总是调用main方法,因此main是创建和启动线程对象的地方。main本身也是一个线程,是程序自动拥有的一个线程,称为主线程。在main方法中
8、创建了两个testThread线程对象:t1与t2,并在创建后马上调用start方法启动了这两个线程。(2)从输出的结果可以看出两个线程的名字是交替显示的,这是因为两个线程是同步的,于是,两个run方法也同时被执行。线程语句的顺序只是决定于线程执行的顺序,线程的执行顺序是由系统调度和控制的。,(3)由继承Thread创建的子类,必须覆盖run方法,因为Thread线程类的run方法是空的。run是线程类的关键方法,线程的所有活动都是通过它来实现的。当调用线程对象时系统就自动调用其run方法,正是通过run方法才使创建线程的目的得以实现。run方法的作用如同Application应用程序的mai
9、n方法一样。由例子可以看出,创建线程对象,就是设计run方法。一旦启动线程对象,就进入run方法,便执行run中的所有语句,run方法执行完毕,这个线程也就结束了。,通过Thread类创建线程子类的格式为:class 线程的类名 extends Thread public void run()程序语句,当一个类是从其他类继承时,如继承Applet类。此时就不能再继承Thread类来创建线程,这时可以通过接口Runnable直接创建线程对象。接口Runnable是一个抽象接口,接口中只声明了一个未实现的run方法。,10.2.2 通过Runnable接口创建线程,例10.2 通过Runnable
10、接口运行线程,运行结果如图10.2所示。import java.awt.*;import java.applet.Applet;import java.util.*;import java.text.DateFormat;public class Clock2 extends Applet implements Runnable Thread clockThread=null;public void init()setBackground(Color.blue);setForeground(Color.yellow);public void start()if(clockThread=null
11、)clockThread=new Thread(this,Clock2);clockThread.start();,public void run()Thread myThread=Thread.currentThread();while(clockThread=myThread)repaint();try Thread.sleep(1000);catch(InterruptedException e)public void paint(Graphics g)Date date=new Date();DateFormat formatter=DateFormat.getTimeInstance
12、();String str=formatter.format(date);g.drawString(str,5,10);,public void stop()clockThread=null;图 10.2通过例10.2可以了解通过接口创建线程对象是通过Thread类直接创建线程对象的。,在Applet的start方法中用new操作符创建了一个线程对象clockThread:clockThread=new Thread(this,Clock2);clockThread.start();此时,线程clockThread处于New Thread创建状态,是一个空线程,因为还没有为它分配系统资源。处于
13、创建状态的线程除了可以调用start方法开始线程外,不能调用其他方法,否则将产生IllegalThreadStateException异常。,调用线程的start方法后,将为线程分配系统资源,并将线程加入线程队列,然后调用run方法运行线程。此时,线程转入Runnable可运行状态。由于大部分计算机只有一个CPU,不可能同时运行所有处于Runnable状态的线程,于是Java虚拟机就建立了一个线程队列,让这些线程以排队的方式轮流使用CPU。在实现接口Runnable的run方法中,又通过Thread.currentThread方法创建了一个当前运行的线程对象myThread:Thread my
14、Thread=Thread.currentThread();,设计Thread的run方法。因为Thread的run方法是空方法,而run方法是完成线程任务的根本途径,本例中通过接口Runnable来实现run方法的具体设计:首先创建一个线程对象myThread,然后根据判断条件如果当前线程是clockThread,就进入循环。在循环中先执行repaint方法,它将调用paint显示系统时间。然后令线程休眠1000毫秒,此时线程将停止运行。休眠结束时线程被唤醒,如果CPU可用,就会继续运行这个线程,否则将排队等待。由本例可知,接口Runnable只是提供了一个可以实现run方法的途径。,两种创
15、建线程方法的比较:(1)由继承Thread类创建线程对象简单方便,可以直接操作线程,但不能再继承其他类;(2)在继承其他类的类中可用Runnable接口创建线程对象。可保持程序风格的一致性。,大部分计算机都是一个CPU,一个时刻只能运行一个线程。如果有多个线程处于可运行状态,需要排队等待CPU资源,此时线程自动获得一个线程的优先级,CPU资源分配是根据“先到先服务”的原则确定的。Java为了使有些线程可以提前得到服务,可给线程设置优先级。在单个CPU上运行多线程时采用了线程队列技术,Java虚拟机支持固定优先级队列,一个线程的执行顺序取决于其相对其他Runnable线程的优先级。,10.3 线
16、程的优先级,线程在创建时,继承了父类的优先级。线程创建后,可以在任何时刻调用setPriority方法改变线程的优先级。优先级为110,Thread定义了其中3个常数:(1)MAX_PRIORITY,最大优先级(值为10)。(2)MIN_PRIORITY,最小优先级(值为1)。(3)NORM_PRIORITY,默认优先级(值为5)。,例10.3 线程优先级的使用情况如图10.3所示。图 10.3,class Thread2 extends Thread public static void main(String args)Thread2 t=new Thread24;for(int i=0;
17、i4;i+)ti=new Thread2();for(int i=0;i4;i+)ti.start();t1.setPriority(MIN_PRIORITY);t3.setPriority(MAX_PRIORITY);public void run()for(int i=0;i1000000;i+);System.out.println(getName()+线程的优先级是+getPriority()+已计算完毕!);,通过线程的方法可以控制线程的状态。1.挂起一个线程如果要暂停一个线程可使用suspend()方法:t1.suspend(),t1为线程对象实例名称。在想运行线程时,用resum
18、e()方法重新激活线程:t1.resume()。2.停止一个线程用stop()方法可以停止线程的执行:t1.stop()。注意:这并没有消灭这个线程,只是停止了线程的执行。但这个线程不能用t1.start()重新启动。,10.4 线程的控制,3.线程休眠如果让线程停止运行几毫秒可使用sleep(long)方法,上面已经提到。4.连接线程join()方法可以中止当前线程,等待调用该方法的线程完成后再继续本线程。5.暂停线程调用yield()方法可以暂停当前运行线程运行,但处于可运行状态。其他线程可照样运行。若无其他线程运行,则继续运行该线程。,6.中断线程如果要中断一个运行中的线程可使用inte
19、rrupt()方法:t1.interrupt()。要运行该线程可使用t1.start()重新启动。7.了解线程的状态如果要知道线程处于什么状态,可通过线程的isAlive()方法来了解,它将返回线程当前的状态。如果返回值为true,说明线程处于Runnable或Not Runnable状态;如果返回值为false,说明线程处于New Thread或Dead状态。该方法无法进一步区分具体状态。,前面的线程例子都是独立的,而且异步执行,也就是说每个线程都包含了运行时所需要的数据和方法,不需要外部资源,也不用关心其他线程的状态和行为。但有时一些同时运行的线程需要共享数据。因此,编程时必须考虑其他线程
20、的状态和行为,以解决资源共享问题。Java提供了同步设定功能。共享对象可将自己的成员方法定义为同步化(synchronized)方法,通过调用同步化方法来执行单一线程,其他线程则不能同时调用同一个对象的同步化方法。,10.5 线程的同步机制与共享资源,例10.4 生产者和消费者线程同步化问题示例。使用某种资源的线程称为消费者,产生或释放这个资源的线程称为生产者。生产者生成10个整数(09),存储到一个共享对象中,并把它们打印出来。每生成一个数就随机休眠0100ms,然后重复这个过程。一旦这10个数可以从共享对象中得到,消费者将尽可能快地消费这10个数,即把它们取出后打印出来。这个模型由4个程序
21、组成。,1.生产者程序public class Producer extends Thread private Share shared;private int number;public Producer(Share s,int number)shared=s;this.number=number;public void run()for(int i=0;i10;i+)shared.put(i);System.out.println(生产者+this.number+输出的数据为:+i);try sleep(int)(Math.random()*100);catch(InterruptedEx
22、ception e),2.消费者程序public class Consumer extends Thread private Share shared;private int number;public Consumer(Share s,int number)shared=s;this.number=number;public void run()int value=0;for(int i=0;i10;i+)value=shared.get();System.out.println(消费者+this.number+得到的数据为:+value);,3.共享资源对象public class Sha
23、re private int contents;public int get()return contents;public void put(int value)contents=value;,4.主程序public class PCTest public static void main(String args)Share s=new Share();Producer p=new Producer(s,1);Consumer c=new Consumer(s,1);p.start();c.start();,分析一下可能发生的情况:一种情况是生产者比消费者速度快,那么在消费者还没有取出上一个
24、数据之前,生产者又存入了新数据,于是,消费者很可能会跳过上一个数据。另一种情况则相反,当消费者比生产者速度快,消费者可能两次取出同一个数据,如图10.4所示。我们希望生产者存入一个数,消费者取出的就是这个数。必须锁定生产者线程,当它向共享对象中存储数据时禁止消费者线程从中取出数据,反之也一样。将共享对象Share中的put和get分别定义为同步化方法就可达到这个目的。,图 10.4,5.改写后的程序对共享资源对象实现同步化,运行结果如图10.5所示图 10.5,public class Share private int contents;private boolean available=f
25、alse;public synchronized int get()while(available=false)try wait();catch(InterruptedException e)available=false;notifyAll();return contents;public synchronized void put(int value)while(available=true)try,wait();catch(InterruptedException e)contents=value;available=true;notifyAll();,修改后的Share仍利用put和g
26、et方法来写入和读取数据,但对put和get方法添加了关键字synchronized,锁定了方法。并增加了wait和notifyAll方法。因为wait和notifyAll是Object类的方法可以直接使用。wait方法可以使线程进入短暂休眠,收到notifyAll方法的通知后会马上醒来。当消费者线程调用共享对象的get方法时,如果生产者没有写入数据,available变量就会保持为false,线程进入循环并调用wait方法等待。一旦生产者写入了新数据,available的值就会改变,同时生产者还会向消费者发出通知,唤醒消费者线程退出循环。此时,消费者线程将做两个非常重要的工作,一是把avai
27、lable变量改为false,二是通知生产者线程。最后,返回contents,它包含最新写入的数据。,当生产者线程第一次调用共享对象的put方法时,available变量为false,线程将跳过循环并将第一个数据写入contents变量,然后将available变量改为true,调用notifyAll方法通知消费者线程可以取数据了。再次调用put方法时,如果消费者没有取走数据,available变量就会保持为true,线程将进入循环并调用wait方法等待。一旦消费者取走上一个数据,available的值就会改变,线程也会被唤醒并退出循环,继续后面的工作。说明:关键字synchronized用于
28、声明在任何时候都只能有一个线程可以执行一段代码或一个方法。它有两种作用:锁定一个对象,或者锁定一个方法。,例10.5 设计一个银行账户的存款线程。假设一个有100名雇员的公司,该公司在银行设立了一个公共的账号,每个雇员都可以在任何时间向账号中存钱和取款。如果不进行同步可能会使得账号上的资金数目弄错。所以在下面的程序中使用资源锁定的方法进行同步。本例中有3类:银行账户类、存款线程类、取款线程类。当存入2000元,取出1500元时,运行结果如图10.6所示。图 10.6,class Account1/账户缓冲区 private String name;private int value;void
29、put(int i)/欲存入金额i的方法 value=value+i;/存入时,value值增加 int get(int i)/欲取金额i的方法,返回实际取到金额 if(valuei)value=value-i;/取走时,value值减少 else/账户金额不够所取时 i=value;value=0;/取走全部所余金额 return i;int howmatch()/查看账户上现有金额方法 return value;,class Save extends Thread/存款线程 private Account1 a1;private int amount;public Save(Account
30、1 a1,int amount)this.a1=a1;this.amount=amount;public void run()synchronized(a1)/锁定账户对象 int k=a1.howmatch();try sleep(1);/花费时间 catch(InterruptedException e)System.out.println(e);a1.put(amount);System.out.println(现有+k+,存入+amount+,余额+a1.howmatch();public static void main(String args),Account1 a1=new Ac
31、count1();(new Save(a1,100).start();(new Save(a1,200).start();(new Fetch(a1,500).start();class Fetch extends Thread/取款线程 private Account1 a1;private int amount;public Fetch(Account1 a1,int amount)this.a1=a1;this.amount=amount;public void run()synchronized(a1)/锁定账户对象 int k=a1.howmatch();try sleep(1);/
32、花费时间 catch(InterruptedException e),System.out.println(e);System.out.println(现有+k+,取走+a1.get(amount)+,余额+a1.howmatch();,在这里使用的同步方法非常简单,仅使用了synchronized关键字锁定了账户对象a1,保证同一时间内只有一个线程可以获取这个对象。使用synchronized关键字锁定对象资源后,只有在该线程获得共享资源对象后,方可运行。任何时刻都只有一个获得共享资源对象的程序在运行,其他线程在获得共享资源对象前处于等待状态。这种方式称为“同步对象”。当第一个线程执行带有同
33、步对象的代码时,获得使用该对象的使用权,或称拥有该对象的锁。当线程执行完代码时,自动释放锁,等待的第二个线程获得锁并开始运行。,考虑到多线程时,人们立刻会想到某些任务是可以使用多线程的,例如数据计算、数据库查询,以及输入的获得。因为这些任务通常都被认为是后台任务,不直接与用户打交道。在Java语言程序设计中,动态效果的程序都会使用多线程,例如动画的播放、动态的字幕,等等。,10.6 何时使用多线程及注意问题,在程序中使用多线程是有代价的。它会对系统产生以下影响:(1)线程需要占用内存;(2)线程过多,会消耗大量CPU时间来跟踪线程;(3)必须考虑多线程同时访问共享资源的问题,如果没有协调好,就
34、会产生令人意想不到的问题,例如可怕的死锁和资源竞争;(4)因为同一个任务的所有线程都共享相同的地址空间,并共享任务的全局变量,所以程序也必须考虑多线程同时访问全局变量的问题。,10-1 Java语言中的线程和多线程指的是什么?10-2 用继承的方法创建一个多线程程序。10-3 使用接口Runnable创建一个多线程程序。10-4 同步机制有什么作用?10-5 能否在生产者和消费者模型中使用synchronized关键词锁定资源对象达到同步的目的,请你重写程序,并在机上进行试验。,习题,10-6 下面有关线程的哪些叙述是对的。(BD)A.一旦一个线程被创建,它就立即开始运行。B.使用start()方法可以使一个线程成为可运行的,但是它不一定立即开始运行。C.当一个线程因为抢先机制而停止运行,它被放在可运行队列的前面。D.一个线程可能因为不同的原因停止运行并进入就绪状态。10-7 方法resume()负责恢复哪些线程的执行?(D)A.通过调用stop()方法而停止的线程。B.通过调用sleep()方法而停止运行的线程。C.通过调用wait()方法而停止运行的线程。D.通过调用suspend()方法而停止运行的线程。,