第5章多线程

上传人:gu****n 文档编号:243133683 上传时间:2024-09-16 格式:PPT 页数:39 大小:416.50KB
返回 下载 相关 举报
第5章多线程_第1页
第1页 / 共39页
第5章多线程_第2页
第2页 / 共39页
第5章多线程_第3页
第3页 / 共39页
点击查看更多>>
资源描述
单击此处编辑母版文本样式,第二级,第三级,第四级,Page,*,单击此处编辑母版标题样式,多线程,第 五 章,2,目标,了解多线程的概念,掌握如何创建线程,了解死锁的概念,掌握线程同步,掌握使用,wait(),和,notify(),在线程之间进行通信,本章相关词汇(蓝色为关键字),单 词,说 明,thread,线程,4,多任务处理,什么是进程呢?,进程包括运行中的程序和程序所使用到的各系统资源。,基于进程的特点是允许计算机同时运行两个或更多的程序。,而一个进程又是由多个线程所组成的,线程是程序中的一个执行流,每个线程都有自己独有的栈空间,但代码区是共享的,即不同的线程可以执行同样的函数。,多任务处理有两种类型:,- 基于进程,- 基于线程,5,思考,我们的,CPU,真的能够同时执行多个任务吗?,线程就是增加一个新的虚拟,cpu,吗?,那么为什么要多任务?,如果没有多进程,如果没有多线程,进程与线程的区别:,进程,Process,:简单来说就是一个程序在给定的活动空间和,初始条件,在一个处理机上的执行过程。可以简单的理解为,进程就是一个在运行的程序,它是由一个或多个线程组成。,线程,Threed,:一个线程是一个程序内部的一个顺序控制流。,即:一个或多个线程组成一个进程。,两者区别:,1.,多个进程的内部数据和状态都是完全独立的,而,多线程是共享一块内存空间和一组系统资源,有可能会影响,2.,线程本身的数据通常只有寄存器数据,以及一个程序执行,时使用的堆栈,所以线程的切换比进程的负担要小。,线程优势:,1.,减轻编写交互频繁,涉及面多的程序困难;,2.,程序的吞吐会得到改善(如同时监听多个设备,网络端口),3.,多个处理器的系统可以并发运行不同的线程,开发所需开销小,多线程,在,Java,中,一个应用程序可以包含多个线程。每个线程执行特定的任务,并可与其他线程,并发,执行,基于线程所需的开销更少,在多任务中,各个进程需要分配它们自己独立的地址空间,多个线程可共享相同的地址空间并且共同分享同一个进程,进程间调用涉及的开销比线程间通信多,线程间的切换成本比进程间切换成本低,运用线程的结果类似于大家常说的“并行处理”,其实在我们日常生活中也常常进行着“并行处理”,您的血液循环、饥饿、愤怒可能同时发生;您的视、听、言、行也可能同时进行;您一面写文章、一面打印成报表、一面还收电子邮件,也在同时进行着;您一面开车、一面吹冷气、一面听音乐等都是“并行处理”的例子,主线程,在,Java,程序启动时,一个线程立刻运行,该线程通常称为程序的主线程。,每个,Java,程序都至少有一个线程主线程。,主线程的重要性体现在两个方面:,它是产生其他子线程的线程。,通常它必须最后完成执行,因为它执行各种关闭动作。,主线程示例,class,Mythread,public static void,main(String,args,) ,Thread t=,Thread.currentThread,();,System.out.println,(,当前线程是:,+,t);,t.setName(MyJavaThread,);,System.out.println,(,当前线程名是:,+,t);,try ,for(int,i=0;i3;i+) ,System.out.println(i,);,Thread.sleep(1500);,catch(InterruptedException,e) ,System.out.println,(,主线程被中断,); ,获得当前线程,即主线程,改变线程的内部名称,输出每个数后暂停1500毫秒,创建子线程,2-1,要想创建线程,最简单的办法就是继承,java.lang.Thread,要触发一个新线程,使用,start(),方法,,如:,Mythread,t = new,Mythread,();,t.start,();,在调用,start(),方法时,将创建一个新的控制线程,接着它将调用,run(),方法。,run(),属于那些会与程序中的其他线程“并发”或“同时”执行的代码。,线程生命周期,新建,:,当一个,Thread,类或者其子类的对象被声明并创建时,新的线程对象处于新建状态,此时它已经有了相应的内存空间和其他资源,.,就绪,:,处于新建状态的线程被启动后,将进入线程队列排队,这个时候具备了运行的条件,一旦轮到,CPU,的时候,就可以脱离创建它的主线程独立开始自己的生命周期,.,运行,:,就绪的线程被调度进入运行状态,每一个,Thread,类及其子类的对象都有一个重要的,run(),方法,当线程对象被调度执行的时候,它将自动调用本对象的,run(),方法,从第一句代码开始执行。所以说对线程的操作应该写到,run(),方法中,.,阻塞(中断),:,一个正在执行的线程如果再某种情况下不能执行了,.,进入阻塞状态,这个时候它不能进入排队状态,只有引起了阻塞的原因消失的时候,线程才可以继续进入排队状态等待,CUP,处理。,死亡,:,处于死亡状态的线程不具有继续执行的能力,线程死亡主要的原因是正常运行的线程完成了全部工作,即执行完了,run(),方法,另外就是被提前强制的终止了。,线程的生命周期内的状态,线程的生命周期是指线程从产生到结束的整个过程,在这个过程中线程通常会呈现以下的几种状态:,新线程状态,(New Thread),可运行状态,(,Runnable,),运行状态,(Running),非运行状态,(Not,Runnable,),死亡状态,(Dead),线程优先级,线程的优先级,(priority),的作用是,告诉线程调度机制这个线程的重要程度的高低。,Java,中的线程优先级是在,Thread,类中定义的常量,NORM_PRIORITY,:,值为 5,MAX_PRIORITY,:,值为,10,MIN_PRIORITY,:,值为,1,有关优先级的方法有两个,:,final void,setPriority(int,newp,),:,修改线程的当前优先级,final,int,getPriority,(),:,返回线程的优先级,自私的线程,有很高的优先权的线程,不主动睡眠或让出处理器控制权,Thread,类中的重要方法,方法,用途,final String,getName,( ),返回线程的名称,final void,setName(String,name),将线程的名称设置为由,name,指定的名称,void start( ),调用,该,方法启动线程,开始线程的执行,static void sleep( ),用于将线程挂起一段时间,static,int,activeCount,( ),返回激活的线程数,static void yield(),自动放弃,CPU,,以便其他线程能够运行,自己到队列的最后等待,final void join( ),等待线程执行完毕,创建子线程,2-2,声明一个实现,Runnable,接口的类,并实现,run(),方法。,class,mythread,implements,Runnable,public void run( ) ,/*,实现该方法*/,例子:,RunnableThread1.java,RunnableDemo.java,如果对象是,Runnable,,那只说明它有,run( ),方法,这并没有什么特别的,这一点同,Thread,的派生类不同的。所以你必须像例程那样,用,Runnable,对象去创建线程。把,Runnable,对象传给,Thread,的构造函数,创建一个独立的,Thread,对象。接着再调用那个线程的,start( ),,由它来进行初始化,然后线程的调度机制就能调用,run( ),了。,共享资源,有时两个或多个线程可能会试图同时访问一个资源,通常共享资源就是一段内存,其表现形式就是对象,不过也可以是文件,,I/O,端口或打印机之类的,例如同时往一台打印机上输出,或者一个线程可能尝试从一个文件中读取数据,而另一个线程则尝试在同一文件中修改数据,多线程环境的最本质的问题,:,永远也不会知道线程会在什么时候启动,并发程序要处理的问题就是资源共享的问题,对于多线程环境,你必须要有办法能防止两个线程同时访问同一个资源,解决共享资源的冲突,要防止这种冲突很简单,只要在线程运行的时候给资源上锁就行了。第一个访问这个资源的线程给它上锁,在它解锁之前,其它线程都不能访问这个资源,接着另一个线程给这个资源上锁然后再使用,如此循环。,实际上所有的多线程架构都采用串行访问的方式来解决共享资源的冲突问题。也就是说,同一时刻只有一个线程可以访问这个共享资源。通常是这样实现的,在代码的前后设一条加锁和解锁的语句,这样同一时刻只有一个线程能够执行这段代码。由于锁定语句会产生,互斥,(mutual exclusion),的效果,因此这一机制通常也被称为,mutex,。,Java,中的同步解决方案,Java,提供了内置的防止资源冲突的解决方案,这就是,synchronized,关键词。,每个对象都有一个锁,(,也称监控器,monitor),,它是对象生来就有的东西。当你执行一个对象的任何,synchronized,代码时,这个对象就被锁住了。,对任何一个特定的对象,所有的,synchronized,代码都会共享同一个锁,,而这个锁能防止两个或两个以上线程同时读写一块共用内存。,只要有一个线程还在调用,synchronized,方法,其它线程就不允许访问所有的,synchronized,方法。当线程离开,synchronized,方法,对象就被解锁,synchronized,并不是保护数据不被访问,只是保证同一时刻只有线程在运行,有了,synchronized,,多线程程序才变得可以控制,synchronized(,同步,),这同步的是谁?,public class A,public synchronized f(),这同步的,又,是谁?,好处又是什么?,public class A,Person p;,public f(),synchronized (this) ,public f(),synchronized (p) ,同步小结,当两个或多个并发线程访问同一个对象中的,synchronized,代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。,然而,当一个线程访问对象的一个,synchronized,同步代码块时,另一个线程仍然可以访问该对象中的非,synchronized,同步代码块。,当一个线程访问对象的一个,synchronized,同步代码块时,它就获得了该对象的对象锁。结果,其它线程对该对象所有同步代码部分的访问都被暂时阻塞。,以上规则对其它对象锁同样适用,.,回顾和思考,如果一个应用程序没有实现多线程,则意味着什么?,在多线程环境下,对于一个共享资源,会出现什么情况?需要采取什么措施?,线程间的通信,要做到协调多个线程对共享数据的存取这一点,关键是要让线程能相互,“,协商,”,。而这个任务要由,Object,的,wait( ),和,notify( ),来完成。,避免轮流检测,,Java,提供了一个精心设计的线程间通信机制,使用,wait()、notify,(),和,notifyAll,(),方法 。,这些方法是作为,Object,类中的,final,方法实现的。,这三个方法仅在,synchronized,方法中才能被调用。,wait-notify,机制,wait(),方法告知被调用的线程解锁并进入阻塞状态,直到其他线程调用,notify( ),方法。,notify( ),随机地选取等待某个对象的线程,让它离开阻塞状态。,notifyAll,(),方法让等待某个对象的所有线程离开阻塞状态,小结:,wait(),让线程等待,,notify(),和,notifyall,(),激活某个等待线程,其实就是撤销该线程的阻塞状态,从而使他们有机会再次运行,用管道进行线程间的,I/O,操作,在很多情况下,线程也可以利用,I/O,来进行通信。多线程类库会提供一种“管道(,pipes)”,来实现线程间的,I/O。,对,Java I/O,类库而言,这个类就是,PipedWriter,(,可以让线程往管道里写数据)和,PipedReader,(,让另一个线程从这个管道里读数据)。,引起阻塞状态的原因,用,sleep(milliseconds,),。在此期间,线程是不能运行的。,用,wait( ),方法把线程挂了起来。除非收到,notify( ),或,notifyAll,( ),消息,否则线程无法重新进入,runnable,状态。,线程在等,I/O,结束。,线程要调用另一个对象的,synchronized,方法,但是还没有得到对象的锁。,思考,线程池的实现,如,Web,服务器、数据库服务器、文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务。 服务器应用程序中经常出现的情况是:单个任务处理的时间很短而请求的数目却是巨大的。如何解决?,关于线程间通讯的原则,通讯原则确实很复杂,不过使用起来实际上是相当简单的。你只需要按照下面这,5,个原则进行操作即可:,对于访问某个关键共享资源的所有方法,都必须把它们设为,synchronized,,否则就不能正常地工作,如果一个线程必须等待某个对象的状态出现变化,那么它应该在对象的内部等待,而不是在外边等待,这可通过输入一个,synchronized,方法,并且调用,wait,方法来实现,不要在,synchronized,方法中花费大量时间。大多数操作只是更新数据结构,然后很快返回。如果你不能立即完成,synchronized,方法的操作,那么请调用,wait,方法,这样就可以在等待时释放该对象锁,每当一个方法改变某个对象的状态时,它就应该调用,notifyAll,方法。这样可以给等待线程一个机会,请记住,,wait,和,notifyAll,/notify,方法都属于,Object,类的方法,而不是,Thread,类的方法。请反复检查你对,wait,方法的调用与同一个对象上的通知是否匹配,线程安全问题,如果对共享数据都是只读操作,则线程安全的,当对共享数据同时进行读写操作如果时采取了同步措施,调用者不需要考虑数据同步问题,则线程安全的,其他情况则不是线程安全的,停止线程,很多情况下,,run,方法中都是一个无限循环,当一个线程执行完所有语句后就自动终止,要放弃,stop( ),方法,如果希望线程正常终止,可采用标记来使线程中的,run,()方法退出。,打断受阻的线程,有时线程在阻塞之后就不能再做轮询了,比如在等输入,这时就不能像前面那样去查询标记了。碰到这种情况,你可以用,interrupt( ),方法打断受阻的线程,更好的习惯是让打断后的线程成为垃圾,死锁,由于线程能被阻塞,更由于,synchronized,方法能阻止其它线程访问本对象,因此有可能会出现如下这种情况:线程一在等线程二(释放某个对象),线程二又在等线程三,这样依次排下去直到有个线程在等线程一。这样就形成了一个环,每个线程都在等对方释放资源,而它们谁都不能运行。这就是所谓的死锁(,deadlock,),。例如:,如果已经持有一个锁并试图获取另一个锁时,就有死锁的危险,线程,2,pen,线程,1,note,把“货”给我,我,才能给你“钱,”,把“钱”给我,我,才能给你“或”,死锁的深入研究,产生死所的四个必要条件,互斥使用资源,占有并等待资源,不可抢夺资源,循环等待资源,死锁的防止,破坏占有并等待条件,资源的静态分配、释放已占有资源,破坏不可剥夺条件,允许抢夺资源,破坏循环等待条件,资源有序分配法,补充,类锁,也可将类的静态成员函数声明为,synchronized,,以控制其对类的静态成员变量的访问,精灵线程,(daemon thread),只要程序还在运行,它就应该在后台提供某种公共服务的线程,但是守护线程不属于程序的核心部分。因此,当所有非守护线程都运行结束的时候,程序也结束了。相反,只要还有非守护线程在运行,程序就不能结束。比如,运行,main( ),的线程就属于非守护线程。,线程组,常见错误,IllegalMonitorStateException,什么情况下使用多线程,多线程的用途很广。主要应用方面是程序的某一部分正在等一个事件或资源,同时又不想为它而暂停程序其他部分的执行,因此你可以创建一个与该事件或资源相关的线程,让它与主程序分开来运行。,总结 2-1,多线程允许程序员编写可最大程度利用,CPU,的高效程序。,Java,以类和接口的形式为多线程提供内置支持。,Java,程序启动时,一个线程立刻运行,该线程称为主线程。,可通过两种方式创建线程:继承,Thread,类、实现,Runnable,接口,。,总结2-2,Thread,类,的有两个构造函数。,线程的缺省优先级为,5。,作为后台线程并为其他线程提供服务的线程称为精灵线程。,synchronized(),并不是保护数据不被访问,只是保证同一时刻只有一个线程在运行。,wait-notify,机制,用来处理线程间通信,作业:,1.,尝试使用多线程复制一个文件。,2.,写一个多人操作同一个银行帐号的存钱和取钱操作,
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 图纸专区 > 小学资料


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!