收藏 分销(赏)

B09多线程-1.ppt

上传人:天**** 文档编号:8915411 上传时间:2025-03-07 格式:PPT 页数:61 大小:175.54KB 下载积分:14 金币
下载 相关 举报
B09多线程-1.ppt_第1页
第1页 / 共61页
B09多线程-1.ppt_第2页
第2页 / 共61页


点击查看更多>>
资源描述
单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,第,9,章 多线程,9.1 多线程的概念,9.2 线程类,9.3 资源的协调与同步,9.4 线程间通信,9.1,多线程的概念,程序是一段静态的代码,它是应用软件执行的蓝本。进程就是程序的运行时的一个实例。,线程可以看作单独地占有CPU时间来执行相应的代码的。,线程是共享地址空间的,也就是说多线程可以同时读取相同的地址空间,并且利用这个空间进行交换数据。,9.1,多线程的概念,多线程具有以下特点:,(,1,)多个线程在运行时,系统自动在线程之间进行切换;,(,2,)由于多个线程共存于同一块内存,线程之间的通信非常容易;,(,3,),Java,将线程视为一个对象。线程要么是,Thread,类的对象,要么是接口,Runnable,的对象。,(,4,)当多个线程并行执行时,具有较高优先级的线程将获得较多的,CPU,时间片;,(,5,)优先级是从,0,到,10,的整数,并且它仅表示线程之间的相对关系;,(,6,)多个线程共享一组资源,有可能在运行时产生冲突。必须采用,synchronized,关键字协调资源,实现线程同步。,9.2,线程类,9.2.1 多线程编程中常用的常量和方法,9.2.2 线程的生命周期,9.2.3 创建多线程的方法,9.2.1,多线程编程中常用的常量和方法,Thread,类包含的常量有:,1.,public static final int MAX_PRIORITY,:最大优先级,值是,10,。,2.,public static final int MIN_PRIORITY,:最小优先级,值是,1,。,3.,public static final int NORM_PRIORITY,:缺省优先级,值是,5,。,9.2.1,多线程编程中常用的常量和方法,常用方法:,currentThread():,返回当前运行的线程对象,是一个静态的方法。,sleep(int n),:使当前运行的线程睡n个毫秒,然后继续执行,也是静态方法。,yield(),:使当前运行的线程放弃执行,切换到其它线程,是一个静态方法。,isAlive(),:判断线程是否处于执行的状态,返回值true表示处于运行状态,false表示已停止。,start(),:使调用该方法的线程开始执行。,run(),:该方法由start()方法自动调用。,9.2.1,多线程编程中常用的常量和方法,常用方法:,stop(),:使线程停止执行,并退出可执行状态。,suspend():,使线程暂停执行,不退出可执行态。,resume(),:将暂停的线程继续执行。,setName(String s),:赋予线程一个名字。,getName(),:获得调用线程的名字。,getPriority(),:获得调用线程的优先级。,setPriority(int p),:设置线程的优先级。,join(),:等待线程死亡,若中断了该线程,将抛出异常。,【,实例,8-1】,class,getThreadInfo,public static void,main(String,args,),Thread,curr,;,int,num=7;,curr,=,Thread.currentThread,();,curr.setPriority(num,);,(,当前线程:,+,curr,);,(,线程名:,+,curr.getName,();,(,优先级:,+,curr.getPriority,();,程序输出结果:,当前线程:,Threadmain,,,7,,,main,线程名:,main,优先级:,7,9.2.2,线程的生命周期,Java,支持一种“抢占式”(,preemptive),调度方式,“Newborn”,(新建)状态,:,线程在己被创建但未执行这段时间内,处于一个特殊的,Newborn,状态,这时,线程对象己被分配内存空间,其私有数据己被初始化,但该线程还未被调度。此时线程对象可通过,start,()方法调度,或者利用,stop,()方法杀死,.,新创建的线程一旦被调度,就将切换到,Runnable,状态。,9.2.2,线程的生命周期,Runnable(就绪)状态,:,表示线程正等待处理器资源,随时可被调用执行。处于就绪状态的线程事实上己被调度,也就是说,它们己经被放到某一队列等待执行。处于就绪状态的线程何时可真正执行,取决于线程优先级以及队列的当前状况。线程的优先级如果相同,将遵循先来先服务的调度原则。,9.2.2,线程的生命周期,“Running”,(运行)状态,:,表明线程正在运行,该线己经拥有了对处理器的控制权,其代码目前正在运行。这个线程将一直运行直到运行完毕,除非运行过程的控制权被一优先级更高的线程强占。,9.2.2,线程的生命周期,“Blocked”,(堵塞)状态,:,一个线程如果处于,Blocked,(堵塞)状态,那么暂时这个线程将无法进入就绪队列。处于堵塞状态的线程通常必须由某些事件才能唤醒。至于是何种事件,则取决于堵塞发生的原因:处于睡眠中的线程必须被堵塞一段固定的时间,;,被挂起、或处于消息等待状态的线程则必须由一外来事件唤醒。,“Dead”,(死亡)状态,:,Dead,表示线程巳退出运行状态,并且不再进入就绪队列。其中原因可能是线程巳执行完毕(正常结束),也可能是该线程被另一线程所强行中断(,kill,)。,图,8-1,线程生命周期示意图,start,创建,就绪,运行,挂起,睡眠,阻塞,结束,等待,时间片结束,分配时间片,睡眠时,间结束,notify,notify All,sleep,wait,stop,I/O,请求,suspend,resume,I/O,请求结束,9.2.3,创建多线程的方法,Java,中编程实现多线程应有两种途径,一种是创建,Thread,线程的子类,实现一个接口,Runnable,无论是哪种途径最终都需要使用,Thread,类及其方法。,9.2.3,创建多线程的方法,1,通过继承,Thread,类实现多线程,(,1,)定义,Thread,类的一个子类。,(,2,)定义子类中的方法,run(),,覆盖父类中的方法,run(),。,(,3,)创建该子类的一个线程对象。,(,4,)通过,start(),方法启动线程。,【,实例,8-2】,class,myThread,extends Thread,int,sleeptime,;,public,myThread(String,id)/,构造函数,super(id,);,sleeptime,=(,int)(Math.random,()*100);,System.out.println(The,Thread Name=+,getName,()+,,,Sleeping,:,+,sleeptime,);,public void run(),try /,通过线程睡眠模拟程序的执行,Thread.sleep(sleeptime,);,catch(InterruptedException,e),System.err.println(Exception,:,+,e.toString,();,System.out.println(The,running Thread=+,getName,();,【,实例,8-2】,public class,fourThreads,public static void,main(String,args,),myThread,t1,,,t2,,,t3,,,t4;,t1=new,myThread(Thread,1);,t2=new,myThread(Thread,2);,t3=new,myThread(Thread,3);,t4=new,myThread(Thread,4);,t1.start();t2.start();,t3.start();t4.start();,【,实例,8-2】,程序某次的运行结果:,he Thread Name=Thread 1,,,Sleeping,:,6,The Thread Name=Thread 2,,,Sleeping,:,49,The Thread Name=Thread 3,,,Sleeping,:,19,The Thread Name=Thread 4,,,Sleeping,:,69,The running Thread=Thread 1,The running Thread=Thread 3,The running Thread=Thread 2,The running Thread=Thread 4,注意:,Thread,类中的,run(),方法具有,public,属性,覆盖该方法时,前面必须带上,public,。,9.2.3,创建多线程的方法,2,通过实现,Runnable,接口实现多线程,(,1,)定义一个实现,Runnable,接口的类。,(,2,)定义方法,run(),。,Runnable,接口中有一个空的方法,run(),,实现它的类必须覆盖此方法。,(,3,)创建该类的一个线程对象,并将该对象作参数,传递给,Thread,类的构造函数,从而生成,Thread,类的一个对象。,(,4,)通过,start(),方法启动线程。,【,实例,8-3】,class,myThread,implements,Runnable,int,count=1,,,number;,public,myThread(int,num),number=num;,(,创建线程:,+number);,public void run(),while(true,),(,线程,+number+,:计数,+count);,if(+count,=6)return;,public class,runnableThreads,public static void,main(String,args,),for(int,i=0;i5;i+)new,Thread(new,myThread(i+1).start();,【,实例,8-3】,程序运行某次的输出结果:,创建线程:,1,创建线程:,2,线程,1,:计数,1,线程,1,:计数,2,线程,1,:计数,3,线程,2,:计数,1,线程,2,:计数,2,线程,2,:计数,3,创建线程:,3,线程,3,:计数,1,线程,3,:计数,2,线程,3,:计数,3,值得指出的是同一个实现了,Runnable,接口的对象作为参数产生的所有,Thread,对象是同一对象下的线程。,9.2.3,创建多线程的方法,3两种方法的简单比较,使用Thread方法简单明了,符合大家的习惯,但是,它也有一个很大的缺点,那就是如果我们的类已经从一个类继承(如小程序必须继承自 Applet 类),则无法再继承 Thread 类。,使用 Runnable 接口来实现多线程使得我们能够在一个类中包容所有的代码,有利于封装,它的缺点在于,我们只能使用一套代码,若想创建多个线程并使各个线程执行不同的代码,则仍必须额外创建类,如果这样的话,在大多数情况下也许还不如直接用多个类分别继承 Thread 来得紧凑。,9.3,资源的协调与同步,9.3.1 线程调度模型,9.3.2 资源冲突,9.3.3 同步方法,9.3.1,线程调度模型,当计算机中只有一个,CPU,时,同一时刻正在运行的线程只能有一个,当一个新的线程通过,new(),创建并通过,start(),方法启动后,线程只是进入就绪状态,是否能运行要看调度的结果。,线程调度程序挑选线程时,将选择处于就绪状态且优先级最高的线程。优先级别主要分高、中、低三个级别,分别代表的数字是,10,、,5,、,1,。最小优先级的常量是,MIN_PRIORITY,,普通的优先级的常量是,NORM_PRIORITY,,最高的优先级的常量是,MAX_PRIORITY,。一般主线程的优先级是普通。另外可以通过,Thread,类的,setPriority,(,int a,)方法来修改系统自动设置的线程优先级。,如果多个线程具有相同的优先级,它们将被轮流调度。,图,8-1,线程优先级调度示意图,新建线程,y,就绪线程,O,的等锁池,优先级多列,正在运行线程,x,y.start(),执行到,synchronized(O),标记不存在则进入,O,等锁池,O,标记返还了,线程可运行,按优先级排队,高优先级占先优先级,【,实例,8-5】,验证了,Java,对多线程的调度方法。,class,myThread,extends Thread,myThread(String,str,),super(str,);,public void run(),tryThread.sleep(2);,catch(InterruptedException,e),System.err.println(e.toString,();,System.out.println(the,Thread Name=+,getName,()+,,,Priority=+,getPriority,();,【,实例,8-5】,验证了,Java,对多线程的调度方法。,public class,testTheadPriority,public static void,main(String,agrs,),Thread,minThread,=new,myThread(MinPriorityThread,);,Thread,normThread,=new,myThread(NormPriorityThread,);,Thread,maxThread,=new,myThread(MaxPriorityThread,);,minThread.setPriority(Thread.MIN_PRIORITY,);,normThread.setPriority(Thread.NORM_PRIORITY,);,maxThread.setPriority(Thread.MAX_PRIORITY,);,minThread.start,();,normThread.start,();,maxThread.start,();,【,实例,8-5】,程序输出结果:,the Thread Name=MaxPriorityThread,,,Priority=10,the Thread Name=NormPriorityThread,,,Priority=5,the Thread Name=MinPriorityThread,,,Priority=1,9.3.2,资源冲突,多个线程同时运行虽然可以提高程序的执行效率,但由于共享一组资源,可能会产生冲突,例如【实例8-6】。,【,实例,8-6】,class,myThread,void,Test(int,n),(,运行线程,NO,:,+n);,tryThread.sleep(3);,catch(InterruptedException,e),(,线程异常,,,NO,:,+n);,(,结束线程,NO,:,+n);,【,实例,8-6】,class,rThread,implements,Runnable,myThread,Obj,;,int,num;,rThread(myThread,t,,,int,n),Obj,=t;,num=n;,public void run(),Obj.Test(num,);,【,实例,8-6】,public class,ThreadClash,public static void,main(String,args,),myThread,Obj,=new,myThread,();,Thread t1=new,Thread(new,rThread(Obj,,,1);,Thread t2=new,Thread(new,rThread(Obj,,,2);,Thread t3=new,Thread(new,rThread(Obj,,,3);,t1.start();t2.start();t3.start();,【,实例,8-6】,程序运行结果:,运行线程 NO:1,运行线程 NO:2,运行线程 NO:3,结束线程 NO:1,结束线程 NO:2,结束线程 NO:3,9.3.3,同步方法,同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。,可以通过 private 关键字来保证数据对象只能被方法访问,synchronized 关键字:锁定冲突的方法和锁定冲突的对象。,9.3.3,同步方法,1锁标志,每个对象都有一个标志锁。当对象的一个线程访问了对象的某个synchronized数据时,这个对象就将被“上锁”,被声明为synchronized的数据都不能被调用,只有当前线程访问完它要访问的synchronized数据者抛出了没有在 synchronized 块中捕获的异常时,释放“锁标志”后,同一个对象的其它线程才能访问synchronized数据。,这样,每次只有一个线程可以执行给定监控器保护的代码块。从其它线程的角度看,该代码块可以看作是原子的,它要么全部执行,要么根本不执行。,9.3.3,同步方法,1锁标志,锁用于保护代码块或整个方法,必须记住是锁的身份保护了代码块,而不是代码块本身,这一点很重要。一个锁可以保护许多代码块或方法。,反之,仅仅因为代码块由锁保护并不表示两个线程不能同时执行该代码块。它只表示如果两个线程正在等待相同的锁,则它们不能同时执行该代码。,此外 non-static的synchronized数据只能在同一个对象的纯种实现同步访问,不同对象的线程仍可同时访问。,每个class也有一个“锁标志”。对于synchronized static数据可以在整个class下进行锁定,避免static数据的同时访问。对象的“锁标志”和class的“锁标志”是相互独立的。这点在前面已举例说明,在此不在赘述。,9.3.3,同步方法,2.锁定冲突的方法,即在待锁定方法声明中加入 synchronized关键字。这种加上synchronized关键字的方法也称synchronized方法。,格式为:,public synchronized void accessVal(int newVal);,synchronized方法控制对类成员变量的访问:每个类实例对应一把锁,每个synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。,9.3.3,同步方法,2.锁定冲突的方法,synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run()声明为 synchronized,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。,【,实例,8-7】,public class SyncThreads1,private static,int,x,,,y;,private static class Thread1 extends Thread,public synchronized void run(),x=y=0;,System.out.println(x,);,private static class Thread2 extends Thread,public synchronized void run(),x=y=1;,System.out.println(y,);,public static void,main(String,args,),new Thread1().run();,new Thread2().run();,9.3.3,同步方法,3,锁定冲突块(,synchronized,块):,通过,synchronized,关键字来声明,synchronized,块。语法如下:,synchronized(syncObject),/,允许访问控制的代码,synchronized,块是这样一个代码块,其中的代码必须获得对象,syncObject,(如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。,9.3.3,同步方法,3,锁定冲突块(,synchronized,块):,通过,synchronized,关键字来声明,synchronized,块。语法如下:,synchronized(syncObject),/,允许访问控制的代码,synchronized,块是这样一个代码块,其中的代码必须获得对象,syncObject,(如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。,【,实例,8-8】,public class SyncThreads2,private static,int,x,,,y;,private static Object,lockObject,=new Object();,private static class Thread1 extends Thread,public void run(),synchronized(,lockObject,),x=y=0;,System.out.println(x,);,【,实例,8-8】,private static class Thread2 extends Thread,public void run(),synchronized(,lockObject,),x=y=1;,System.out.println(y,);,public static void,main(String,args,),new Thread1().run();,new Thread2().run();,9.4,线程间通信,多线程通信的方法有两种:,1.,把共享变量和方法封装在一个类中实现;,2.,通过,wait(),和,notify(),方法实现。,9.4,线程间通信,9.4.1 共享变量和方法封装在一个类中,9.4.2 通过系统方法实现线程通信,9.4.1,共享变量和方法封装在一个类中,以下通过实例演示了通过将共享变量封装在一个类中,实现线程通信。,【,实例,8-9】,。,class,shareClass,/,共享类,private,int,n;,private,boolean,flag=false;,void,produce(int,i),while(flag,),n=i;,flag=true;,System.out.println(n,产生数据:,+n);,void get(),while(!flag,),flag=false;,(,读取数据:,+n);,【,实例,8-9】,/,读取数据类,class Producer implements,Runnable,shareClass,shc,;,Producer(shareClass,c),shc,=c;,public void run()/,生产者产生,5,个随机数,for(int,i=0;i5;i+),shc.produce(int)(Math.random,()*100);,class Consumer implements,Runnable,shareClass,shc,;,Consumer(shareClass,c),shc,=c;,public void run(),for(int,i=0;i5;i+),shc.get,();/,消费者读取数据,【,实例,8-9】,public class,TheadsCommunication,public static void,main(String,args,),shareClass,shc,=new,shareClass,();,Thread t1=new,Thread(new,Producer(shc,);,Thread t2=new,Thread(new,Consumer(shc,);,t1.start();,t2.start();,【,实例,8-9】,程序的某次运行结果:,产生数据:47,读取数据:47,产生数据:73,读取数据:73,产生数据:69,读取数据:69,产生数据:98,读取数据:98,产生数据:68,读取数据:68,9.4.2,通过系统方法实现线程通信,多线程通过把任务分成分散的逻辑单元取代了事件循环,消除了循环检测。循环检测通常通过重复检查一个特定条件的循环,一旦条件为真,就采取相应的行动,它浪费了,CPU,时间。例如,经典的队列问题,一个线程产生一些数据,而另一些线程取走数据。假设生产者生产更多的数据之前必须等到消费者结束。它才开始循环检测,浪费许多的,CPU,周期来等待消费者结束。很明显,这种情况并不是我们所期望的。,9.4.2,通过系统方法实现线程通信,为了避免循环检测,Java通过wait()、notify()和notifyAll()方法实现了一个巧妙的进程内通信的机制。这些方法作为Object的final方法来实现,因此所有类都包含有它们。这三个方法只可以在一个同步的上下文访问,而且从计算机科学的角度上来说,它在概念上是很先进的,但是使用这些方法的规则却很简单,。,9.4.2,通过系统方法实现线程通信,wait notify调度机制是几个线程对同一对象进行操作,其中某些线程在一定条件下自动挂起,等待其他线程在一定条件下通知其继续运行。这和join()不同,join()是等其他线程运行完,而wait()是等其他线程向其发出通知。,wait()和notify()是java中每个对象都有的方法,当一个线程执行到对象O的wait()方法时,java虚拟机会自动挂起,进入该对象的wait池等待。当其他线程执行到对象O的notify()方法时,java虚拟机会从对象O的wait池随机取出一个线程放入O的等锁池中,一旦O的标记被其他线程返回,即可运行。也可以执行对象O的notifyALL(),对象O的wait池中所有线程放入O的等锁池中。,图,8-3,线程调度示意图,新建线程,y,就绪线程,O,的等锁池,O,的,wait,池,优先级多列,正在运行线程,x,x.sleep(),x.yield(),z.join(),y.start(),执行到,synchronized(O),标记不存在则进入,O,等锁池,O,标记返还了,线程可运行,有线程执行,O.notify(),或,O.interrupt(),,则终止等待,有,O,的标记且执行到,O.wait(),则释放标记,进入,O,的,wait,池,按优先级排队,高优先级占先优先级,等待条件满足,9.4.2,通过系统方法实现线程通信,(1)wait()函数有两种形式:第一种形式接受一个毫秒值,用于在指定时间长度内暂停线程,使线程进入阻塞状态。第二种形式为不带参数,代表wait()在notify()或notifyAll()之前会持续阻塞。,(2)当对一个对象执行notify()时,会从线程等待池中随机取出一个线程放入O的等锁池中;当对一个对象执行notifyAll()时,会从线程等待池中移走所有该对象的所有线程,并把它们放到锁标志等待池中。,(3)当调用wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其它synchronized数据可被别的线程使用。,9.4.2,通过系统方法实现线程通信,下面的程序错误地实现了生产者/消费者问题的简化形式。它包含了四个类:Queue,试图同步的队列;Producer,产生队列输入的线程对象;Consumer,使用队列数据的线程对象;PC,一个创建单个Queue、Producer和Consumer的小型类。,【,实例,8-10】,class Queue,int,n;,synchronized,int,get(),System.out.println(Get,:,+n);,return n;,synchronized void,put(int,n),this.n,=n;,System.out.print(Put,:,+n);,【,实例,8-10】,class Producer implements,Runnable,Queue q;,Producer(Queue,q),this.q,=q;,new,Thread(this,,,Producer).start,();,public void run(),int,i=0;,while(true,),q.put(i,+);,【,实例,8-10】,class Consumer implements,Runnable,Queue q;,Consumer(Queue,q),this.q,=q;,new,Thread(this,,,Consumer).start,();,public void run(),while(true)q.get,();,【,实例,8-10】,class PC,public static void,main(String,args,),Queue q=new Queue();,new,Producer(q,);,new,Consumer(q,);,System.out.println(Press,control-C to stop.);,【,实例,8-10】,class PC,public static void,main(String,args,),Queue q=new Queue();,new,Producer(q,);,new,Consumer(q,);,System.out.println(Press,control-C to stop.);,
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传

当前位置:首页 > 包罗万象 > 大杂烩

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2026 宁波自信网络信息技术有限公司  版权所有

客服电话:0574-28810668  投诉电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服