资源描述
,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,计算机科学与技术学院网络工程教研室,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,*,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,*,计算机软件及应用线程ppt,计算机软件及应用线程ppt计算机软件及应用线程ppt一、线程的创建与运行一、多线程原理与概念,二、线程的运行过程,三、线程的状态与生命周期,四、创建线程的两种方式,一、线程的创建与运行,一、多线程原理与概念,二、线程的运行过程,三、线程的状态与生命周期,四、创建线程的两种方式,一、多线程原理和概念,一、线程的创建与运行,1.单线程,以往开发的程序都是单线程的,一个程序只有一条从头到尾的执行线索。如,public class,ClassA,public static void,main(String,args,),while(true),System.out.println(hello,);,大多数程序在设计时习惯上从考虑使程序从头至尾一步步地实现所有的功能入手,即使得一个程序只有一条执行路线。,一、线程的创建与运行,2.多线程,public class ClassA,public static void main(String args),while(true),System.out.println(“hello!);,while(true),System.out.println(“你好!);,问题:这个程序的执行结果是什么?,一、多线程原理和概念,一、线程的创建与运行,2.多线程,在现实世界里,事情往往是同时进行的,这说明计算机世界要想真正地反映现实世界,必须解决事情的,同步问题,,反映到计算机世界,即解决程序,实现多线程,的问题。,因此可编写有几条执行路径的程序,使得程序能够同时执行多个任务,借此实现多线程问题。,Java,语言的一大特点就是,内置对多线程,的支持。,一、多线程原理和概念,一、线程的创建与运行,2.多线程,如何修改,ClassA.java,?使它按照两条输出路线进行内容的输出?,一、多线程原理和概念,一、线程的创建与运行,2.多线程,如何修改,ClassA.java,?使它按照两条输出路线进行内容的输出?,思路,:,定义两个线程类,每个线程负责一条输出:,class OutThread1 extends Thread,class OutThread2 extends Thread,在,main(),中启动两条线程,OutThread1,outThread1,=new OutThread1();,OutThread2,outThread2,=new OutThread2();,outThread1.start();,outThread2.start();,一、多线程原理和概念,一、线程的创建与运行,2.多线程,掌握线程编程的几个关键之处:,理解线程运行的理论原理,理解程序中,每条线程,的生命周期,何时被创建,何时开始,何时(被)中断,何时继续执行,何时结束?,线程中常用方法的功能、作用和编程技巧,start(),run(),sleep(),isAlive(),currentThread(),interrupt(),wait(),notify(),notifyAll,(),认真分析程序中每条线程的执行过程,一、多线程原理和概念,一、线程的创建与运行,3.线程运行的理论原理,程序:,一段静态的代码,是应用软件执行的蓝本,。,进程,操作系统中程序的一次动态执行过程,对应了从代码加载、执行到执行完毕的一个完整过程。每个进程都有,一段专用,的,内存区域,。,线程,一个进程在执行过程中,可以产生多个线程,形成多条执行线索,每个线程也是动态的,都有其自身产生、存在和消亡的过程,但线程间可以,共享相同的内存单元,,利用共享单元来实现数据交换、实时通信等同步操作。,一、多线程原理和概念,一、线程的创建与运行,3.线程运行的理论原理,CPU,的时间片,操作系统使用,分时,管理各个进程,按照,时间片,管理每个进程。,操作系统每次分时给,java,程序一个,时间片,的,CPU,时间内,在若干个独立的可控制线程之间切换。,CPU的执行时间被划分成大小相等的时间片,每个时间片被分配给一个进程使用,时间片用完后进程需要排队等待下一次被调度;下一个时间片被分配给别的进程使用,一、多线程原理和概念,一、线程的创建与运行,3.线程运行的理论原理,Java Application,的主线程,每个,Java,程序都有一个默认的主线程。,Java Application,总是从主类的,main,方法开始执行。发现,main,方法后,就启动一个线程,这个线程称为主线程。,其他线程,在,main,方法的执行中再创建的线程,称为其他线程。,一、多线程原理和概念,一、线程的创建与运行,3.线程运行的理论原理,其他线程,如果,main,方法中没有创建其他线程,当,main,方法执行完最后一个语句时,程序就结束并返回。,如果,main,方法中又创建了其他线程,,JVM,在,主线程,和,其他线程,之间切换,每个线程都有机会使用,CPU,资源。,JVM,一直到程序中的所有线程都执行结束后,才结束,java,应用程序。,JVM,应用程序1,主线程,线程1,线程2,应用程序n,一、多线程原理和概念,一、线程的创建与运行,1.程序中线程的运行过程分析:代码,LeftAndRight.java,读程序,LeftAndRight.java,,回答下面的问题:,该程序中一共启动了,几个,线程?,每个线程都是,如何被创建,的?,每个线程是,何时开始运行,的?,每个线程是,如何被中断,将,CPU,资源给其他线程使用的?,每个线程是,何时运行结束,的?,定义了其他线程的,Java,程序中,程序的执行结果是固定的吗?,二、线程的运行过程,一、线程的创建与运行,1.程序中线程的运行过程分析:代码,LeftAndRight.java,问题一:该程序中一共启动几个线程?,一共启动了三个线程:主线程,main,,其他线程,right,和,left,JVM,加载代码时,发现,main(),方法,启动主线程,将,CPU,资源给主线程,启动,main(),方法。主线程在获得,CPU,资源后,执行了下面的操作,创建了线程,left,和,right,。,left=new,Lefthand();,/创建线程,。,right=new,Righthand,();,left.start,();,right.start,();,然后执行输出:,for(int,i=0;i=6;i+),System.out.println,(,我是主线程,);,二、线程的运行过程,一、线程的创建与运行,二、线程的运行过程,1.程序中线程的运行过程分析:代码,LeftAndRight.java,问题一:该程序中一共启动几个线程?,但是下面的输出不会全部执行完毕或者马上执行,为什么?,for(int,i=0;i=6;i+),System.out.println,(,我是主线程,);,问题二:每个线程都是如何被创建的?,线程,left,和,right,分别是通过,Thread,的子类,Lefthand,和,RightHand,线程类来创建的。创建后,left,和,right,处于,新建状态,。,class,Lefthand,extends Thread,public void run(),LeftHand,left=new,Lefthand,();,一、线程的创建与运行,二、线程的运行过程,1.程序中线程的运行过程分析:代码,LeftAndRight.java,问题三:每个线程是何时开始运行的?,JVM,首先将,CPU,资源给主线程,主线程的运行过程中创建了其他线程,left,和,right,,当执行到下列语句时,,left.start,();,right.start,();,线程,left,和,right,进入到,就绪状态,当,JVM,将,CPU,的使用权切换给线程,left,时,,left,对象中的,run(),方法就立刻执行。,JVM,将,CPU,的使用权在,主线程,线程,right,和线程,left,之间切换。,每个线程都可以按照自己的执行路线向前执行,一、线程的创建与运行,二、线程的运行过程,1.程序中线程的运行过程分析:代码,LeftAndRight.java,问题四:每个线程是如何被中断,将,CPU,资源给其他线程使用的?,当,JVM,分配给,三个线程中任何一个线程,的时间片结束后,如果该线程还没有运行结束,它也必须让出,CPU,资源给其他线程使用,而本线程到,JVM,的等待队列中排队,等待,JVM,在下一次调度中将,CPU,资源分配给该线程使用。,main(),right.start(),left.start(),main(),main(),left,right,main(),right,left,CPU资源在,主线程,left,right三个线程之间进行切换,一、线程的创建与运行,二、线程的运行过程,1.程序中线程的运行过程分析:代码,LeftAndRight.java,问题四:每个线程是如何被中断,将,CPU,资源给其他线程使用的?,线程,left,和,right,在,run(),方法的执行中,,public void run(),for(,int,i=1;i=9;i+),System.,out,.println,(,我是左手线程,);,try,sleep,(500);,catch(,InterruptedException,e),线程执行一次输出后,休眠,500ms,(毫秒),使自己放弃,CPU,资源,,500ms,时间到后,重新排队等待接受,CPU,的调度,一、线程的创建与运行,二、线程的运行过程,1.程序中线程的运行过程分析:代码,LeftAndRight.java,问题五:每个线程是何时运行结束的?,主线程:创建好线程,left,和,right,,并完成输出“,我是主线程,”语句七次后,执行结束。,线程,left,:其,run(),方法中,完成输出“,我是左手线程,”语句九次后,执行结束。,线程,right,:其,run(),方法中,完成输出“,我是右手线程,”语句五次后,执行结束。,一、线程的创建与运行,二、线程的运行过程,1.程序中线程的运行过程分析:代码,LeftAndRight.java,问题六:定义了其他线程的,Java,程序中,程序的执行结果是固定的吗?,不是的,这取决于程序的流程和本机本次运行中,CPU,的调度情况。,一、线程的创建与运行,三、线程的状态与生命周期,1.新建状态,当一个,Thread,类或其子类的对象被声明并被创建时,新生的线程对象处于新建状态。,LeftHand,left=new,Lefthand,();/,线程,left,处于新建状态。,已经分配了相应的内存空间和其他资源,。,但,JVM,管理的线程队列中还没有这个线程。,一、线程的创建与运行,三、线程的状态与生命周期,2.就绪状态,通过调用线程的,start(),方法,线程由新建状态变为就绪状态,;,left.start,();,由,JVM,管理线程,left,,排队等待,CPU,的使用权。,3.,运行状态,JVM,将,CPU,的使用权交给一个线程后,该线程进入运行状态,调用其,run(),方法,执行线程操作的具体内容。,run(),方法中定义了线程对象,被调度后所执行的操作,,是线程获得,CPU,的使用权后系统自动调用的方法,不需用户程序引用。,run(),定义了该线程的使命,,是需要被重写覆盖父类的,run(),方法的。,一、线程的创建与运行,三、线程的状态与生命周期,4.中断状态,JVM将CPU资源从当前线程切换给其他线程,,当前线程进入到中断状态,引起线程中断的原因有:,线程在使用,CPU,资源期间执行,sleep(int,milsecond,),方法,使线程进入休眠状态,让出,CPU,的使用权。在,milsecond,毫秒后,线程重新进入到线程队列中排队等待,CPU,资源,以便从中断处继续运行。,线程在使用,CPU,资源期间执行,wait(),方法,进入等待状态。由其他线程使用,notify(),方法通知此线程进入到线程队列中排队等待,CPU,资源。,线程在使用,CPU,资源期间由于,读写操作,等进入阻塞状态。当阻塞原因被排除后,线程进入到线程队列排队等待,CPU,资源。,一、线程的创建与运行,三、线程的状态与生命周期,5.死亡状态,进入死亡状态的线程不具备继续运行的能力,释放了实体,,释放了分配给线程对象的内存,资源:,正常运行的线程完成了它的全部工作,执行完,run(),方法中全部语句的,结束了,run(),方法。,线程被提前强制性终止。,一、线程的创建与运行,三、线程的状态与生命周期,6.线程的状态转换图,新建,可运行,运行,死亡,start(),操作系统调度,运行结束,睡眠,sleep(毫秒);,休眠时间到,等待/阻塞,wait()或等待,读写操作,notify()/notifyAll()/读写操作完成,一、线程的创建与运行,四、创建线程的两种方式,1.由Thread的子类创建线程,创建一个类,由它,通过继承Thread类并重写其中的run()方法来定义线程体以实现线程的具体行为,然后创建该子类的对象以创建线程。,举例:,分析,StudentAndTeacher.java,回答问题,1.,主线程中又创建了哪两个线程?这两个线程之间有什么关系?,2.,分析这两个线程的执行流程。,一、线程的创建与运行,四、创建线程的两种方式,1.由Thread的子类创建线程,回答问题:分析,StudentAndTeacher.java,1.,主线程中又创建了哪两个线程?这两个线程之间有什么关系?,主线程中由线程类的子类又创建了两个线程,分别是,ComputerSum,cSum,=new,ComputerSum,();,People,teacher,=new People(,教师,100,cSum,);,People,student,=new People(“,学生,”,100,cSum,);,这两个线程共享,ComputerSum,类的对象,cSum,,在运行的过程中修改这个对象的整型成员变量,sum,的值。,一、线程的创建与运行,四、创建线程的两种方式,1.由Thread的子类创建线程,回答问题:分析,StudentAndTeacher.java,2.,分析这两个线程的执行流程。,当线程,teacher,或线程,student,使用,CPU,资源时,先取出类,computerSum,的对象,cSum,的数据成员,cSum,.sum,,并将其加,1,,然后输出当前线程的信息。休眠一段时间以后继续上述操作。,两个线程交替运行,每个线程执行五次上述操作后结束运行,进入死亡状态。,一、线程的创建与运行,四、创建线程的两种方式,1.由Thread的子类创建线程,问题:为什么两个线程会出现相同的,sum,值的情况?,如何才能出现如下运行结果?,一、线程的创建与运行,四、创建线程的两种方式,2.使用Runnable接口创建线程,使用,Thread,子类创建线程可以在子类中增加新的成员变量和方法,使线程具有某种属性或具有某种功能,但,Java,不支持多继承,,Thread,的子类不能再扩展其他的类。,Runnable,接口是,Java,中实现线程的接口。,class People1 implements,Runnable,Thread teacher;,People1(),teacher=new,Thread(this,);,public void run(),一、线程的创建与运行,四、创建线程的两种方式,2.使用Runnable接口创建线程,Thread thread1=new,Thread(Runnable,target),:,目标对象,Runnable,target,:,在创建线程对象时向构造方法的参数传递一个实现了,Runnable,接口的实例,该实例称为所创建对象的,目标对象,。,接口回调:,当执行,teacher.start,(),时,一旦轮到线程,teacher,使用,CPU,资源,目标对象会自动调用其接口中的,run(),方法,这个过程称为,接口回调,。,一、线程的创建与运行,四、创建线程的两种方式,2.使用Runnable接口创建线程,应用举例,:,分析,StudentAndTeacher1.java,问题,1.,线程,teacher,和,student,是在哪里被创建的?采用哪种技术创建的?,2.,分析一下程序的可能运行结果界面是什么?,3.,分析方法,sleep(),的作用是什么?,为了使结果尽量不依赖于当前,CPU,资源的使用情况,应当让线程主动调用,sleep(),方法,让出,CPU,的控制权进入中断状态;使其他线程能够使用,CPU,资源完成一定的操作。,一、线程的创建与运行,四、创建线程的两种方式,2.使用Runnable接口创建线程,应用举例,:,分析,StudentAndTeacher1.java,4.,分析方法,computer(Thread,thread),,,关键字,synchronized,对程序的运行有什么影响?,public synchronized void,computer(Thread,thread),int,m=,computerSum.getSum,();,computerSum.setSum(m,+1);,System.,out,.println,(,我是,+,thread.getName,()+,,现在的和是,+,computerSum.getSum,();,主要是为了处理,线程同步,,当两个以上线程同时访问一个变量时,线程,A,在修改这个变量值的过程中,还没有完成修改的全部动作时,其他线程就不能访问,修改这个变量的方法,。,一、线程的创建与运行,四、创建线程的两种方式,2.使用Runnable接口创建线程,应用举例,:,分析,StudentAndTeacher1.java,public,synchronized,void,computer(Thread,thread),线程同步:若干个线程需要使用一个,synchronized,修饰的方法。,在处理线程同步时,要做的第一件事情就是把修改数据地方法用关键字,synchronized,来修饰;,这样当线程,A,使用这个方法时,其他想使用这个方法的线程就必须等待,直到线程,A,使用完该方法为止。,二、线程的常用方法,一、构造方法,1.,public Thread(ThreadGroup group,Runnable target,String name),参数group代表该线程所属的线程组,target代表执行线程体的目标对象(必须实现Runnable 接口),name代表线程名,2.public Thread(),创建一个系统线程类的对象,3.public Thread(Runnable target),利用参数对象(实现了Runnable接口的target对象)创建线程,4.public Thread(String name),为所创建的线程对象指定一个字符串名称供以后使用,5.public Thread(Runnable target,String name),实现3,4两个构造方法的功能,二、线程的常用方法,二、其他方法,1.start()方法,启动线程,使线程从新建状态进入就绪队列排队等待CPU资源。,2.run()方法,线程所执行具体操作的代码,自动调用,不得引用。,run()方法没有结束之前,不能再让线程调用start()方法,否则会发生IllegalThreadStateException异常。,问题:在程序设计中,避免start()被调用多次而引发的异常?,二、线程的常用方法,二、其他方法,3.sleep(int millsecond)方法,线程进入休眠状态,以毫秒为单位。处于休眠状态的线程让出CPU控制权。需捕捉InterruptedException异常。,4.isAlive()方法,判断线程是否处于活动状态(既已调用start,但run还未返回)。,5.,currentThread()方法,以类名调用,返回当前正在使用CPU资源的线程。,if(Thread.currentThread()=student),二、线程的常用方法,二、其他方法,6.interrupt()方法,线程A进入休眠状态时,其他线程可以调用线程A的interrupt方法“吵醒”,从而使线程A结束休眠。,7.void setName(String threadName),改变线程的名字,8.String gerName(),取得由setName()方法设置的线程名字的字符串,三、多线程应用,一、猜数游戏GuessNumberGame.java,二、猜数游戏之改进GuessNumberGame.java,三、GUI线程之ScrollWordsAndTimer.java,三、多线程应用,一、猜数游戏GuessNumberGame.java,1.程序功能描述,猜数程序,第一个线程,givenNumberThread,负责随机给出,1-100,之间的一个整数,第二个线程,guessNumberThread,负责猜出这个数,每当第二个线程,guessNumberThread,给出自己的猜测后,第一个线程会将其比较后,提示“猜大了”,“猜小了”,“猜对了,当第二个线程,guessNumberThread,给出正确的猜测后,两个线程运行结束,三、多线程应用,一、猜数游戏GuessNumberGame.java,2.问题,两个线程,givenNumberThread,和,guessNumberThread,是在哪里被创建和启动的?,简述猜数线程,guessNumberThread,是如何根据,givenNumberThread,的提示给出下一个,guessNumber,的值的?,在什么情况下线程,givenNumberThread,和,guessNumberThread,运行结束?,观察这个程序的运行,看它是否存在什么问题?原因是什么?如何解决?,三、多线程应用,一、猜数游戏GuessNumberGame.java,3.程序流程图,for(i),givenNumberThread线程在运行,i=1,产生,realNumber,小于,guessNumber与realNumber比较,message=,SMALLER,提示猜小了,大于,message=,LARGER,提示猜大了,等于,message=,SUCCESS,提示猜对了,线程运行结束,是,否,guessNumberThread线程在运行,休眠一段时间,i=1,产生guessNumber,是,否,根据message的值修改GuessNumber,SUCCESS,LARGER,SMALLER,修改min或max,猜对了!,线程运行结束,休眠一段时间,三、多线程应用,一、猜数游戏GuessNumberGame.java,3.问题,观察这个程序的运行,看它是否存在什么问题?原因是什么?如何解决?,三、多线程应用,一、猜数游戏GuessNumberGame.java,3.问题,观察这个程序的运行,看它是否存在什么问题?原因是什么?如何解决?,通常情况下,我们希望,givenNumberThread,和,guessNumberThread,能够交替工作,,guessNumberThread,在,给出猜得的数字,guessNumber,后,让出,CPU,资源;,然后,givenNumberThread,能立刻使用,CPU,资源,判定猜得数字的大小,设定,message,的取值,然后让出,CPU,资源;,然后,guessNumberThread,获得,CPU,的使用权,根据,message,的取值调整,guessNumber,的值,然后让出,CPU,资源;,如此反复,直到,guessNumberThread,猜对了,两个线程运行结束!,OK,!,三、多线程应用,一、猜数游戏GuessNumberGame.java,3.问题,但实际中,程序的运行并没有如我们的愿,很多情况下,两个线程并没有如我们想象的那样交替执行;,一个线程的处理流程还没有执行完,其,CPU,资源就被剥夺,等再次获得,CPU,资源执行时,另外一个的执行情况已经无法和它协调工作了,而且猜数的算法本身也存在问题。,如何解决,?,三、多线程应用,二、猜数游戏GuessNumberGame1.java,1.线程中的wait(),notify()和notifyAll()方法,wait(),方法:,当一个线程使用的同步方法中用到某个变量,而此变量又需要其他线程修改后才能符合本线程的需要,那么可以在线程中使用同步方法wait(),使用wait()方法可以中断方法的执行,使得本线程等待;,本线程,暂时让出CPU的使用权,并允许其他线程使用这个同步方法。,三、多线程应用,二、猜数游戏GuessNumberGame1.java,1.线程中的wait(),notify()和notifyAll()方法,notify(),和,notifyAll,(),方法,其他线程如果使用这个同步方法时不需要等待,那么它使用完这个同步方法的同时,应当使用notifyAll()方法通知所有的由于使用这个同步方法而处于等待的线程,结束等待,。,曾中断的线程就会,从刚才的中断处继续执行这个同步方法,,并遵循“,先中断先继续,”的原则。,如果使用notify()方法,则是通知处于等待中的线程的某一个结束等待。,wait(),notify(),notifyAll()方法都是Object类的final方法,是被所有的类继承、而且不允许重写的方法。,三、多线程应用,二、猜数游戏GuessNumberGame1.java,2.在程序流程中加上wait()和notify(),线程,givenNumberThread,每次对,guessNumberThread,所猜得的数字做出正确的判定后(,给变量,message,赋值,),就应该唤醒,guessNumberThread,(,notify(),),,让出,CPU,资源,同时将自己阻塞(,wait(),);,线程,guessNumberThread,每次根据,givenNumberThread,给出的,message,(判定值)的值,调整,guessNumber,的取值后,就应该唤醒线程,givenNumberThread,(,notify(),),,,让出,CPU,资源,同时将自己阻塞(,wait(),),。,三、多线程应用,二、猜数游戏GuessNumberGame1.java,2.在程序流程中加上wait()和notify(),for(i);加同步锁synchronized(this),givenNumberThread线程在运行,大于,guessNumber与realNumber比较,message=,SMALLER,提示猜小了,小于,message=,LARGER,提示猜大了,等于,message=,SUCCESS,提示猜对了,guessNumberThread线程在运行,休眠一段时间,根据message的值修改GuessNumber,SUCCESS,LARGER,SMALLER,修改min和max,猜对了!,线程运行结束,休眠,一段,时间,notify(),线程运行结束,线程运行结束,wait()将自己阻塞,notify()唤醒guessNumberThread,notify()唤醒givenNumberThread,wait()将自己阻塞,三、多线程应用,三、GUI线程,1.,GUI线程,当Java程序中包含图形界面时,Java虚拟机在运行应用程序时会启动更多的线程,其中有两个重要的线程:,AWT-,EventQuecue,:负责处理,GUI,事件,AWT-Windows,:负责将窗体或组件绘制到桌面,2.,ScrollWordsAndTimer.java,本例中是一个带滚动字幕的小时钟,单击“开始显示时间”在文本框中显示系统当前时间;单击“停止时间显示”,时间显示就停止,滚动的字幕在单击“加速”键后,会加速字幕的滚动速度;单击“停止“后,会停止滚动,单击”开始“后,文字会重新开始滚动,三、多线程应用,三、GUI线程,2.,ScrollWordsAndTimer.java,读程序,回答问题:,本例中的两个线程都是在哪里被创建和启动的?,标志位,stopScrolling,stopTimer,的作用是什么?,文字是如何实现滚动的?加速滚动是如何实现的?,单击“停止滚动”按钮后,文字的滚动是如何停止的?单击“开始滚动”后又是如何实现滚动的?,为什么在单击按钮,startWordsBtn,以后,判定线程已经进入死亡状态后,才给线程分配新的实体?而且需要将,startWordsBtn,设置为不可用?,三、多线程应用,三、GUI线程,2.,ScrollWordsAndTimer.java,读程序,回答问题:,为什么在单击按钮,startWordsBtn,以后,判定线程已经进入死亡状态后,才给线程分配新的实体?而且需要将,startWordsBtn,设置为不可用?,一个已经运行的线程在没有进入死亡状态之前,不能给它分配新的实体,否则会抛出,IllegalThreadStateException,;,而当通过单击一个按钮让一个线程运行时,在这个线程执行完,run(),方法之前,用户可能会随时再次单击该按钮,这时也会引发,IllegalThreadStateException,,所以可以在单击,startWordsBtn,按钮后,将其设为不可用。,四、小结和作业,1、要精通线程编程,首先需要理解线程的原理,运行过程及其状态装换图,2、练习读线程程序,弄清楚在每一个程序中,线程的启动,运行过程和结束部分的代码,3、掌握线程的创建方式,及其常用方法,4、仿照例题,自己编写简单的线程程序,作业:,完成实验指导书中的题目,谢谢观赏,
展开阅读全文