• / 58
  • 下载费用:10 金币  

第1讲-多线程.ppt

关 键 词:
第1讲-多线程.ppt
资源描述:
第1讲 多线程,学习目标,理解线程的定义。 掌握线程的两种创建方法。 掌握线程的生命期及状态转换。 理解线程的同步和通信。 掌握线程控制的一些方法。,概述,进程和线程 单线程和多线程,进程和线程,进程:程序是静态的代码,进程是处于运行过程中程序,具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。进程的三个特征:独立性、动态性和并发性。 线程:一个进程中可以包含一个或多个线程,一个线程就是一个程序内部的一条执行线索。当进程被初始化后,主线程就被创建了。线程可以共享内存单元和系统资源,但不能够单独执行,必须存在于某个进程当中。 线程的调度和管理由进程本身负责完成。 多线程应用:一个浏览器必须能够同时下载多个图片;一个web服务器必须能同时响应多个用户请求;Java虚拟机本身在后台提供了一个超级线程来进行垃圾回收;图形用户界面需要启动单独的线程来从主机环境收集用户界面事件。,单线程和多线程,单线程:一个进程中只包含一个线程,也就是说一个程序只有一条执行路线。 多线程:在单个进程中可以同时运行多个不同的线程执行不同的任务。 示例:SingleThread.javaMultiThread.javaThreadDemo1.java,执行多线程的时候,Java虚拟处理机在多个线程之间轮流切换,对于单CPU来说每个时刻只能有一个线程在执行 。 main方法是Java的入口程序,一旦进入就启动了一个main线程 。 即使main方法执行完最后一句, Java程序也会一直等到所有线程都运行结束后才停止 。,多线程的创建,线程体继承Thread子类创建线程使用Runnable接口创建线程两种创建方法的比较,线程体,线程中真正执行的语句块称为线程体。 方法run()就是一个线程体,在一个线程被建立并初始化以后,系统自动调用run()方法。 开始启动线程的方法是start()方法。run()方法是供start()调用的,不能直接自己调用,否则就不是启动线程了而是和调用普通方法一样。,继承Thread类创建线程,继承Thread类并重写其中的方法run( )来实现,把线程实现的代码写到run( )方法中,线程从run( )方法开始执行,直到执行完最后一 行代码时线程消亡。编程练习:创建并运行三个线程: 第一个线程打印a 100次。 第二个线程打印b 100次。 第三个线程打印1-100的整数。,后台线程与联合线程,setDaemon(boolean on)方法是把调用该方法的线程设置为后台线程。线程默认为前台线程,也就是用户线程。 把一个线程设置为后台线程时,后台线程在所有前台线程运行完毕,如果它的run()方法还没执行结束,后台线程也会立刻结束。把一个线程设置为后台线程方式如下:setDaemon(true),必须在启动前设为后台线程。 pp.join()方法是把pp线程合并到调用此语句所在的线程中去。,实现Runnable接口创建线程,举例:编写一个应用程序模拟铁路售票系统,实现通过四个售票点发售某日某次列车的100张车票,一个售票点要用一个线程来表示。 采用该方式来创建线程,必须使用Thread 类的构造方法,把采用Runnable接口类的对象作为参数封装到线程对象当中。,两种创建方法的比较,利用Runnable接口,可以避免由于Java的单继承特性带来的局限。 利用Runnable接口,适合多个线程去处理同一资源的情况,从而把虚拟CPU、代码和数据有效分离,形成清晰的模型,较好的体现了面向对象的思想。 有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程可以操作相同的数据,与它们的代码无关。当共享相同的对象时,即共享相同的数据。 实际编程时都是采用实现Runnable接口的方式来实现多线程。,线程的生命期及其状态,线程的状态线程的状态转换图与线程状态有关的Thread类的方法,线程的状态,线程的生命期是指从线程被创建开始到死亡的过程。通常包括以下5种状态: 新建状态 就绪状态 运行状态 阻塞状态 死亡状态,新建状态,当用Thread类或其子类创建了线程对象时,该线程对象就处于新建状态,系统为该新线程分配了内存空间和其他资源。,就绪状态,线程就绪排队等待CPU调度的状态。有以下情况使得线程进入就绪状态: 新建状态的线程被启动,但不具备运行的条件; 处于正在运行的线程时间片结束; 调用yield()方法主动放弃CPU资源; 被阻塞的线程引起阻塞的因素消除了,进入队列等待CPU的调度。,运行状态,当线程被调度获得了CPU控制权的时候,就进入了运行状态。线程在运行状态时,会调用本对象的run()方法 。,阻塞状态,当运行的线程被人为挂起或由于某些操作使得资源不满足的时候,暂时终止自己的运行,让出CPU进入阻塞状态。 有下面原因使得线程进入阻塞状态: 调用了join ()方法 调用了wait()方法 调用了sleep()方法 由于等待I/O操作而引起阻塞 一个线程已经对某对象加了锁,而另一个线程试图在给同一对象加锁,死亡状态,线程消亡有两种情况: 线程的run()方法执行完所有的任务正常地结束; 使用其他方法强行终止线程的执行; 注意:如果线程已经消亡,则不能再重新启动执行。,线程的状态转换图,与线程状态有关Thread类方法,线程状态的判断线程的新建和启动线程的阻塞和唤醒线程的停止,线程状态的判断,public final boolean isAlive() isAlive()方法判断线程是否在运行,如果是就绪、运行及阻塞状态,返回true,否则返回false。不管是线程未开启还是结束,isAlive()方法都会返回false。,线程的新建和启动,public void start() 通过new Thread()方法可以创建出一个线程对象,不过此时Java虚拟机并不知道它,因此,我们需要通过start()方法来启动它。只能启动一次,如果已经处于消亡状态也不能再start()。,线程的阻塞和唤醒,wait()方法sleep()方法join()方法yield()方法suspend ()方法,wait()方法,public final void wait() public final void wait(long time) public final void wait(long time,int args) 调用wait()方法的线程必须通过调用notify()方法来唤醒它。方法定义如下: public final void notify() public final void notifyAll() 其中,notify()方法是随机唤醒一个等待的线程notifyAll()方法是唤醒所有等待的线程。,sleep()方法,public static void sleep(long time) public static void sleep(long time,int args),实例方法将作用在该方法所在的线程示例,而静态方法将作用在调用它的线程上,这一点必须区分清楚 Thread的sleep()方法使线程进入睡眠状态,但它并不会释放线程持有的资源,不能被其他资源唤醒,不过睡眠一段时间会自动醒过来,而wait()方法让线程进入等待状态的同时也释放了持有的资源,能被其他线程唤醒。SleepyHead.java,join()方法,join()方法是指线程的联合,即在一个线程运行过程中,若其他线程调用了join()方法与当前运行的线程联合,运行的线程会立刻阻塞,直到与它联合的线程运行完毕后才重新进入就绪状态,等待CPU的调度。 public final void join() public final void join(long time) public final void join(long time,int args),yield()方法,public static void yield() 线程调用该方法后,会放弃CPU资源进入就绪队列,等待下一次竞争CPU重新运行。 TestYield.java,suspend ()方法,在Java2之前,可以利用suspend()和resume()方法对线程挂起和恢复,但这两个方法可能会导致死锁,因此现在不提倡使用。 Java语言建议采用wait()和notify()来代替suspend()和resume ()方法。,线程的停止,使用stop()方法停止一个线程,不过stop()方法是不安全的,停止一个线程可能会使线程发生死锁,所以现在不推荐使用了。 Java建议使用其他的方法来代替stop()方法,比如可以把当前线程对象设置为空,或者为线程类设置一个布尔标志,定期地检测该标志是否为真,如要停止一个线程,就把该布尔标志设置为true。示例:ThreadStop.java 分析售票程序实现售完票就结束线程的执行,线程的同步,问题提出synchronized方法代码级同步死锁线程间的通信,问题提出,由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。 如果在线程体中对一些访问共享数据的代码或者不能打断顺序执行完整性的代码进行同步互斥控制,就可以使各个线程以固定秩序访问共享数据,从而避免这些问题。 这种考虑了多线程竞争资源访问冲突的代码或程序称为线程安全的。某个类是否是线程安全,就是该类的同一个实例对象的方法在多个线程被调用,是否会出现不一致的情况。 Java中线程同步有两种机制:代码级同步和方法级同步。,同步代码块,定义共享对象的类时,可以只对其成员方法中的某段代码进行同步控制,采用一种加锁的方法。 代码块同步的语法如下:synchronized (Object o){//被同步的代码块}线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定。虽然Java程序允许使用任何对象来作为同步监视器,但通常推荐使用可能被并发访问的共享资源充当同步监视器。,同步方法,通过在方法声明中加入synchronized关键字来声明同步方法,其同步监视器为this对象。 synchronized 方法:每个类实例对应一把锁(同步监视器),每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入就绪状态。这种机制确保证了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员方法中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为synchronized)。,多个线程要实现同步时,不管是同步方法还是同步代码,一定要考虑是否是使用同一个对象监视器,只有使用了同一个对象监视器的多个线程之间才能实现多线程并发访问相同资源的问题。 同一代码块在多个线程间可以实现同步,而且若干个不同的代码块也可以实现相互之间的同步,只要使用的是同一个对象监视器。例题:假设创建并启动100个线程,每个线程都往同一个账户中添加一个便士。假定开始时账户是空的。 AccountSync.java(对题目进行分析),释放同步监视器锁,当前线程的同步方法、同步代码块执行结束,当前线程即释放同步监视器。 当线程在同步代码块、同步方法中遇到break、return终止该代码块、该方法的继续执行,当前线程将会释放同步监视器。 当线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、同步方法异常结束时释放同步监视器。 当线程执行同步代码块或同步方法时,程序执行了同步监视器的wait方法,则当前线程暂停,并释放同步监视器。,同步锁Lock,JDK1.5之后,Java提供了另外一种线程同步的机制:它通过显式定义同步锁对象来实现同步,在这种机制下,同步锁应该使用Lock对象充当。 Lock锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。,死锁问题,如果两个线程都在互相等待对方持有的锁,那么这些线程都进入阻塞状态,永远等待下去,无法执行,程序就出现了死锁。 例如:一个线程进入对象X的监视器,而另一个线程进入了对象Y的监视器,这时进入X对象监视器的线程如果还试图进入Y对象的监视器就会被阻隔,接着进入Y对象监视器的线程如果试图进入X对象的监视器也会被阻隔,这两个线程都处于阻塞状态。演示死锁示例,线程间的通信,问题引出—生产者和消费者问题将需要共同访问的数据定义成一个类,每个线程类的构造器设置一个参数用以接受共享数据类对象,为此每个线程需定义一个共享数据类型的成员变量。优点:可以把对共同访问数据的一些操作方法封装到共享数据类中,结构清晰,符合面向对象程序设计的思想要求。,线程间的通信,wait/notify实现线程间的通信格式如下: synchronized(obj) { if(!condition) { obj.wait(); } doSomething(); } 当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就obj.wait(),告诉当前线程放弃对象监视器,进入此对象监视器的等待队列。另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒obj.notify()同一对象监视器等待队列的线程A,只有当前线程放弃对该对象监视器的锁定后,才可以执行被唤醒的线程。 synchronized(obj) { condition = true;obj.notify(); …… }注意:一个线程中使用了wait,必然在另外一个线程中要使用notify或notifyAll来唤醒对应的等待线程。这个是必须成对出现的。,线程间的通信,需要注意的概念是: 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {……} 内,特别强调必须使用同一个对象监视器obj。 调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj) {……} 代码段内唤醒A. 当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。 如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。 obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。 当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。,线程间的通信,示例分析: 1、ThreadCooperation.java 2、编写一个栈Stack类,具有一个数组成员变量和一个栈顶位置成员变量,成员方法public void push(int)表示入栈,成员方法public int pop()表示出栈。编写相应的入栈线程和出栈线程,要求两个线程之间实现同步通信。,使用条件变量控制协调,当使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,Condition对象也可以唤醒其他处于等待的线程。 Condition替代了同步监视器的wait、notify及notifyAll功能。 Condition实例实质上被绑定在一个Lock对象上。可以使用Lock对象的方法来获得Condition对象。Condition newCondition();,线程的优先级和调度,线程的优先级 线程的调度,线程的优先级,Java中,给每个线程赋一个从1到10整数值来表示多线程优先级,优先级决定了线程获得CPU调度执行的优先程度。 Thread.MIN_PRIORITY(通常为1)的优先级最小; Thread.MAX_PRIORITY(通常为10)优先级最高, NORM_PRIORITY表示缺省优先级,默认值为5。 每个线程默认的优先级与创建它的父线程具有相同的优先级。 优先级的操作 获得线程的优先级int getPriority(); 改变线程的优先级 void setPriority(int newPriority),线程的调度,Java调度器调度遵循以下原则: 优先级高的线程比优先级低的线程先调度。 优先级相等的线程按照排队队列的顺序进行调度。 在时间片方式下,优先级高的线程要等优先级低的线程时间片运行完毕才能被调度。 在抢占式调度方式下,优先级高的线程可以立刻获得CPU的控制权。 由于优先级低的线程只有等优先级高的线程运行完毕或优先级高的线程进入阻塞状态才有机会运行,为了让优先级低的线程也有机会运行,通常会不时让优先级高的线程进入睡眠或等待状态,让出CPU的控制权。,线程组,入门 线程组的构造ThreadGroup类 的一些方法,入门,线程组是把多个线程集成到一个对象里并可以同时管理这些线程。 每个线程组都拥有一个名字以及与它相关的一些属性。每个线程都属于一个线程组。 在线程创建时,可以将线程放在某个指定的线程组中,也可以将它放在一个默认的线程组。 若创建线程而不明确指定属于哪个组,它们就会自动归属于系统默认的线程组。 一旦线程加入了某个线程组,它将一直是这个线程组的成员,而不能改变到其他的组。,线程组的构造,以下三种Thread类的构造方法实现线程创建的同时指定其属于哪个线程组。 public Thread (ThreadGroup group,Runnable target) public Thread (ThreadGroup group,String name) public Thread (ThreadGroup group,Runnable target,String name),ThreadGroup类 的一些方法,activeCount() //返回线程组中当前所有激活的线程的数目。 activeCountGroupCount() //返回当前激活的线程作为父线的线程组的数目。 getName() //返回线程组的名字。 getParent() //返回该线程的父线程组的名称。 setMaxPriority(int priority) //设置线程组的最高优先级。 getMaxPriority() //获得线程组包含的线程中的最高优先级。 getTheradGroup() //返回线程组。 isDestroyed() //判断线程组是否已经被销毁。 destroy() //销毁线程组及其它包含的所有线程。 interrupt() //向线程组及其子组中的线程发送一个中断信息。 parentOf(ThreadGroup group) //判断线程组是否是线程组group或其子线程组。 setDaemon(booleam daemon) //将该线程组设置为守护状态。 isDaemon() //判断是否是守护线程组。,ThreadGroup类 的一些方法,list() //显示当前线程组的信息。 toString() //返回一个表示本线程组的字符串。 enumerate(Thread[ ] list) //将当前线程组中所有的线程复制到list数组中。 enumerate(Thread[ ] list,boolean args) //将当前线程组中所有的线程复制到list数组中,若args为true,则把所有子线程组中的线程复制到list数组中。 enumerate(ThreadGroup[ ] group)//将当前线程组中所有的子线程组复制到group数组中。 enumerate(ThreadGroup[ ] group,boolean args)//将当前线程组中所有的子线程组复制到group数组中,若args为true,则把所有子线程组中的子线程组复制到group数组中。编程练习:创建并启动100个线程,每个线程都往同一个账户中添加一个便士。假定开始时账户是空的。,线程未处理异常,从JDK1.5,Java加强了线程的异常处理,如果线程执行过程中抛出了一个未处理的异常,JVM在结束线程之前会自动查找是否有对应的Thread.UncaughtExceptionHandler对象,如果找到该处理器对象,将会调用该对象的uncaughtException(Thread t,Throwable e)方法来处理该异常。t代表出现异常的线程,而e代表该线程抛出的异常。 Thread类中提供了两个方法来设置异常处理器: public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)为该线程类的所有线程实例设置默认的异常处理器 public void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)为指定线程实例设置异常处理器 ThreadGroup类实现了Thread.UncaughtExceptionHandler接口,所以每个线程所属的线程组将会作为默认的异常处理器。,线程未处理异常,当一个线程抛出未处理异常时,JVM会首先查找该异常对应的异常处理器,如果找到该异常处理器,将调用该异常处理器处理该异常。否则,JVM将会调用该线程所属的线程组对象的uncaughtException方法来处理该异常,线程组处理异常的流程如下: 如果该线程组有父线程组,则调用父线程组的uncaughtException方法来处理异常。 否则,如果该线程实例所属的线程类有默认的异常处理器,那就调用该异常处理器来处理该异常。 否则,将异常调用栈的信息打印到System.err错误输出流,并结束该线程。ExHandler.java,Callable和Future,JDK1.5中提供了Runnable增强版Callable。该接口中提供了一个call方法可以作为线程执行体,但比run方法功能更强大: call方法可以有返回值 call可以声明抛出异常 JDK1.5提供了Future接口代表call方法的返回值,并为Future接口提供了一个实现类FutureTask,该类同时还实现了Runnable接口,所以可以作为Thread类的target。 创建并启动有返回值的线程的步骤如下: 创建Callable接口的实现类,并实现call方法,该方法将作为线程执行体,且该方法有返回值。 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象call方法的返回值。 使用FutureTask对象作为Thread对象的target创建并启动线程。 调用FutureTask对象的方法来获取子线程执行结束后的返回值。,线程池,JDK1.5开始,Java内建支持线程池。线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象传给线程池,线程池就会启动一条线程来执行该对象的run方法,当run方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run方法。 提供了一个Executors工厂类来产生线程池,该工程类里包含如下几个静态工厂方法来创建线程池: public static ExecutorService newCachedThreadPool() public static ExecutorService newFixedThreadPool(int nThreads) ExecutorService代表一个线程池,它可以尽快执行Runnable或Callable对象所代表的线程。 ScheduledExecutorService 代表可在指定延迟或周期性执行线程任务的线程池。,线程池,当用完一个线程池后,应该调用该线程池的shutdown(),该方法将启动线程池的关闭序列,调用该方法后的线程池不再接受新任务,但会将以前所有已提交任务执行完成。另外,shutdowNow方法试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并放回等待执行的任务列表。 使用线程池来执行线程任务的步骤: 调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。 创建Runnable实现类或Callable实现类的实例,作为线程执行任务。 调用ExecutorService对象的submit方法来提交实例。 当不想提交任何任务时调用ExecutorService对象的shutdown方法关闭线程池。 ThreadPoolTest.java,ThreadLocal实现线程范围共享变量,线程局部变量是用于实现线程内部数据的共享,即对于相同的代码,多个模块在同一线程中运行时要共享同一份数据,而在另外一个线程中执行是共享另外一份数据。 实现方法是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。 每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,其key为各自的线程,value为set进去的值。ThreadLocalTest.java 通常:如果需要进行多个线程之间共享资源,以达到线程之间的通信功能,就使用同步机制;如果仅仅需要隔离多个线程之间的共享冲突,可以使用ThreadLocal。,作业,编写程序:有两个线程:Company和Staff,职员Staff有一个账户,公司每个月把工资存到该职员的账户上,该职员可以从账户上领取工资,职员每次要等Company线程把钱存到账户后,才能从账户上领取工资。提示:两个线程需要共享同一个账户,因此可以定义个账户类Account作为共享数据类,在此类中可以定义同步方法,并且要求按顺序存一个月就要取一个月工资。另外要求账户中一个数组用来存放6个月的工资。(CampanyStaffAccount.java),
展开阅读全文
  微传网所有资源均是用户自行上传分享,仅供网友学习交流,未经上传用户书面授权,请勿作他用。
0条评论

还可以输入200字符

暂无评论,赶快抢占沙发吧。

关于本文
本文标题:第1讲-多线程.ppt
链接地址:https://www.weizhuannet.com/p-10035988.html
微传网是一个办公文档、学习资料下载的在线文档分享平台!

网站资源均来自网络,如有侵权,请联系客服删除!

 网站客服QQ:80879498  会员QQ群:727456886

copyright@ 2018-2028 微传网络工作室版权所有

     经营许可证编号:冀ICP备18006529号-1 ,公安局备案号:13028102000124

收起
展开